符号执行最大的问题就是路径爆炸,当一个程序存在循环结构时,即使逻辑十分简单也可能会产生规模十分巨大的执行路径,本文记录三种处理路径爆炸的方法:
1.避免进入循环结果,手动添加约束条件
循环结果如下
该循环结构在程序中比较两个字符串是否相等,每次循环都会有if语句生成两个不同的分支,路径成指数级增长,该函数可以通过手动添加约束条件来避免执行
solution.found[0].solver.add(string1=='BWYRUBQCMVSBRGFU')
使程序停止在该函数之前,再添加约束条件后求解,得出效果与执行完该函数一致
import angr import sys import claripy def Go(): p=angr.Project("./08con",auto_load_libs=False) istate=p.factory.blank_state(addr=0x08048627) buff_addr=0x0804A050 passwd0=claripy.BVS('passwd0',16*8) istate.memory.store(buff_addr,passwd0) sm=p.factory.simulation_manager(istate) solution=sm.explore(find=0x08048678) //提前停止 print(len(solution.found)) if solution.found: string1=solution.found[0].memory.load(buff_addr,16) //读出此刻状态下的字符串值 solution.found[0].solver.add(string1=='BWYRUBQCMVSBRGFU') //对此刻字符串值条件约束条件 solution0=solution.found[0].solver.eval(passwd0,cast_to=bytes) //求解该字符串 print("[+] Success! Solution is: {}".format(solution0)) else: raise Exception("error") if __name__=="__main__": Go()
2.hooks方法
就是用我们自己设计的函数去取代被hook的函数
找到会导致路径爆炸的函数,用自己编写的函数取代该函数即可,可以利用函数地址hook,也可以利用函数名进行hook
利用函数地址hook:
@p.hook(check_equals_called_address, length=instruction_to_skip_length) def skip_check(state): flag=state.memory.load(buff_addr,16) string1='XKSPZSJKJYQCQXZV' state.regs.eax=claripy.If( string1==flag, claripy.BVV(1,32), claripy.BVV(0,32) )
@p.hook() 第一个参数是需要被hook的函数地址,length是call该函数指令占据的字节大小
如图,addr=0x80486B8,length=D-8
state.regs.eax=claripy.If()
设置寄存器eax的值,使该函数与被hook的函数具有一样的效果,函数的返回值被保存在eax中,字符串相等则返回BVV(1,32),否则返回BVV(0,32)
利用函数名hook
当一个需要被hook的函数被调用了很多次时,利用地址hook便会出现问题,而通过程序的符号表利用函数名完全可以解析出地址
hook函数名的方法:
class Replace_check(angr.SimProcedure): def run(self,user_input,length): user_input_addr=user_input flag=self.state.memory.load(user_input_addr,length) string1='WQNDNKKWAWOLXBAC' self.state.regs.eax=claripy.If( string1==flag, claripy.BVV(1,32), claripy.BVV(0,32) ) check_funcname='check_equals_WQNDNKKWAWOLXBAC' p.hook_symbol(check_funcname,Replace_check())
class Replace_check(angr.SimProcedure) 需要替换的函数名 Replace_check()
def run(self,user_input,length) run函数名固定,self后面的参数与被hook的函数参数保持一致
p.hook_symbol(check_funcname,Replace_check()) 第一个参数时被替换的函数名,第二个参数是构造出来的函数
完成exp
import angr import sys import claripy def Go(): p=angr.Project("./10sim",auto_load_libs=False) istate=p.factory.entry_state() class Replace_check(angr.SimProcedure): def run(self,user_input,length): user_input_addr=user_input flag=self.state.memory.load(user_input_addr,length) string1='WQNDNKKWAWOLXBAC' self.state.regs.eax=claripy.If( string1==flag, claripy.BVV(1,32), claripy.BVV(0,32) ) check_funcname='check_equals_WQNDNKKWAWOLXBAC' p.hook_symbol(check_funcname,Replace_check()) sm=p.factory.simulation_manager(istate) def succ(state): output1=state.posix.dumps(1) if b'Good Job.' in output1: return True else: return False def _abort(state): output2=state.posix.dumps(1) if b'Try again.' in output2: return True else: return False solution=sm.explore(find=succ,avoid=_abort) print(len(solution.found)) if solution.found: solution0=solution.found[0].posix.dumps(0) print(solution0) else: raise Exception("error") if __name__=="__main__": Go()
3.veritesting技术处理路径爆炸
简单粗暴,只需要在angr里我们只要在构造模拟管理器时,启用Veritesting了就行
sm=p.factory.simulation_manager(istate,veritesting=True)
忽视循环结构,直接硬怼,耗时较长
完整版
import angr import sys import claripy def Go(): p=angr.Project("./12ver",auto_load_libs=False) istate=p.factory.entry_state() sm=p.factory.simulation_manager(istate,veritesting=True) def succ(state): output1=state.posix.dumps(1) if b'Good Job.' in output1: return True else: return False def _abort(state): output2=state.posix.dumps(1) if b'Try again.' in output2: return True else: return False solution=sm.explore(find=succ,avoid=_abort) print(len(solution.found)) if solution.found: solution0=solution.found[0].posix.dumps(0) print(solution0) else: raise Exception("error") if __name__=="__main__": Go()