Last updated on January 27, 2024 am
一道题学会angr和IDApython,甚至BFS(:
题目流程很简单,输入一个8个十六进制数,依次根据最低位选择一条路径走,走到会终点即可得到flag
这里是题目
IDApython
观察迷宫的流程图,会发现是很有规律的,每一个block都有两个successor,根据输入决定前往哪一个successor。还要注意一点,就是会有直接jmp块,需要做个小处理。通过IDApython 提取处迷宫的结构,再用BFS算法去走即可。
提取迷宫
大致分为三部分,第一部分是计算每个block的successor,存储为字典形式,这里不区分是普通跳转还是直接jmp跳转
第二部分是记录直接跳转块
第三部分是计算普通跳转是通过什么方式去跳,即是jz还是jnz,也是存储为字典形式
note:在提取迷宫的时候,最好将地址全部转为十进制的整型,否则后面比较会有各种问题(在这卡了好久)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
| start_ea = 0x1500 end_ea = 0x23D9
func = idaapi.get_func(start_ea) cfg = idaapi.FlowChart(func)
Maze = dict() JmpMethod = dict() DirJmp = list() for block in cfg: BlockStart = block.start_ea BlockEnd = block.end_ea Maze[BlockStart] = list()
for succ in block.succs(): if not GetDisasm(succ.start_ea).startswith('jmp'): Maze[BlockStart].append(succ.start_ea) else: Maze[BlockStart].append(int(GetDisasm(succ.start_ea)[-4::],base=16)) if GetDisasm(BlockStart).startswith('jmp'): DirJmp.append(BlockStart) elif BlockStart!=start_ea and BlockStart!=end_ea: JmpMethod[BlockStart] = list() Dst = int(GetDisasm(prev_head(BlockEnd))[-4::],base=16) JmpMethod[BlockStart].append(print_insn_mnem(prev_head(BlockEnd))) JmpMethod[BlockStart].append(Dst)
print("Maze:") print(Maze) print("***************************************************************") print("DirJmp:") print(DirJmp) print("***************************************************************") print("JmpMethod:") print(JmpMethod)
|
BFS
得到迷宫后,就可以用BFS走,DFS应该是走不出来的,因为可能有环的存在。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
| import queue
start = 0x1500 end = 0x23d9 q = queue.Queue() q.put(0x1500)
def maze_walk(): visited = set() father = {0x1500: None} while not q.empty(): curr = q.get() nxs = Maze[curr] for dirc in nxs: if dirc not in visited: visited.add(dirc) father[dirc] = curr q.put(dirc)
if dirc == end: p = [dirc] while father[dirc] is not None: dirc = father[dirc] p.append(dirc)
return p[::-1]
path = maze_walk() p = []
for i in path: if i not in DirJmp: p.append(i) p = p[1:]
ans = '' for i in range(len(p) - 1): if JmpMethod[p[i]][0] == 'jnz' and JmpMethod[p[i]][1] == p[i + 1]: ans += '1' if JmpMethod[p[i]][0] == 'jz' and JmpMethod[p[i]][1] == p[i + 1]: ans += '0' if JmpMethod[p[i]][0] == 'jnz' and JmpMethod[p[i]][1] != p[i + 1]: ans += '0' if JmpMethod[p[i]][0] == 'jz' and JmpMethod[p[i]][1] != p[i + 1]: ans += '1'
ans = int(ans[::-1], 2) print(hex(ans))
|
Angr
主要思路就是模拟迷宫的走法,自定义一个ExplorationTechnique,避免路径爆炸的问题,毕竟 2^64次方还是很大的,里面的各种技术细节可以参考下之前的文章(就是因为这题,才写了这些文章
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109
| import angr import claripy from angr import SimState
class Walk(angr.ExplorationTechnique): """大体思路就是BFS""" def __init__(self,MazeRange:tuple[int ,int],MazeFinal:int) -> None: ''' MazeRange:整个迷宫范围,也就是起始地址到结束地址,注意是整个迷宫,不是起始到终点的范围 MazeFinal:迷宫终点 ''' self._MazeRange = MazeRange self._MazeFinal = MazeFinal self._Vistited = set()
def setup(self,simgr:angr.SimulationManager): """ 定义并初始化两个stash 初始化_Vistited """ simgr.stashes['visited'] =[] simgr.stashes['found'] =[] self._Vistited.add(simgr.active[0].addr)
def isInMaze(self,state:angr.SimState) -> bool: return self._MazeRange[0] <= state.addr < self._MazeRange[1] def StateSplitter(self,states:list[angr.SimState]) -> tuple[list[angr.SimState],list[angr.SimState]]: """ 此函数用于分拣State,如果访问过就不要让这个State加入下一次的循环了 类似于BFS那个核心要点 """ _keep =[] _split =[] for state in states: if self.isInMaze(state): if state.addr in self._Vistited: _split.append(state) else: self._Vistited.add(state.addr) _keep.append(state) else: _keep.append(state)
return _keep,_split def isEnd(self,state:SimState) -> bool: return state.addr == self._MazeFinal def step(self,simgr:angr.SimulationManager,stash :str = 'active',**kwargs) -> angr.SimulationManager: simgr.step(stash=stash,**kwargs)
simgr.split(stash_splitter = self.StateSplitter,from_stash = 'active',to_stash = 'visited')
simgr.move(from_stash = 'active',to_stash = 'found',filter_func = self.isEnd)
return simgr def complete(self,simgr:angr.SimulationManager): return bool(simgr.found)
proj = angr.Project("./maze",auto_load_libs = False,main_opts={'base_addr': 0})
SymMazeWalk = proj.loader.find_symbol("maze_walk") SymMazeFinal = proj.loader.find_symbol("maze_final")
SymKey = proj.loader.find_symbol("key") SymSteps = proj.loader.find_symbol("steps")
KeyValue = claripy.BVS("SymKey",64)
State = proj.factory.call_state(SymMazeWalk.rebased_addr,prototype = 'void f()') State.options.add(angr.sim_options.ZERO_FILL_UNCONSTRAINED_REGISTERS)
State.memory.store(SymKey.rebased_addr,KeyValue) State.memory.store(SymSteps.rebased_addr,KeyValue)
tech = Walk(MazeRange=(SymMazeWalk.rebased_addr,SymMazeWalk.rebased_addr + SymMazeWalk.size), MazeFinal=SymMazeFinal.rebased_addr)
Simgr = proj.factory.simgr(State)
Simgr.use_technique(tech)
Simgr.run()
Simgr.remove_technique(tech)
SolveState = proj.factory.simgr(Simgr.found[0]) SolveState.run()
print(SolveState.deadended[0].posix.dumps(1))
|