第二部分主要是hook相关的内容的学习
PS :
angr == 8.20.1.7
二进制文件和脚本存储于https://github.com/ycdxsb/Challenges/tree/master/angr_ctf
添加条件约束 1 2 3 4 5 6 7 8 9 10 11 12 13 _BOOL4 __cdecl check_equals_DQCSFFYXVUJIKEBQ (int a1, unsigned int a2) { int v3; unsigned int i; v3 = 0 ; for ( i = 0 ; i < a2; ++i ) { if ( *(_BYTE *)(i + a1) == *(_BYTE *)(i + 134520896 ) ) ++v3; } return v3 == a2; }
由于符号执行存在路径爆炸问题,在一些情况下,会出现路径爆炸的问题,比如上面的示例中,本来字符串一起比较即可,但由于实现中单字节比较,就会出现2的16次方的分支,因此选择自己添加条件约束是十分明智的。
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 import angrimport sysimport claripydef main (filepath) : project = angr.Project(filepath) start_address = 0x8048625 init_state = project.factory.blank_state(addr=start_address) password = claripy.BVS('password' , 0x10 *8 ) password_addr = 0x804A050 init_state.memory.store(password_addr, password) sim = project.factory.simgr(init_state) def find (state) : stdout_output = state.posix.dumps(sys.stdout.fileno()) return b"Good Job." in stdout_output def avoid (state) : stdout_output = state.posix.dumps(sys.stdout.fileno()) return b"Try again." in stdout_output sim.explore(find=0x804866C ) if sim.found: solution_state = sim.found[0 ] constrained_parameter_address = 0x804A050 constrained_parameter_size_bytes = 0x10 constrained_parameter_bitvector = solution_state.memory.load( constrained_parameter_address, constrained_parameter_size_bytes ) constrained_parameter_desired_value = "DQCSFFYXVUJIKEBQ" constraint_expression = constrained_parameter_bitvector == constrained_parameter_desired_value solution_state.add_constraints( constrained_parameter_bitvector == constrained_parameter_desired_value) solution = solution_state.se.eval( password, cast_to=bytes) print(solution) else : raise Exception('Could not find the solution' ) if __name__ == "__main__" : if (len(sys.argv) != 2 ): print('usage:python angr_basic.py filepath' ) filepath = sys.argv[1 ] main(filepath)
简单Hook 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 int __cdecl main (int argc, const char **argv, const char **envp) { _BOOL4 v3; signed int i; signed int j; qmemcpy(password, "JRZSQFKOVMSDNCWS" , 16 ); memset (buffer , 0 , 0x11 u); printf ("Enter the password: " ); __isoc99_scanf("%16s" , buffer ); for ( i = 0 ; i <= 15 ; ++i ) *(_BYTE *)(i + 0x804A054 ) = complex_function(*(char *)(i + 0x804A054 ), 18 - i); equals = check_equals_JRZSQFKOVMSDNCWS((int )buffer , 0x10 u); for ( j = 0 ; j <= 15 ; ++j ) *(_BYTE *)(j + 0x804A044 ) = complex_function(*(char *)(j + 0x804A044 ), j + 9 ); __isoc99_scanf("%16s" , buffer ); v3 = equals && !strncmp (buffer , password, 0x10 u); equals = v3; if ( v3 ) puts ("Good Job." ); else puts ("Try again." ); return 0 ; }
同样是为了解决路径爆炸问题,但由于后续还有其他操作,所以我们在这里需要hook掉比较函数,然后继续向下执行
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 import angrimport claripyimport sysdef main (filepath) : project = angr.Project(filepath) init_state = project.factory.entry_state() sim = project.factory.simgr(init_state) check_equals_called_address = 0x80486B3 instruction_to_skip_length = 5 @project.hook(check_equals_called_address, length=instruction_to_skip_length) def skip_check_equals_ (state) : user_input_buffer_address = 0x804A054 user_input_buffer_length = 0x10 user_input_string = state.memory.load( user_input_buffer_address, user_input_buffer_length) check_against_string = "JRZSQFKOVMSDNCWS" state.regs.eax = claripy.If( user_input_string == check_against_string, claripy.BVV(1 , 32 ), claripy.BVV(0 , 32 )) simulation = project.factory.simgr(init_state) def find (state) : stdout_output = state.posix.dumps(sys.stdout.fileno()) return b"Good Job." in stdout_output def avoid (state) : stdout_output = state.posix.dumps(sys.stdout.fileno()) return b"Try again." in stdout_output simulation.explore(find=find, avoid=avoid) if simulation.found: solution_state = simulation.found[0 ] print(solution_state.posix.dumps(sys.stdin.fileno())) else : raise Exception('Could not find the solution' ) if __name__ == '__main__' : if (len(sys.argv) != 2 ): print('usage:python angr_basic.py filepath' ) filepath = sys.argv[1 ] main(filepath)
Hook时,首先要确定位置和hook掉函数占用的指令数,然后自己实现hook的函数,需要注意的是,返回值在eax寄存器中,需要进行符号化处理
进阶Hook 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 int __cdecl main (int argc, const char **argv, const char **envp) { signed int i; char s[17 ]; unsigned int v6; v6 = __readgsdword(0x14 u); memcpy (&password, "JWRJJJAJTWHCQHPZ" , 0x10 u); memset (s, 0 , 0x11 u); printf ("Enter the password: " ); __isoc99_scanf("%16s" , s); for ( i = 0 ; i <= 15 ; ++i ) s[i] = complex_function(s[i], 18 - i); if ( check_equals_JWRJJJAJTWHCQHPZ((int )s, 0x10 u) ) puts ("Good Job." ); else puts ("Try again." ); return 0 ; }
在一些时候,如果需要Hook的函数存在于多个地方,我们不可能一个一个的去Hook,所以按函数的符号名Hook是比较科学的方法,虽然示例中只出现了一次,但这次我们通过函数符号进行Hook
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 import angrimport claripyimport sysdef main (filepath) : project = angr.Project(filepath) init_state = project.factory.entry_state() sim = project.factory.simgr(init_state) class ReplacementCheckEquals (angr.SimProcedure) : def run (self,data_addr,length) : user_input_string = self.state.memory.load(data_addr,length) check_against_string = "JWRJJJAJTWHCQHPZ" return claripy.If(user_input_string==check_against_string,claripy.BVV(1 ,32 ),claripy.BVV(0 ,32 )) check_equals_symbol = "check_equals_JWRJJJAJTWHCQHPZ" project.hook_symbol(check_equals_symbol,ReplacementCheckEquals()) simulation = project.factory.simgr(init_state) def find (state) : stdout_output = state.posix.dumps(sys.stdout.fileno()) return b"Good Job." in stdout_output def avoid (state) : stdout_output = state.posix.dumps(sys.stdout.fileno()) return b"Try again." in stdout_output simulation.explore(find=find, avoid=avoid) if simulation.found: solution_state = simulation.found[0 ] print(solution_state.posix.dumps(sys.stdin.fileno())) else : raise Exception('Could not find the solution' ) if __name__ == '__main__' : if (len(sys.argv) != 2 ): print('usage:python angr_basic.py filepath' ) filepath = sys.argv[1 ] main(filepath)
Hook scanf函数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 cint __cdecl main (int argc, const char **argv, const char **envp) { _BOOL4 v3; signed int i; char s[4 ]; int v7; unsigned int v8; v8 = __readgsdword(0x14 u); memset (s, 0 , 0x14 u); *(_DWORD *)s = 1329940303 ; v7 = 1179340618 ; for ( i = 0 ; i <= 7 ; ++i ) s[i] = complex_function(s[i], i); printf ("Enter the password: " ); __isoc99_scanf("%u %u" , buffer0, buffer1); v3 = !strncmp (buffer0, s, 4u ) && !strncmp (buffer1, (const char *)&v7, 4u ); if ( v3 ) puts ("Good Job." ); else puts ("Try again." ); return 0 ; }
和前面很像,但这次需要hook我们最常用的scanf函数
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 import angrimport claripyimport sysdef main (filepath) : project = angr.Project(filepath) init_state = project.factory.entry_state() sim = project.factory.simgr(init_state) class ReplacementScanf (angr.SimProcedure) : def run (self,format_string,buffer0_addr,buffer1_addr) : buffer0 = claripy.BVS('buffer0' ,4 *8 ) buffer1 = claripy.BVS('buffer1' ,4 *8 ) self.state.memory.store(buffer0_addr,buffer0,endness=project.arch.memory_endness) self.state.memory.store(buffer1_addr,buffer1,endness=project.arch.memory_endness) self.state.globals['solution0' ] = buffer0 self.state.globals['solution1' ] = buffer1 scanf_symbol = "__isoc99_scanf" project.hook_symbol(scanf_symbol,ReplacementScanf()) simulation = project.factory.simgr(init_state) def find (state) : stdout_output = state.posix.dumps(sys.stdout.fileno()) return b"Good Job." in stdout_output def avoid (state) : stdout_output = state.posix.dumps(sys.stdout.fileno()) return b"Try again." in stdout_output simulation.explore(find=find, avoid=avoid) if simulation.found: solution_state = simulation.found[0 ] solution0 = solution_state.se.eval(solution_state.globals['solution0' ]) solution1 = solution_state.se.eval(solution_state.globals['solution1' ]) print(solution0,solution1) else : raise Exception('Could not find the solution' ) if __name__ == '__main__' : if (len(sys.argv) != 2 ): print('usage:python angr_basic.py filepath' ) filepath = sys.argv[1 ] main(filepath)
需要将符号数据存入buffer0和buffer1中,同时由于变量在对象内声明并且最后需要在对象外使用,因此通过state类的globals实现存取
Veritesting模式 veritesting
是一个符号执行的选项,使用路径融合来解决路径爆炸的问题,例如前面一个字符一个字符比较的情况
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 import angrimport sysdef main (filepath) : project = angr.Project(filepath) init_state = project.factory.entry_state() sim = project.factory.simgr(init_state,veritesting=True ) def find (state) : stdout_output = state.posix.dumps(sys.stdout.fileno()) return b"Good Job." in stdout_output def avoid (state) : stdout_output = state.posix.dumps(sys.stdout.fileno()) return b"Try again." in stdout_output sim.explore(find=find,avoid=avoid) if sim.found: solution_state = sim.found[0 ] print(solution_state.posix.dumps(sys.stdin.fileno())) else : raise Exception('Could not find the solution' ) if __name__=="__main__" : if (len(sys.argv)!=2 ): print('usage:python angr_basic.py filepath' ) filepath = sys.argv[1 ] main(filepath)
在静态编译的二进制中进行Hook 在符号执行时,遇到一些libc中的函数,angr其实都会帮我们hook掉,不然会影响符号执行的速度。所以这里通过实例让我们自己试试使用angr的功能Hook一下,angr已经实现的hook如下:
https://github.com/angr/angr/tree/master/angr/procedures/libc
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 import angrimport sysdef main (filepath) : project = angr.Project(filepath) init_state = project.factory.entry_state() sim = project.factory.simgr(init_state,veritesting=True ) project.hook(0x804EF40 ,angr.SIM_PROCEDURES['libc' ]['printf' ]()) project.hook(0x804EF80 ,angr.SIM_PROCEDURES['libc' ]['scanf' ]()) project.hook(0x804F550 ,angr.SIM_PROCEDURES['libc' ]['puts' ]()) project.hook(0x80491F0 ,angr.SIM_PROCEDURES['glibc' ]['__libc_start_main' ]()) def find (state) : stdout_output = state.posix.dumps(sys.stdout.fileno()) return b"Good Job." in stdout_output def avoid (state) : stdout_output = state.posix.dumps(sys.stdout.fileno()) return b"Try again." in stdout_output sim.explore(find=find,avoid=avoid) if sim.found: solution_state = sim.found[0 ] print(solution_state.posix.dumps(sys.stdin.fileno())) else : raise Exception('Could not find the solution' ) if __name__=="__main__" : if (len(sys.argv)!=2 ): print('usage:python angr_basic.py filepath' ) filepath = sys.argv[1 ] main(filepath)
Hook自定义的动态链接库 这里需要我们Hook自定义的动态链接库中的函数,这里不需要我们去执行这个二进制文件,而是直接通过angr对动态库lib14_angr_shared_library.so
中的validate
函数进行符号执行。
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 import angrimport sysimport claripydef main (filepath) : base = 0x400000 validate_addr_offset = 0x6D7 project = angr.Project(filepath, load_options={ 'main_opts' : { 'custom_base_addr' : base } }) password_addr = claripy.BVV(0xffffff00 , 32 ) validate_addr = base + validate_addr_offset init_state = project.factory.call_state( validate_addr, password_addr, claripy.BVV(8 , 32 )) password = claripy.BVS("password" , 8 *8 ) init_state.memory.store(password_addr, password) sim = project.factory.simgr(init_state) success_addr = base+0x783 def find (state) : stdout_output = state.posix.dumps(sys.stdout.fileno()) return b"Good Job." in stdout_output def avoid (state) : stdout_output = state.posix.dumps(sys.stdout.fileno()) return b"Try again." in stdout_output sim.explore(find=success_addr) if sim.found: solution_state = sim.found[0 ] solution_state.add_constraints(solution_state.regs.eax != 0 ) solution = solution_state.se.eval(password,cast_to = bytes) print(solution) else : raise Exception('Could not find the solution' ) if __name__ == "__main__" : if (len(sys.argv) != 2 ): print('usage:python angr_basic.py filepath' ) filepath = sys.argv[1 ] main(filepath)
之前的hook我们都知道函数的地址,而在动态库中,我们没法直接知道地址,所以需要制定base,并传入参数调用函数。由于只对这个函数进行符号执行,所以也要加入符号执行正确时的约束。