00_angr_find
了解 angr 基础方法后便可轻松求解:
1 2 3 4 5 6 7 8 9 10 import angrp = angr.Project("./00_angr_find" ) init_state = p.factory.entry_state() sm = p.factory.simulation_manager(init_state) sm.explore(find=0x08048678 ) print ("find: " +hex (len (sm.found)))found_state = sm.found[0 ] print (found_state.posix.dumps(1 ))print (found_state.posix.dumps(0 ))
01_angr_avoid
main 函数过大,IDA 分析失败,传统逆向受阻,但可以用 angr 求解:
不过这个脚本分析的时间有点长,添加 avoid
可以进行加速:(告诉 angr 哪些地址不需要到达)
1 2 3 4 void avoid_me () { should_succeed = 0 ; }
逆向分析题目时,发现题目已经给出了提示,在 avoid
处写入此地址即可
1 2 3 4 5 6 7 int __cdecl maybe_good (char *s1, char *s2) { if ( should_succeed && !strncmp (s1, s2, 8u ) ) return puts ("Good Job." ); else return puts ("Try again." ); }
同时,我们还可以把 Try again
处的地址写入 avoid
1 2 3 4 5 6 7 8 9 10 11 import angrp = angr.Project("./01_angr_avoid" ) init_state = p.factory.entry_state() sm = p.factory.simulation_manager(init_state) sm.explore(find=0x080485E0 ,avoid=[0x080485A8 ,0x080485F2 ]) print ("find: " +hex (len (sm.found)))print ("avoid: " +hex (len (sm.avoid)))found_state = sm.found[0 ] print (found_state.posix.dumps(1 ))print (found_state.posix.dumps(0 ))
02_angr_find_condition
这个题目与之前的有所不同,如果按照之前的思路来写脚本是跑不出答案的
用 IDA 分析一下就知道原因了:
程序把 Good Job
和 Try again
分为很多份,每个执行流分支都有一个,并且地址不同
在这些 Good Job
中,肯定有一个是可以到达的,但是我们不确定是哪个,因此也不能确定要查找的地址
于是我们改变 angr 的写法,使其查找输出 Good Job
的那一个路径分支:(用同样的办法可以避免输出 Try again
的那些路径分支)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 import angrp = angr.Project("./02_angr_find_condition" ) init_state = p.factory.entry_state() sm = p.factory.simulation_manager(init_state) def is_good (state ): return b"Good Job" in state.posix.dumps(1 ) def is_bad (state ): return b"Try again" in state.posix.dumps(1 ) sm.explore(find=is_good,avoid=is_bad) print ("find: " +hex (len (sm.found)))print ("avoid: " +hex (len (sm.avoid)))found_state = sm.found[0 ] print (found_state.posix.dumps(1 ))print (found_state.posix.dumps(0 ))
03_angr_symbolic_registers
本题目有多个输入:
1 2 3 4 5 6 7 8 9 10 int get_user_input () { int v1; int v2; int v3[4 ]; v3[1 ] = __readgsdword(0x14 u); __isoc99_scanf("%x %x %x" , &v1, &v2, v3); return v1; }
老版本的 angr 不能很好地处理多输入,因此需要用一些复杂的方法来解决问题(新版本的 angr 可以直接解出答案)
根据程序逻辑,输入的3个字符串的地址分别存于 eax ebx edx
1 2 3 4 5 6 7 8 9 .text:0804892 A push offset aXXX ; "%x %x %x" .text:0804892F call ___isoc99_scanf .text:08048934 add esp, 10 h .text:08048937 mov ecx, [ebp+var_18] .text:0804893 A mov eax, ecx .text:0804893 C mov ecx, [ebp+var_14] .text:0804893F mov ebx, ecx .text:08048941 mov ecx, [ebp+var_10] .text:08048944 mov edx, ecx
在 main 中把 eax ebx edx
放回栈,然后调用对应的函数进行加密
1 2 3 4 .text:0804897B call get_user_input .text:08048980 mov [ebp+var_14], eax .text:08048983 mov [ebp+var_10], ebx .text:08048986 mov [ebp+var_C], edx
既然 angr 不支持多输入,我们就可以直接把 get_user_input
跳过(从 0x08048980
开始执行程序),然后把符号向量放入 eax ebx edx
:
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 import angrimport claripyp = angr.Project("./03_angr_symbolic_registers" ) init_state = p.factory.blank_state(addr=0x08048980 ) password1 = claripy.BVS("password1" ,32 ) password2 = claripy.BVS("password2" ,32 ) password3 = claripy.BVS("password3" ,32 ) init_state.regs.eax=password1 init_state.regs.ebx=password2 init_state.regs.edx=password3 sm = p.factory.simulation_manager(init_state) def is_good (state ): return b"Good Job" in state.posix.dumps(1 ) def is_bad (state ): return b"Try again" in state.posix.dumps(1 ) sm.explore(find=is_good,avoid=is_bad) print ("find: " +hex (len (sm.found)))print ("avoid: " +hex (len (sm.avoid)))found_state = sm.found[0 ] answer1 = found_state.solver.eval (password1) answer2 = found_state.solver.eval (password2) answer3 = found_state.solver.eval (password3) print ("{:x} {:x} {:x}" .format (answer1,answer2,answer3))
04_angr_symbolic_stack
本题目有多个输入:
1 2 3 4 5 6 7 8 9 10 11 12 13 int handle_user () { int v1; int v2[3 ]; __isoc99_scanf("%u %u" , v2, &v1); v2[0 ] = complex_function0(v2[0 ]); v1 = complex_function1(v1); if ( v2[0 ] == 0x773024D1 && v1 == 0xBC4311CF ) return puts ("Good Job." ); else return puts ("Try again." ); }
这次输入的数据存放在栈中,因此需要将符号向量存放入栈中
这时我们要模拟栈帧构建的过程,把符号向量 push
到正确的位置上
1 2 3 4 5 6 7 8 9 10 .text:08048679 push ebp .text:0804867 A mov ebp, esp .text:0804867 C sub esp, 18 h .text:0804867F sub esp, 4 .text:08048682 lea eax, [ebp+var_10] .text:08048685 push eax .text:08048686 lea eax, [ebp+var_C] .text:08048689 push eax .text:0804868 A push offset aUU ; "%u %u" .text:0804868F call ___isoc99_scanf
目标就是把 target1
和 target2
处的局部变量给替换为符号向量
先调试程序,分析输入执行完毕后的栈帧:
1 2 3 4 5 6 7 8 9 10 11 00 :0000 │ esp 0xffc983c0 —▸ 0x80487b3 ◂— 0x25207525 01 :0004 │ 0xffc983c4 —▸ 0xffc983dc ◂— 0x3d 02 :0008 │ 0xffc983c8 —▸ 0xffc983d8 ◂— 0x3d 03 :000 c│ 0xffc983cc ◂— 0x0 04 :0010 │ 0xffc983d0 —▸ 0xf7f24088 (environ) —▸ 0xffc984ac —▸ 0xffc9a15b ◂— 'HTTP_PROXY=http://127.0.0.1:7890/' 05 :0014 │ 0xffc983d4 —▸ 0xf7f75990 ◂— 0x0 06 :0018 │ edx 0xffc983d8 ◂— 0x3d 07 :001 c│ 0xffc983dc ◂— 0x3d 08 :0020 │ 0xffc983e0 —▸ 0x80487ce ◂— 0x65746e45 09 :0024 │ 0xffc983e4 —▸ 0xffc984a4 —▸ 0xffc9a12b ◂— '/home/yhellow/tools/angr/04_angr_symbolic_stack' 0 a:0028 │ ebp 0xffc983e8 —▸ 0xffc983f8 ◂— 0x0
我们只需要关注 target1 target2
的位置,把符号向量 push
到这里即可
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 import angrimport claripyp = angr.Project("./04_angr_symbolic_stack" ) init_state = p.factory.blank_state(addr=0x08048694 ) password1 = claripy.BVS("password1" ,32 ) password2 = claripy.BVS("password2" ,32 ) init_state.stack_push(init_state.regs.ebp) init_state.regs.ebp = init_state.regs.esp init_state.regs.esp -= 0x8 init_state.stack_push(password1) init_state.stack_push(password2) init_state.regs.esp -= 0x18 sm = p.factory.simulation_manager(init_state) def is_good (state ): return b"Good Job" in state.posix.dumps(1 ) def is_bad (state ): return b"Try again" in state.posix.dumps(1 ) sm.explore(find=is_good,avoid=is_bad) print ("find: " +hex (len (sm.found)))print ("avoid: " +hex (len (sm.avoid)))found_state = sm.found[0 ] answer1 = found_state.solver.eval (password1) answer2 = found_state.solver.eval (password2) print ("{:d} {:d}" .format (answer1,answer2))
05_angr_symbolic_memory
本题目有多个输入:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 int __cdecl main (int argc, const char **argv, const char **envp) { int i; memset (user_input, 0 , 0x21 u); printf ("Enter the password: " ); __isoc99_scanf("%8s %8s %8s %8s" , user_input, &unk_A1BA1C8, &unk_A1BA1D0, &unk_A1BA1D8); for ( i = 0 ; i <= 31 ; ++i ) *(_BYTE *)(i + 0xA1BA1C0 ) = complex_function(*(char *)(i + 0xA1BA1C0 ), i); if ( !strncmp (user_input, "NJPURZPCDYEAXCSJZJMPSOMBFDDLHBVN" , 0x20 u) ) puts ("Good Job." ); else puts ("Try again." ); return 0 ; }
这次输入的数据存放在全局变量中,直接在目标地址上写入符号向量即可(注意顺序)
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 import angrimport claripyp = angr.Project("./05_angr_symbolic_memory" ) init_state = p.factory.blank_state(addr=0x08048601 ) password1 = claripy.BVS("password1" ,64 ) password2 = claripy.BVS("password2" ,64 ) password3 = claripy.BVS("password3" ,64 ) password4 = claripy.BVS("password4" ,64 ) password1_addr = 0x0A1BA1C0 password2_addr = 0x0A1BA1C8 password3_addr = 0x0A1BA1D0 password4_addr = 0x0A1BA1D8 init_state.memory.store(password1_addr,password1) init_state.memory.store(password2_addr,password2) init_state.memory.store(password3_addr,password3) init_state.memory.store(password4_addr,password4) sm = p.factory.simulation_manager(init_state) def is_good (state ): return b"Good Job" in state.posix.dumps(1 ) def is_bad (state ): return b"Try again" in state.posix.dumps(1 ) sm.explore(find=is_good,avoid=is_bad) print ("find: " +hex (len (sm.found)))print ("avoid: " +hex (len (sm.avoid)))found_state = sm.found[0 ] answer1 = found_state.solver.eval (password1,cast_to=bytes ).decode() answer2 = found_state.solver.eval (password2,cast_to=bytes ).decode() answer3 = found_state.solver.eval (password3,cast_to=bytes ).decode() answer4 = found_state.solver.eval (password4,cast_to=bytes ).decode() print ("{} {} {} {}" .format (answer1,answer2,answer3,answer4))
06_angr_symbolic_dynamic_memory
1 2 3 4 5 6 buffer0 = (char *)malloc (9u ); buffer1 = (char *)malloc (9u ); memset (buffer0, 0 , 9u );memset (buffer1, 0 , 9u );printf ("Enter the password: " );__isoc99_scanf((int )"%8s %8s" , (int )buffer0, (int )buffer1);
由于我们直接跳过了输入,导致 malloc
并没有执行,此时堆应该是没有初始化的
因此我们直接在栈上伪造一个堆空间,然后在 buffer0 buffer1
中写入我们伪造的地址:
注意:angr 默认使用大端写,但写地址时需要指定 endness=p.arch.memory_endness
切换为小端写
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 import angrimport claripyp = angr.Project("./06_angr_symbolic_dynamic_memory" ) init_state = p.factory.blank_state(addr=0x08048696 ) password1 = claripy.BVS("password1" ,64 ) password2 = claripy.BVS("password2" ,64 ) password1_addr = 0x0ABCC8A4 password2_addr = 0x0ABCC8AC password1_fakeheap = init_state.regs.esp + 0x100 password2_fakeheap = init_state.regs.esp + 0x200 init_state.memory.store(password1_addr,password1_fakeheap,endness=p.arch.memory_endness) init_state.memory.store(password2_addr,password2_fakeheap,endness=p.arch.memory_endness) init_state.memory.store(password1_fakeheap,password1) init_state.memory.store(password2_fakeheap,password2) sm = p.factory.simulation_manager(init_state) def is_good (state ): return b"Good Job" in state.posix.dumps(1 ) def is_bad (state ): return b"Try again" in state.posix.dumps(1 ) sm.explore(find=is_good,avoid=is_bad) print ("find: " +hex (len (sm.found)))print ("avoid: " +hex (len (sm.avoid)))found_state = sm.found[0 ] answer1 = found_state.solver.eval (password1,cast_to=bytes ).decode() answer2 = found_state.solver.eval (password2,cast_to=bytes ).decode() print ("{} {}" .format (answer1,answer2))
07_angr_symbolic_file
1 2 3 4 5 6 7 __isoc99_scanf("%64s" , buffer); ignore_me(buffer, 0x40 u); memset (buffer, 0 , sizeof (buffer));fp = fopen("OJKSQYDP.txt" , "rb" ); fread(buffer, 1u , 0x40 u, fp); fclose(fp); unlink("OJKSQYDP.txt" );
程序先把输入的数据传输到文件中,然后打开文件从里面取出数据
这次的目标就是符号化一个文件,使用 init_state.fs.insert(file_name,SimFile)
可以把一个指定文件名的文件,设置为符号化文件
我们需要跳过输入,在 open
之前开始执行程序:
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 import angrimport claripyp = angr.Project("./07_angr_symbolic_file" ) init_state = p.factory.blank_state(addr=0x080488D3 ) file_name = "OJKSQYDP.txt" file_size = 0x40 password1 = claripy.BVS("password1" ,file_size) SimFile = angr.storage.SimFile(file_name,content=password1,size=file_size) init_state.fs.insert(file_name,SimFile) sm = p.factory.simulation_manager(init_state) def is_good (state ): return b"Good Job" in state.posix.dumps(1 ) def is_bad (state ): return b"Try again" in state.posix.dumps(1 ) sm.explore(find=is_good,avoid=is_bad) print ("find: " +hex (len (sm.found)))print ("avoid: " +hex (len (sm.avoid)))found_state = sm.found[0 ] answer1 = found_state.solver.eval (password1,cast_to=bytes ).decode() print ("{}" .format (answer1))
08_angr_constraints
本题目使用单字节对比:
1 2 3 4 5 6 7 8 9 10 11 12 13 _BOOL4 __cdecl check_equals_AUPDNNPROEZRJWKB (int a1, unsigned int a2) { int v3; unsigned int i; v3 = 0 ; for ( i = 0 ; i < a2; ++i ) { if ( *(_BYTE *)(i + a1) == *(_BYTE *)(i + 0x804A040 ) ) ++v3; } return v3 == a2; }
angr 有一个名为“路径爆炸”的问题,这种单字节对比的函数会导致路径特别多,大大降低 angr 的运行速度,于是我们需要一种方法来减缓“路径爆炸”的影响
常见的做法就是把“对比函数”提取出来,手动为该函数添加 check_constraint
(通过条件),然后再用约束器求解:
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 import angrimport claripyp = angr.Project("./08_angr_constraints" ) init_state = p.factory.blank_state(addr=0x08048622 ) password = claripy.BVS("password" ,0x10 *8 ) password_addr = 0x0804A050 init_state.memory.store(password_addr,password) check_addr = 0x08048565 check_key = "AUPDNNPROEZRJWKB" sm = p.factory.simulation_manager(init_state) sm.explore(find=check_addr) if sm.found: check_state = sm.found[0 ] check_param1 = password_addr check_param2 = 0x10 check_bvs = check_state.memory.load(check_param1,check_param2) check_constraint = check_key == check_bvs check_state.add_constraints(check_constraint) answer = check_state.solver.eval (password,cast_to=bytes ) print ("{}" .format (answer.decode()))
09_angr_hooks
1 2 3 4 5 6 7 8 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_XYMKBKUHNIQYNQXE((int )buffer, 0x10 u); for ( j = 0 ; j <= 15 ; ++j ) *(_BYTE *)(j + 0x804A044 ) = complex_function(*(char *)(j + 0x804A044 ), j + 9 ); __isoc99_scanf("%16s" , buffer);
本题目的执行路径比较混乱,如果用之前的方法来处理“路径爆炸”就会导致代码冗余(查找到目标“比较函数”后,需要判断该“比较函数”是否通过,然后再查找 Good Job
)
angr 还提供了另一种方法来应对“路径爆炸”:
直接 hook 掉“比较函数”,利用自己实现的函数来替代它
然后根据比较结果来操控 eax
寄存器
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 import angrimport claripyp = angr.Project("./09_angr_hooks" ) init_state = p.factory.entry_state() password_addr = 0x0804A054 check_addr = 0x080486B3 check_skip = 4 + 1 check_key = "XYMKBKUHNIQYNQXE" @p.hook(check_addr,length=check_skip ) def check_hook (state ): check_param1 = password_addr check_param2 = 0x10 input_bvs = state.memory.load(check_param1,check_param2) state.regs.eax = claripy.If( check_key == input_bvs, claripy.BVV(1 ,32 ), claripy.BVV(0 ,32 ) ) sm = p.factory.simulation_manager(init_state) def is_good (state ): return b"Good Job" in state.posix.dumps(1 ) def is_bad (state ): return b"Try again" in state.posix.dumps(1 ) sm.explore(find=is_good,avoid=is_bad) print ("find: " +hex (len (sm.found)))print ("avoid: " +hex (len (sm.avoid)))found_state = sm.found[0 ] print (found_state.posix.dumps(0 ))
10_angr_simprocedures
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) { int i; char s[17 ]; unsigned int v6; v6 = __readgsdword(0x14 u); memcpy (&password, "ORSDDWXHZURJRBDH" , 0x10 u); memset (s, 0 , sizeof (s)); 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_ORSDDWXHZURJRBDH((int )s, 0x10 u) ) puts ("Good Job." ); else puts ("Try again." ); return 0 ; }
这个题目看似可以用上一个方法来做,但稍微用 IDA 分析一下就会发现问题:
程序为每个路径分支都提供了一个“比较函数”,我们根本不知道该 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 import angrimport claripyp = angr.Project("./10_angr_simprocedures" ) init_state = p.factory.entry_state() password_addr = 0x0804C048 class mySimPro (angr.SimProcedure ): def run (self,input_addr,input_length ): check_key = "ORSDDWXHZURJRBDH" input_bvs = self.state.memory.load(input_addr,input_length) return claripy.If( check_key == input_bvs, claripy.BVV(1 ,32 ), claripy.BVV(0 ,32 ) ) check_name = "check_equals_ORSDDWXHZURJRBDH" p.hook_symbol(check_name,mySimPro()) sm = p.factory.simulation_manager(init_state) def is_good (state ): return b"Good Job" in state.posix.dumps(1 ) def is_bad (state ): return b"Try again" in state.posix.dumps(1 ) sm.explore(find=is_good,avoid=is_bad) print ("find: " +hex (len (sm.found)))print ("avoid: " +hex (len (sm.avoid)))found_state = sm.found[0 ] print (found_state.posix.dumps(0 ))
11_angr_sim_scanf
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) { int i; char s[20 ]; unsigned int v7; v7 = __readgsdword(0x14 u); memset (s, 0 , sizeof (s)); qmemcpy(s, "SUQMKQFX" , 8 ); for ( i = 0 ; i <= 7 ; ++i ) s[i] = complex_function(s[i], i); printf ("Enter the password: " ); __isoc99_scanf("%u %u" , buffer0, buffer1); if ( !strncmp (buffer0, s, 4u ) && !strncmp (buffer1, &s[4 ], 4u ) ) puts ("Good Job." ); else puts ("Try again." ); return 0 ; }
题目原本考点是:将 scanf 替换为我们自己的版本(老版本 angr 不支持使用 scanf 请求多个参数)
现在新版本的 angr 已经支持多个参数的 scanf 了:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 import angrp = angr.Project("./11_angr_sim_scanf" ) init_state = p.factory.entry_state() sm = p.factory.simulation_manager(init_state) def is_good (state ): return b"Good Job" in state.posix.dumps(1 ) def is_bad (state ): return b"Try again" in state.posix.dumps(1 ) sm.explore(find=is_good,avoid=is_bad) print ("find: " +hex (len (sm.found)))print ("avoid: " +hex (len (sm.avoid)))found_state = sm.found[0 ] print (found_state.posix.dumps(0 ))
12_angr_veritesting
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 __isoc99_scanf( (int )"%32s" , (int )v19 + 3 , v5, v6, v7, v8, v9, (int )v10, v11, v12, v13, v14, v16, v18, v19[0 ], v19[1 ], v19[2 ], v19[3 ], v19[4 ], v19[5 ]);
一样的是针对 scanf 的问题
由于变量过长,需要在构造仿真管理器时添加 veritesting=True
选项
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 import angrp = angr.Project("./12_angr_veritesting" ) init_state = p.factory.entry_state() sm = p.factory.simulation_manager(init_state,veritesting=True ) def is_good (state ): return b"Good Job" in state.posix.dumps(1 ) def is_bad (state ): return b"Try again" in state.posix.dumps(1 ) sm.explore(find=is_good,avoid=is_bad) print ("find: " +hex (len (sm.found)))print ("avoid: " +hex (len (sm.avoid)))found_state = sm.found[0 ] print (found_state.posix.dumps(0 ))
13_angr_static_binary
这是一个静态的程序:
1 2 ➜ angr file 13 _angr_static_binary 13 _angr_static_binary: ELF 32 -bit LSB executable, Intel 80386 , version 1 (GNU/Linux), statically linked, for GNU/Linux 2.6 .32 , BuildID[sha1]=89 d11f111deddc580fac3d22a1f6c352d1883cd5, not stripped
angr 在处理静态程序时,会进入到 libc 函数的内部进行分析,有些 libc 函数内部的执行分支很多,路径很长,会浪费大量的时间
我们需要做的工作就是用 hook 函数来替代原本的 libc 函数:
当执行二进制文件时,main 函数不是调用的第一段代码,程序会首先调用在 _start
函数中的 __libc_start_main
以启动程序,此函数中发生的初始化
使用 angr 可能需要很长时间,所以你应该用 SimProcedure 替换它
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 import angrp = angr.Project("./13_angr_static_binary" ) init_state = p.factory.entry_state() printf_addr = 0x0804ED40 puts_addr = 0x0804F350 strcmp_addr = 0x08048280 scanf_addr = 0x0804ED80 exit_addr = 0x0804E3D0 __libc_start_main = 0x08048D10 p.hook(printf_addr, angr.SIM_PROCEDURES['libc' ]['printf' ]()) p.hook(puts_addr, angr.SIM_PROCEDURES['libc' ]['puts' ]()) p.hook(strcmp_addr, angr.SIM_PROCEDURES['libc' ]['strcmp' ]()) p.hook(scanf_addr, angr.SIM_PROCEDURES['libc' ]['scanf' ]()) p.hook(exit_addr, angr.SIM_PROCEDURES['libc' ]['exit' ]()) p.hook(__libc_start_main, angr.SIM_PROCEDURES['glibc' ]['__libc_start_main' ]()) sm = p.factory.simulation_manager(init_state) def is_good (state ): return b"Good Job" in state.posix.dumps(1 ) def is_bad (state ): return b"Try again" in state.posix.dumps(1 ) sm.explore(find=is_good,avoid=is_bad) print ("find: " +hex (len (sm.found)))print ("avoid: " +hex (len (sm.avoid)))found_state = sm.found[0 ] print (found_state.posix.dumps(0 ))
14_angr_shared_library
1 2 3 4 5 6 7 8 memset (s, 0 , sizeof (s));printf ("Enter the password: " );__isoc99_scanf("%8s" , s); if ( validate((int )s, 8 ) ) puts ("Good Job." ); else puts ("Try again." ); return 0 ;
1 2 3 4 int __cdecl validate (int a1, int a2) { return validate(a1, a2); }
这个 validate
是动态链接库中的函数
执行二进制文件前,需要先将对应的动态链接库添加到 /lib
目录中
1 sudo cp -r lib14_angr_shared_library.so /lib
本题目有一个特点,就是是否输出 Good Job
完全由动态链接库决定,于是我们只需要测试动态链接库
angr 拥有分析动态链接库的能力,但需要在创建项目时指定程序的基地址(随便写一个都行),然后利用 p.factory.call_state
去调用 validate
(不需要检查返回,只要程序执行到“通过”时该执行的地址就可以就说明输出 Good Job
了)
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 import angrimport claripybase_addr = 0x8000000 p = angr.Project("./lib14_angr_shared_library.so" ,load_options={ 'main_opts' : { 'custom_base_addr' : base_addr } }) password_addr = claripy.BVV(0x3000000 ,32 ) password = claripy.BVS("password" ,8 *8 ) validate_addr = base_addr+0x6D7 init_state = p.factory.call_state(validate_addr, password_addr, claripy.BVV(8 , 32 )) init_state.memory.store(password_addr, password) sm = p.factory.simulation_manager(init_state) sm.explore(find=0x783 +base_addr) print ("find: " +hex (len (sm.found)))print ("avoid: " +hex (len (sm.avoid)))found_state = sm.found[0 ] answer = found_state.solver.eval (password,cast_to=bytes ) print ("{}" .format (answer.decode()))
15_angr_arbitrary_read
这个题目有一点不同:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 int __cdecl main (int argc, const char **argv, const char **envp) { char v4[16 ]; char *s; s = try_again; printf ("Enter the password: " ); __isoc99_scanf("%u %20s" , &key, v4); if ( key == 0x27DFB7C ) puts (s); else puts (try_again); return 0 ; }
输入有4字节的溢出,刚好可以覆盖 s
(任意地址读)
除了绕过对应的密码检查,还需要调用 memory.load
方法将 puts
的第一个参数提取出来,与 Good Job
字符串所在的地址进行对比
注意:本题目还需要把 scanf
给 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 48 49 50 import angrimport claripyp = angr.Project("./15_angr_arbitrary_read" ) init_state = p.factory.entry_state() sm = p.factory.simulation_manager(init_state) class mySimPro (angr.SimProcedure ): format_addr = 0x484F4A69 def run (self, format_addr, param0, param1 ): password1 = claripy.BVS('password1' , 32 ) password2 = claripy.BVS('password2' , 8 *20 ) for char in password2.chop(bits=8 ): self.state.add_constraints(char >= 'A' , char <= 'Z' ) self.state.memory.store(param0, password1, endness=p.arch.memory_endness) self.state.memory.store(param1, password2) self.state.globals ['password' ] = (password1, password2) scanf_symbol = '__isoc99_scanf' p.hook_symbol(scanf_symbol, mySimPro()) def check_puts (state ): check_param = state.memory.load(state.regs.esp+4 ,4 ,endness=p.arch.memory_endness) if state.se.symbolic(check_param): goodjob_addr = 0x484F4A47 check_constraint = check_param == goodjob_addr state.add_constraints(check_constraint) if state.satisfiable(): return True else : return False else : return False def is_good (state ): puts_address = 0x8048370 if state.addr == puts_address: return check_puts(state) else : return False sm.explore(find=is_good) print ("find: " +hex (len (sm.found)))print ("avoid: " +hex (len (sm.avoid)))found_state = sm.found[0 ] (password1, password2) = found_state.globals ['password' ] answer1 = (found_state.solver.eval (password1)) answer2 = (found_state.solver.eval (password2,cast_to=bytes )) print ("{} {}" .format (answer1, answer2))
16_angr_arbitrary_write
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 int __cdecl main (int argc, const char **argv, const char **envp) { char s[16 ]; char *dest; dest = unimportant_buffer; memset (s, 0 , sizeof (s)); strncpy (password_buffer, "PASSWORD" , 0xC u); printf ("Enter the password: " ); __isoc99_scanf("%u %20s" , &key, s); if ( key == 0xB11403 ) strncpy (dest, s, 0x10 u); else strncpy (unimportant_buffer, s, 0x10 u); if ( !strncmp (password_buffer, "NDYNWEUJ" , 8u ) ) puts ("Good Job." ); else puts ("Try again." ); return 0 ; }
和上一个题一样,只不过漏洞换成了任意写
在上一个题目的基础上进行修改,再添加一些检查就好了
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 import angrimport claripyp = angr.Project("./16_angr_arbitrary_write" ) init_state = p.factory.entry_state() sm = p.factory.simulation_manager(init_state) class mySimPro (angr.SimProcedure ): format_addr = 0x08048721 def run (self, format_addr, param0, param1 ): password1 = claripy.BVS('password1' , 32 ) password2 = claripy.BVS('password2' , 8 *20 ) for char in password2.chop(bits=8 ): self.state.add_constraints(char >= 'A' , char <= 'Z' ) self.state.memory.store(param0, password1, endness=p.arch.memory_endness) self.state.memory.store(param1, password2) self.state.globals ['password' ] = (password1, password2) scanf_symbol = '__isoc99_scanf' p.hook_symbol(scanf_symbol, mySimPro()) def check_strncpy (state ): check_param1 = state.memory.load(state.regs.esp+4 ,4 ,endness=p.arch.memory_endness) check_param2 = state.memory.load(state.regs.esp+8 ,4 ,endness=p.arch.memory_endness) check_param3 = state.memory.load(state.regs.esp+12 ,4 ,endness=p.arch.memory_endness) check_bvs = state.memory.load(check_param2, check_param3) if state.solver.symbolic(check_bvs) and state.solver.symbolic(check_param1): target_addr = 0x57584344 check_key = "NDYNWEUJ" check_constraint1 = check_param1 == target_addr check_constraint2 = check_bvs[-1 :-64 ] == check_key if state.satisfiable(extra_constraints=(check_constraint1,check_constraint2)): state.add_constraints(check_constraint1,check_constraint2) return True else : return False else : return False def is_good (state ): strncpy_address = 0x08048410 if state.addr == strncpy_address: return check_strncpy(state) else : return False sm.explore(find=is_good) print ("find: " +hex (len (sm.found)))print ("avoid: " +hex (len (sm.avoid)))found_state = sm.found[0 ] (password1, password2) = found_state.globals ['password' ] answer1 = (found_state.solver.eval (password1)) answer2 = (found_state.solver.eval (password2,cast_to=bytes )) print ("{} {}" .format (answer1, answer2))
17_angr_arbitrary_jump
1 2 3 4 5 6 7 int __cdecl main (int argc, const char **argv, const char **envp) { printf ("Enter the password: " ); read_input(); puts ("Try again." ); return 0 ; }
1 2 3 4 5 6 int read_input () { char v1[32 ]; return __isoc99_scanf("%s" , v1); }
这个题开始玩 pwn 了,要我们覆盖返回地址
因为本程序随时可能报错,所以我们要在创建仿真器时指定 save_unconstrained=True
(angr 不抛出不受约束的状态)
我们需要的结果是无约束状态(因为 Good Job
根本不在程序的执行路径里),如果出现了约束状态下的解则求解失败,有待检查的状态才继续循环遍历所有的状态,最终的结果是找到了一个未约束状态
最后给这个状态加一个约束,使其等于 Good Job
的地址
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 claripyp = angr.Project("./17_angr_arbitrary_jump" ) init_state = p.factory.entry_state() sm = p.factory.simulation_manager(init_state) class mySimPro (angr.SimProcedure ): format_addr = 0x42585350 def run (self, format_addr, param0 ): password = claripy.BVS('password' , 8 *64 ) for char in password.chop(bits=8 ): self.state.add_constraints(char >= 'A' , char <= 'Z' ) self.state.memory.store(param0, password, endness=p.arch.memory_endness) self.state.globals ['password' ] = (password) scanf_symbol = '__isoc99_scanf' p.hook_symbol(scanf_symbol, mySimPro()) sm = p.factory.simgr( init_state, save_unconstrained=True , stashes={ 'active' : [init_state], 'unconstrained' : [], 'found' : [], 'not_needed' : [] } ) while (1 ): for unconstrained_state in sm.unconstrained: def should_move (s ): return s is unconstrained_state sm.move('unconstrained' , 'found' , filter_func=should_move) sm.step() if (sm.found): print ("get it" ) break print ("find: " +hex (len (sm.found)))found_state = sm.found[0 ] found_state.add_constraints(found_state.regs.eip == 0x42585249 ) password = found_state.globals ['password' ] answer = (found_state.solver.eval (password,cast_to=bytes )) print (answer[::-1 ])