Angr 学习笔记
安装
安装的话angr
支持了Python 3.x
可以不用去折腾2.x
的版本了,对于Windows
似乎可以直接:
就可以完成大部分需要库的安装,对于Linux
可能会有一些复杂,这里给出一个链接给大家参考一下:
https://www.cnblogs.com/lxy8584099/p/13668749.html
本人是在Windows
下安装的,也没遇到网上出现的缺失文件的现象。出现问题了我也不清楚呀 :(
部分使用教程
导入 angr 包
似乎这一部没有出差错就是说明安装成功了。
新建一个 angr 工程
1 2 p = angr.Project("<需要拿来解题的文件>" )
上述过程相当于完成了angr
的加载。
初始化 angr
Unicorn
是 angr
的依赖库,在Unicorn
下可以执行任意一段二进制的代码,我们对此需要告诉其从哪里开始,因此我们需要对angr
进行初始化
1 2 init_state = p.factory.entry_state()
执行 angr
初始化后我们需要执行angr
1 2 sm = p.factory.simulation_manager(init_state)
上述过程中我们设置了一个模拟器,接下来我们需要告诉程序应该到哪里去,我们需要设置目标地址:
1 sm.explore(find = "<目标地址>" )
此时如果我们找到了通向对应的目标地址的时候,将其打印出来其符号向量:
1 2 3 4 5 6 7 if simulation.found: solution_state = simulation.found[0 ] print (solution_state.posix.dumps(sys.stdin.fileno()))else : raise Exception('Could not find solution' )
00_angr_find
第一题拖进IDA
分析可以看到是十分简单的,其关键部分便是中间的那个complex_function
,我们尝试用angr
来进行解题 ( 虽然爆破也可以直接执行 )。
我们找到对应目标 ( 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 import sysimport angrdef main (argv ): path_to_binary = '../program/00_angr_find' project = angr.Project(path_to_binary) initial_state = project.factory.entry_state() simulation = project.factory.simgr(initial_state) print_good_addr = 0x0804867D simulation.explore(find=print_good_addr) if simulation.found: solution_state = simulation.found[0 ] print (solution_state.posix.dumps(sys.stdin.fileno())) else : raise Exception('Could not find solution' ) if __name__ == '__main__' : main(sys.argv)
值得注意的是在输出语句中print(solution_state.posix.dumps(sys.stdin.fileno()))
,其中的dumps
在Python 3.x
下运行成功,但是在文档中看到有些写的是dump
,不清楚其是否为Python 2.x
运行的,上述脚本运行不成功时可以试试看各改一下输出的dump
通过上述脚本我们可以直接得到目标地址的值在解密后得到的目标答案。
本题中我们了解到了如何编写angr
脚本,了解了如何创建项目,创建模拟器以及如何约束模拟器到对应的地址处和输出约束求解的结果。
1 2 3 4 5 angr.Project(file_path) project.factory.simgr(initial_state) simulation.explore(find=addr) solution_state = simulation.found[0 ]print (solution_state.posix.dumps(sys.stdin.fileno()))
01_angr_avoid
将程序拖进IDA
,反编译主函数程序显示函数过大,无法反编译,在这个题目中我们主要学习angr
的avoid
的使用,对此我们利用avoid
来去除我们不想要到达的地址。
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 sysimport angrdef main (argv ): path_to_binary = '../program/00_angr_find' project = angr.Project(path_to_binary) initial_state = project.factory.entry_state() simulation = project.factory.simgr(initial_state) print_good_addr = 0x080485E0 try_again_addr = 0x080485F2 simulation.explore(find=print_good_addr, avoid=try_again_addr) if simulation.found: solution_state = simulation.found[0 ] print (solution_state.posix.dumps(sys.stdin.fileno())) else : raise Exception('Could not find solution' ) if __name__ == '__main__' : main(sys.argv)
需要注意的是对应的good
的地址不是这个字符串的地址,而是打印字符串时的汇编代码对应的地址。
通过上述例子,我们学会了如何加入规避的地址来进行进一步的约束求解
1 2 3 print_good_addr = XXXX try_again_addr = XXXX simulation.explore(find=print_good_addr, avoid=try_again_addr)
01_angr_condition
将程序拖入IDA
可以看到主函数还是和上一题一样,函数过大而无法反编译,同时我们观察一下主函数里的数据可以看到有多个Good Job
和多个Try again
,此时的一个单一的约束并不能很好的起作用,此时用到了angr
中的condition
来对多个结果进行约束求解。
1 simulation.explore(find=print_good_addr, avoid=try_again_addr)
之前我们的约束条件都是上面的explore
中加入find
和avoid
的函数地址,但是实际上find
和avoid
对应的参数可以是一个函数,我们将在这个函数里面进行一个条件的约束
1 2 3 4 5 6 7 def good_job (state ): stdout_output = state.posix.dumps(sys.stdout.fileno()) return 'Good Job' in str (stdout_output)def try_again (state ): stdout_output = state.posix.dumps(sys.stdout.fileno()) return 'Try again' in str (stdout_output)
stdout_output = state.posix.dumps(sys.stdout.fileno())
用来获取标准输出的字符
我们利用获取的标准输出字符来进行创建函数,而进一步对我们的条件进行约束求解,那么我们传入的便不再是一个单一的地址,可以对输出的字符集合进行集体约束求解,得到我们想要的结果。
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 sysimport angrdef good_job (state ): stdout_output = state.posix.dumps(sys.stdout.fileno()) return 'Good Job' in str (stdout_output)def try_again (state ): stdout_output = state.posix.dumps(sys.stdout.fileno()) return 'Try again' in str (stdout_output)def main (argv ): path_to_binary = './02_angr_find_condition' project = angr.Project(path_to_binary) initial_state = project.factory.entry_state() simulation = project.factory.simgr(initial_state) simulation.explore(find=good_job, avoid=try_again) if simulation.found: solution_state = simulation.found[0 ] print (solution_state.posix.dumps(sys.stdin.fileno())) else : raise Exception('Could not find solution' ) if __name__ == '__main__' : main(sys.argv)
上述例子,我们学会了对输出条件来进行集体约束求解,明白了simulation.explore
对应的参数值可以是一个函数,在函数中我们调用标准的输出库,来对输出字符进行约束求解。
1 2 3 4 5 6 def good_job (state ): return b"XXX" in solution_state.posix.dumps(0 ) def try_again (state ): return b"XXX" in solution_state.posix.dumps(0 ) simulation.explore(find=good_job, avoid=try_again)
03_angr_symbolic_registers
打开题目一看,程序要求我们输入的是 3 个数据,通过三个复杂的函数进行运算,当三个数运算处理后的返回值均为 1 时,判断输入正确,反之判断错误。之前有一个学长去问过angr_CTF
库的原作者问多个输入angr
可以直接处理吗,原作者回答可以直接处理,但是为了学习目的我们不直接按照之前几个题目的方式来约束地址或者输出来进行运算。
我们还是给出之前的代码写法:
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 sysimport angrdef good_job (state ): stdout_output = state.posix.dumps(sys.stdout.fileno()) return 'Good Job' in str (stdout_output)def try_again (state ): stdout_output = state.posix.dumps(sys.stdout.fileno()) return 'Try again' in str (stdout_output)def main (argv ): path_to_binary = './03_angr_symbolic_registers' project = angr.Project(path_to_binary) initial_state = project.factory.entry_state() simulation = project.factory.simgr(initial_state) simulation.explore(find=good_job, avoid=try_again) if simulation.found: solution_state = simulation.found[0 ] print (solution_state.posix.dumps(sys.stdin.fileno())) else : raise Exception('Could not find solution' ) if __name__ == '__main__' : main(sys.argv)
在题目名称中我们可以看到符号化寄存器的英文,那么什么是符号化寄存器?
设置状态的寄存器值,符号化寄存器值含义就是将寄存器内存储的值设为自变量,而后类别列方程加入约束条件内
似乎有点类似于z3
设置多个未知数然后带入方程组进行约束求解。那么我们要怎么做呢?我们先观察一下程序的输入:
可以看出来我们的 3 个输入数据先后从ecx
中转移到了eax
、ebx
、edx
里,同时在后续复杂函数的处理部分中没有改变 3 个寄存器来进行其他处理,对此我们可以直接设置 3 个未知数来代替这三个寄存器,来参与下面函数的运算和执行。
要将寄存器符号化我们需要引入一个新的函数库:import claripy
,由于我们不再是从主函数( main )直接执行我们的符号化向量,我们需要重新设置一个起始地址:
1 2 3 start_addr = XXX initial_state = project.factory.blank_state(addr=start_addr)
其中的project.factory.blank_state
与之前的project.factory.entry_state
不同了,blank_state
相当于表示一个空的地址,需要我们把开始地址进行导入。
接下来便是将寄存器进行符号化,我们便利用到了之前引入的claripy
库,下面给出示例:
1 2 3 4 5 6 7 bit_length = XX register = claripy.BVS('<符号向量取名>' , bit_length) initial_state.regs.eax = register simulation = project.factory.simgr(initial_state)
那么我们想要输出对应找到后的寄存器值又该怎么处理呢?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 if simulation.found: solution_state = simulation.found[0 ] solution = solution_state.se.eval (register) solution = '%x' % (solution) print (solution)else : raise Exception('Could not find the solution' ) -------------------------------------------------------------if simulation.found: solution_state = simulation.found[0 ] solution1 = solution_state.se.eval (register1) solution2 = solution_state.se.eval (register2) solution = '%x %x' % (solution1,solution2) print (solution)else : raise Exception('Could not find the solution' )
此时新的脚本可以如下编写:
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 sysimport angrimport claripydef main (): binary_path = '../program/03_angr_symbolic_registers' project = angr.Project(binary_path) start_addr = 0x0804890E initial_state = project.factory.blank_state(addr=start_addr) bit_length = 32 psd0 = claripy.BVS('psd0' , bit_length) psd1 = claripy.BVS('psd1' , bit_length) psd2 = claripy.BVS('psd2' , bit_length) initial_state.regs.eax = psd0 initial_state.regs.ebx = psd1 initial_state.regs.edx = psd2 simulation = project.factory.simgr(initial_state) def good_job (state ): stdout_content = state.posix.dumps(sys.stdout.fileno()) return b'Good Job.' in stdout_content def fail (state ): stdout_content = state.posix.dumps(sys.stdout.fileno()) return b'Try again.' in stdout_content simulation.explore(find=good_job, avoid=fail) if simulation.found: solution_state = simulation.found[0 ] solution0 = solution_state.se.eval (psd0) solution1 = solution_state.se.eval (psd1) solution2 = solution_state.se.eval (psd2) solution = '%x %x %x' % (solution0, solution1, solution2) print (solution) else : raise Exception('Could not find the solution' ) if __name__ == '__main__' : main()
上述过程中我们学习了符号化寄存器,修改对应的输出格式,同时如何更改执行的起始地址:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 start_addr = XXX initial_state = project.factory.blank_state(addr=start_addr) bit_length = XX register = claripy.BVS('<符号向量取名>' , bit_length) initial_state.regs.eax = register if simulation.found: solution_state = simulation.found[0 ] solution = solution_state.se.eval (register) solution = '%x' % (solution) print (solution)else : raise Exception('Could not find the solution' )
04_angr_symbolic_stack
程序名字是符号化栈,对于这个题目我尝试了一下使用之前的02_angr_find_condition
来进行约束输出求解,发现angr
同样可以直接处理栈,岂不是直接一个输出条件的约束就可以吃遍逆向了?因为有些程序的分支结构十分大,angr
会遍历所有可能的分支结构,简单的约束限制求解消耗的时间会十分巨大,所以我们需要进一步的学习来进行约束求解。
在程序里我们的输入是处于栈上的,在scanf
过后执行了一个add esp, 10h
的操作来清理scanf
所产生的栈,因此我们真正的执行部分应该是在add esp, 10h
之后,我们观察一下输入,可以看到v2
最先被输入,然后便是v1
,因此v2
所在的栈空间是位于v1
下方的 ( 栈向上增长 )。我们每次初始化栈时并不会有数据,而v1
处于栈空间的中间,因此我们需要一个padding
来帮我们覆写到对应v1
所在的空间内。示意图如下:
v1
距离栈顶还有0x8
的空间,我们初始化栈时的padding
将上面的0x8
字节空间进行填充,之后的栈空间地址便是我们输入的v1
地址。那么怎么进行初始化栈呢?栈开始时我们是将EBP
和ESP
两个栈指针的地址相等,然后入栈时栈顶指针ESP
增加,我们在这个过程中是对栈进行了模拟操作,那么在angr
中也有相应的模拟方式来实现模拟栈的初始化。
1 2 3 4 5 6 7 8 9 10 11 initial_state.regs.ebp = initial_state.regs.esp password0 = claripy.BVS('password0' , 32 ) password1 = claripy.BVS('password1' , 32 ) padding_length_in_bytes = 0x8 initial_state.regs.esp -= padding_length_in_bytes initial_state.stack_push(password0) initial_state.stack_push(password1)
那么我们便可以把这一连串代码加入到之前的第二题的代码之中,编写出如下脚本:
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 from mimetypes import initimport sysimport angrimport claripydef main (): binary_path = './04_angr_symbolic_stack' project = angr.Project(binary_path) start_addr = 0x08048697 initial_state = project.factory.blank_state(addr=start_addr) initial_state.regs.ebp = initial_state.regs.esp password0 = claripy.BVS('password0' , 32 ) password1 = claripy.BVS('password1' , 32 ) padding_length_in_bytes = 0x8 initial_state.regs.esp -= padding_length_in_bytes initial_state.stack_push(password0) initial_state.stack_push(password1) simulation = project.factory.simgr(initial_state) def is_successful (state ): stdout_output = state.posix.dumps(sys.stdout.fileno()) return b'Good Job' in stdout_output def should_abort (state ): stdout_output = state.posix.dumps(sys.stdout.fileno()) return b'Try again' in stdout_output simulation.explore(find=is_successful, avoid=should_abort) if simulation.found: solution_state = simulation.found[0 ] solution0 = solution_state.se.eval (password0) solution1 = solution_state.se.eval (password1) solution = '%u %u' % (solution0, solution1) print (solution) else : raise Exception('could not find the solution' )if __name__ == '__main__' : main()
在这个题目中我们学会了如何符号化栈空间,我们通过填充部分padding
使我们的输入数据处于栈顶 ( 绕过了输入 ),同时与符号化寄存器相结合,让angr
进行模拟,来代替输入的输入。
1 2 3 4 5 6 7 8 9 10 initial_state.regs.ebp = initial_state.regs.esp password0 = claripy.BVS('password0' , 32 ) password1 = claripy.BVS('password1' , 32 ) padding_length_in_bytes = XXX initial_state.regs.esp -= padding_length_in_bytes initial_state.stack_push(password0) initial_state.stack_push(password1)
05_angr_symbolic_memory
看到题目便是我们要学习的符号化内存空间,观察一下输入的数据所在的位置,可以看到其处于在bss
段上,属于一个内存区域内,我们所需要做的便是符号化内存空间。
与符号化栈空间有一点类似,我们需要设置 4 个符号向量来进行模拟输入,然后想办法将其放在内存空间里,angr
提供有对应把符号项量放在内存中的函数,我们可以直接通过调用来对内存空间进行符号化。但在这之前我们需要找到bss
段每个输入数据所在的地址值,将对应的地址传入到栈空间里。
1 2 initial_state.memory.store(password_addr, password)
其余部分和前面一个题的符号化栈空间是十分相像的,需要我们对输入进行符号化处理,然后将其地址和参数名传入到内存段即可。
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 import angrimport sysimport claripyfrom Crypto.Util.number import long_to_bytesdef main (): binary_path = '../program/05_angr_symbolic_memory' project = angr.Project(binary_path) start_addr = 0x08048601 initial_state = project.factory.blank_state(addr=start_addr) password0 = claripy.BVS('password0' , 64 ) password1 = claripy.BVS('password1' , 64 ) password2 = claripy.BVS('password2' , 64 ) password3 = claripy.BVS('password3' , 64 ) password0_addr = 0x09FD92A0 password1_addr = 0x09FD92A8 password2_addr = 0x09FD92B0 password3_addr = 0x09FD92B8 initial_state.memory.store(password0_addr, password0) initial_state.memory.store(password1_addr, password1) initial_state.memory.store(password2_addr, password2) initial_state.memory.store(password3_addr, password3) simulation = project.factory.simgr(initial_state) def is_successful (state ): stdout_output = state.posix.dumps(sys.stdout.fileno()) return b'Good Job.' in stdout_output def should_abort (state ): stdout_output = state.posix.dumps(sys.stdout.fileno()) return b'Try again.' in stdout_output simulation.explore(find=is_successful, avoid=should_abort) if simulation.found: solution_state = simulation.found[0 ] solution0 = solution_state.se.eval (password0) solution1 = solution_state.se.eval (password1) solution2 = solution_state.se.eval (password2) solution3 = solution_state.se.eval (password3) solution = long_to_bytes(solution0)+b' ' +long_to_bytes(solution1)+b' ' +long_to_bytes(solution2)+b' ' +long_to_bytes(solution3) print (solution.decode()) else : raise Exception('Could not find solution' )if __name__ == '__main__' : main()
不过需要注意的是符号化向量对应的大小应该是对应的bit
数据大小,而不是看其字节长度。在这个过程中我们将内存进行了符号化处理,然后将我们的符号化向量传入到bss
段中去。
1 2 3 password = claripy.BVS('password' , <bit数据大小>) password_addr = XXX initial_state.memory.store(password_addr, password)
06_angr_symbolic_dynamic_memory
在这个题目中与上一个题目不同的是题目动态分配了两个指针变量,将我们的输入保存在堆空间上,使得我们需要将堆上的空间进行符号化处理,也就是malloc
分配的空间是动态的但是其返回的变量buufer
位于bss
段,是静态的,那么我们可以伪造指针,使其指向的是一片可写内存,进而符号化处理。
我们可以看到空间内的定义数据都是处于bss
段上的,我们便对其想办法来符号化。
相当于我们模拟malloc
分配地址空间,在bss
段里找到一块空的空间来保存我们的输入数据。与此同时我们观察程序入口点可以看到程序在输入后有add esp, 16
对scanf
的栈空间进行整理,所以我们程序开始的地址为下面一行的0x08048699
接下来我们开始模拟malloc
分配地址,并将其保存的输入符号化:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 password0 = claripy.BVS('password0' , 64 ) password1 = claripy.BVS('password1' , 64 ) fake0_addr = 0x09FD9160 fake1_addr = 0x09FD9180 buffer0_addr = 0x09FD92AC buffer1_addr = 0x09FD92B4 initial_state.memory.store(buffer0_addr, fake0_addr, endness=project.arch.memory_endness) initial_state.memory.store(buffer1_addr, fake1_addr, endness=project.arch.memory_endness) initial_state.memory.store(fake0_addr, password0) initial_state.memory.store(fake1_addr, password1)
我们整理一下脚本将符号化动态的内存空间加入到我们的第二题的代码组合便是我们的脚本:
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 from operator import is_import sysimport angrimport claripyfrom Crypto.Util.number import long_to_bytesdef main (): binary_path = '../program/06_angr_symbolic_dynamic_memory' project = angr.Project(binary_path) start_addr = 0x08048699 initial_state = project.factory.blank_state(addr=start_addr) password0 = claripy.BVS('password0' , 64 ) password1 = claripy.BVS('password1' , 64 ) fake0_addr = 0x09FD9160 fake1_addr = 0x09FD9180 buffer0_addr = 0x09FD92AC buffer1_addr = 0x09FD92B4 initial_state.memory.store(buffer0_addr, fake0_addr, endness=project.arch.memory_endness) initial_state.memory.store(buffer1_addr, fake1_addr, endness=project.arch.memory_endness) initial_state.memory.store(fake0_addr, password0) initial_state.memory.store(fake1_addr, password1) simulation = project.factory.simgr(initial_state) def is_successful (state ): stdout_output = state.posix.dumps(sys.stdout.fileno()) return b'Good Job.' in stdout_output def should_abort (state ): stdout_output = state.posix.dumps(sys.stdout.fileno()) return b'Try again.' in stdout_output simulation.explore(find=is_successful, avoid=should_abort) if simulation.found: solution_state = simulation.found[0 ] solution0 = solution_state.se.eval (password0) solution1 = solution_state.se.eval (password1) solution = long_to_bytes(solution0) + b' ' + long_to_bytes(solution1) print (solution) print (solution.decode()) else : raise Exception('Could not find solution' )if __name__ == '__main__' : main()
通过这个题目,我们了解到如何将动态的内存分配的地址进行转换为符号化,我们需要在bss
段上找到对应大小的空间来保存我们的输入,然后将符号化后的输入导入到伪装的bss
段地址处,之后便可以利用之前符号化内存的方式实现解题,我们学到的关键函数为:
1 2 3 4 5 6 7 8 9 10 11 12 fake_addr = XXX buffer_addr = XXX initial_state.memory.store(buffer_addr, fake_addr, endness=project.arch.memory_endness) initial_state.memory.store(fake_addr, password)
参数:endness=project.arch.memory_endness
在默认情况下,angr
使用大端格式往内存中写入整数,这个参数告诉angr
使用小端格式写入,这是x86的格式
07_angr_symbolic_file
看到题目发现里面是一个文件操作,从文件里进行读取数据,而我们需要将文件进行符号化处理,而angr
也提供了对应的封装模块来供我们进行调用处理。下面列出其符号化文件的方式:
1 2 3 4 5 6 7 8 9 10 11 12 start_addr = XXX filename = 'XXX' symbolic_file_size_bytes = 64 password = claripy.BVS('password' , symbolic_file_size_bytes * 8 ) password_file = angr.SimFile(filename, content=password, size=symbolic_file_size_bytes) initial_state = project.factory.blank_state(addr=start_addr, fs={filename: password_file})
那么我们就可以直接在第二题的条件约束上扩展,加入这一串对文件进行格式化的操作指令:
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 import sysimport angrimport claripyfrom Crypto.Util.number import long_to_bytesdef main (): binary_path = '../program/07_angr_symbolic_file' project = angr.Project(binary_path) start_addr = 0x080488EA filename = 'MRXJKZYR.txt' symbolic_file_size_bytes = 64 password = claripy.BVS('password' , symbolic_file_size_bytes * 8 ) password_file = angr.SimFile(filename, content=password, size=symbolic_file_size_bytes) initial_state = project.factory.blank_state(addr=start_addr, fs={filename: password_file}) simulation = project.factory.simgr(initial_state) def is_successful (state ): stdout_output = state.posix.dumps(sys.stdout.fileno()) return b'Good Job.' in stdout_output def should_abort (state ): stdout_output = state.posix.dumps(sys.stdout.fileno()) return b'Try again.' in stdout_output simulation.explore(find=is_successful, avoid=should_abort) if simulation.found: solution_state = simulation.found[0 ] solution = long_to_bytes(solution_state.solver.eval (password)) print (solution.decode()) else : raise Exception('Could not find solution' ) if __name__ == '__main__' : main()
在这节里面我们学会了如何对文件进行符号化,来创建我们的约束求解器,其关键的函数如下:
1 2 3 4 password_file = angr.SimFile(filename, content=password, size=symbolic_file_size_bytes) initial_state = project.factory.blank_state(addr=start_addr, fs={filename: password_file})
08_angr_constraints
把程序拖入进IDA
进行分析可以得到如下伪代码,可以看出程序将我们对输入在经过加密后与字符串MRXJKZYRKMKENFZB
进行比较值,我们可以轻松的看出目的的比较,但是angr
是遍历各个分支来进行模拟的,因此时间开销十分大,我们需要将其进行减小模拟的过程分支来缩短模拟的时间。
那么需要怎么操作?我们可以将buffer
进行符号化处理,然后让程序来替代我们加密,之后我们切换到angr
中,由我们来对加密后的数据进行检验,看是否与目的字符串相等。
1 2 3 4 addr_to_check_constraint = 0x08048669 simulation.explore(find=addr_to_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 if simulation.found: solution_state = simulation.found[0 ] constrained_parameter_addr = 0x0804A050 constrained_parameter_size_bytes = 16 constrained_parameter_bitvector = solution_state.memory.load(constrained_parameter_addr,constrained_parameter_size_bytes) constrained_parameter_desired_value = 'MRXJKZYRKMKENFZB' constrained_expression = constrained_parameter_bitvector == constrained_parameter_desired_value solution_state.add_constraints(constrained_expression) solution = long_to_bytes(solution_state.se.eval (password)) print (solution.decode())else : raise Exception('Could not find the sokution' )
那么脚本如下:
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 import sysimport angrimport claripyfrom Crypto.Util.number import long_to_bytesdef main (): binary_path = '../program/08_angr_constraints' project = angr.Project(binary_path) start_addr = 0x08048625 initial_state = project.factory.blank_state(addr=start_addr) password = claripy.BVS('password' , 16 *8 ) password_addr = 0x0804A050 initial_state.memory.store(password_addr, password) simulation = project.factory.simgr(initial_state) addr_to_check_constraint = 0x08048669 simulation.explore(find=addr_to_check_constraint) if simulation.found: solution_state = simulation.found[0 ] constrained_parameter_addr = 0x0804A050 constrained_parameter_size_bytes = 16 constrained_parameter_bitvector = solution_state.memory.load(constrained_parameter_addr, constrained_parameter_size_bytes) constrained_parameter_desired_value = 'MRXJKZYRKMKENFZB' constrained_expression = constrained_parameter_bitvector == constrained_parameter_desired_value solution_state.add_constraints(constrained_expression) solution = long_to_bytes(solution_state.se.eval (password)) print (solution.decode()) else : raise Exception('Could not find the sokution' )if __name__ == '__main__' : main()
在这一个题目中我们学会了将angr
的遍历范围进行约束,来提升求解速度。
1 2 3 4 5 constrained_parameter_bitvector = solution_state.memory.load(constrained_parameter_addr, constrained_parameter_size_bytes) constrained_expression = XXX solution_state.add_constraints(constrained_expression)
09_angr_hooks
拖入IDA
分析,可以看到程序是由两个输入组成,当第一个输入错误时便会退出,其中第一次的输入经过加密后需要等于password
,第二次的输入需要等于加密后的password
我们观察一下程序中需要hook
的函数,可以看到程序执行的是进行比较字符串的操作,返回的是一个bool
类型的数据,对此我们可以直接自己创建一个函数来进行直接比较并返回对应bool
值,而不需要通过程序一个个的遍历判断,下面展示如何实现一个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 check_equals_caller_addr = 0x080486A9 instruction_to_skip_length = 0x080486BB - 0x080486A9 @project.hook(check_equals_caller_addr, instruction_to_skip_length ) def skip_check_equals (state ): user_input_buffer_addr = 0x0804A054 user_input_buffer_length = 16 user_input_string = state.memory.load( user_input_buffer_addr, user_input_buffer_length ) check_against_string = 'MRXJKZYRKMKENFZB' state.regs.eax = claripy.If( user_input_string == check_against_string, claripy.BVV(1 , 32 ), claripy.BVV(0 , 32 ) ) simulation = project.factory.simgr(initial_state)
那么程序的关键部分也便实现了,我们可以编写如下脚本:
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 import sysimport angrimport claripydef main (): binary_path = './09_angr_hooks' project = angr.Project(binary_path) initial_state = project.factory.entry_state() check_equals_caller_addr = 0x080486A9 instruction_to_skip_length = 0x080486BB - 0x080486A9 @project.hook(check_equals_caller_addr, length = instruction_to_skip_length ) def skip_check_equals (state ): user_input_buffer_addr = 0x0804A054 user_input_buffer_length = 16 user_input_string = state.memory.load( user_input_buffer_addr, user_input_buffer_length ) check_against_string = 'MRXJKZYRKMKENFZB' state.regs.eax = claripy.If( user_input_string == check_against_string, claripy.BVV(1 , 32 ), claripy.BVV(0 , 32 ) ) def is_successful (state ): stdout_output = state.posix.dumps(sys.stdout.fileno()) return b'Good Job.' in stdout_output def should_abort (state ): stdout_output = state.posix.dumps(sys.stdout.fileno()) return b'Try again.' in stdout_output simulation = project.factory.simgr(initial_state) simulation.explore(find=is_successful, avoid=should_abort) if simulation.found: solution_state = simulation.found[0 ] solution = solution_state.posix.dumps(sys.stdin.fileno()) print (solution.decode()) else : raise Exception('Could not find the solution' )if __name__ == '__main__' : main()
通过这个例子我们学会了使用hook
来代替程序中的函数,并执行我们自己构造的函数。其关键函数如下
1 2 3 4 5 6 7 8 9 10 @project.hook(check_equals_caller_addr, length = instruction_to_skip_length ) state.regs.eax = claripy.If( user_input_string == check_against_string, claripy.BVV(1 , 32 ), claripy.BVV(0 , 32 ) )
10_angr_simprocedures
上一道题目我们是只利用一个hook
进行模拟了一个函数,但是在这个题目里面我们可以找到许许多多的函数,如果一个个写会累死的 ( bushi )
所以在这个题目中我们需要想办法hook
一片的函数,那么我们为什么不想一下我们把名字hook
了,意味着每次调用这个相同名字的函数时执行的便是我们自己编写的过程。实现如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 class mySimPro (angr.SimProcedure ): def run (self, user_input_addr, user_input_length ): angr_bvs = self.state.memory.load( user_input_addr, user_input_length ) desired = 'MRXJKZYRKMKENFZB' return claripy.If( desired == angr_bvs, claripy.BVV(1 ,32 ), claripy.BVV(0 ,32 ) ) check_symbol = 'check_equals_MRXJKZYRKMKENFZB' project.hook_symbol(check_symbol,mySimPro()) simulation = project.factory.simgr(initial_state)
那么脚本编写如下:
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 from pydoc import describeimport angrimport sysimport claripydef main (): binary_path = './09_angr_hooks' project = angr.Project(binary_path) initial_state = project.factory.entry_state() class mySimPro (angr.SimProcedure ): def run (self, user_input_addr, user_input_length ): angr_bvs = self.state.memory.load( user_input_addr, user_input_length ) desired = 'MRXJKZYRKMKENFZB' return claripy.If( desired == angr_bvs, claripy.BVV(1 ,32 ), claripy.BVV(0 ,32 ) ) check_symbol = 'check_equals_MRXJKZYRKMKENFZB' project.hook_symbol(check_symbol,mySimPro()) simulation = project.factory.simgr(initial_state) def is_successful (state ): stdout_output = state.posix.dumps(sys.stdout.fileno()) return b'Good Job.' in stdout_output def should_abort (state ): stdout_output = state.posix.dumps(sys.stdout.fileno()) return b'Try again.' in stdout_output simulation.explore(find=is_successful, avoid=should_abort) if simulation.found: solution_state = simulation.found[0 ] solution = solution_state.posix.dumps(sys.stdin.fileno()) print (solution.decode()) else : raise Exception('Could not find the solution' )if __name__ == '__main__' : main()
在这道题目中我们学习了如何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 class ReplacementCheckEquals (angr.SimProcedure ): def run (self, to_check, length ): user_input_buffer_addr = to_check user_input_buffer_length = length user_input_string = self.state.memory.load( user_input_buffer_addr, user_input_buffer_length ) check_against_string = XXX return claripy.If( user_input_string == check_against_string, claripy.BVV(1 , 32 ), claripy.BVV(0 , 32 ) ) check_equals_symbol = 'check_equals_MRXJKZYRKMKENFZB' project.hook_symbol(check_equals_symbol, ReplacementCheckEquals()) ----------------------------------------------------------------------------------- check_equals_addr = XXX project.hook(check_equals_addr, ReplacementCheckEquals())
11_angr_sim_scanf
随着angr
的更新迭代,似乎逐渐支持了scanf
这一类的函数,可以不需要手动模拟scanf
,angr
可以直接自动化模拟,问了一下草莓师傅,似乎angr
现在还能自动hook
,关于这个我也没有去测试过,也不是太了解这些。
根据题目要求,需要我们模拟scanf
来进行模拟输入,实现方式似乎类似于hook
函数名,同样需要我们进行继承angr
下的SimpProcedure
,只是里面的一些函数发生了变化:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 class ReplacementScanf (angr.SimProcedure ): def run (self, fmt, scanf0_addr, scanf1_addr ): scanf0 = claripy.BVS('scanf0' , 4 *8 ) scanf1 = claripy.BVS('scanf1' , 4 *8 ) self.state.memory.store(scanf0_addr, scanf0, endness=project.arch.memory_endness) self.state.memory.store(scanf1_addr, scanf1, endness=project.arch.memory_endness) self.state.globals ['solution0' ] = scanf0 self.state.globals ['solution1' ] = scanf1 scanf_symbol = '__isoc99_scanf' project.hook_symbol(scanf_symbol, ReplacementScanf()) simulation = project.factory.simgr(initial_state)
通过上述模拟可以将输入的字符串保存到内存空间,那么我们便可以写出如下代码:
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 sysimport angrimport claripydef main (): binary_path = '../program/11_angr_sim_scanf' project = angr.Project(binary_path) initial_state = project.factory.entry_state() class ReplacementScanf (angr.SimProcedure ): def run (self, fmt, scanf0_addr, scanf1_addr ): scanf0 = claripy.BVS('scanf0' , 4 *8 ) scanf1 = claripy.BVS('scanf1' , 4 *8 ) self.state.memory.store(scanf0_addr, scanf0, endness=project.arch.memory_endness) self.state.memory.store(scanf1_addr, scanf1, endness=project.arch.memory_endness) self.state.globals ['solution0' ] = scanf0 self.state.globals ['solution1' ] = scanf1 scanf_symbol = '__isoc99_scanf' project.hook_symbol(scanf_symbol, ReplacementScanf()) simulation = project.factory.simgr(initial_state) def is_successful (state ): stdout_output = state.posix.dumps(sys.stdout.fileno()) return b'Good Job.' in stdout_output def should_abort (state ): stdout_output = state.posix.dumps(sys.stdout.fileno()) return b'Try again.' in stdout_output simulation.explore(find=is_successful, avoid=should_abort) if simulation.found: solution_state = simulation.found[0 ] solution0 = solution_state.globals ['solution0' ] solution1 = solution_state.globals ['solution1' ] solution = '%u %u' % (solution_state.se.eval (solution0), solution_state.se.eval (solution1)) print (solution) else : raise Exception('Could not find the solution' )if __name__ == '__main__' : main()
我们在这个题中同样利用到了符号化函数名的方式来对一堆的scanf
进行模拟,并将其保存在内存段里,具体函数如下:
1 2 self.state.globals ['solution0' ] = scanf0 solution0 = solution_state.globals ['solution0' ]
12_angr_veritesting
在这个题目中与01
的avoid
来约束目标输出字符串,但是这个题目中循环的次数较大,如果直接套用01
的脚本可能会出现路径爆炸 ( angr
模拟执行时遍历的路径是成指数级上涨,如果都需要需要执行那么便会引发错误 ),我们之前所做的hook
函数,添加约束条件都是有效的防止路径爆炸的操作,我们接下来采用一个新的方式来解决路径爆炸的问题:
1 sm=p.factory.simulation_manager(istate,veritesting=True )
这个函数让我们直接忽视循环结构,直接进行求解,一般耗时会比较长。
加入这个防止路径爆炸的脚本为:
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 sysimport angrdef main (): binary_path = '../program/12_angr_veritesting' project = angr.Project(binary_path) initial_state = project.factory.entry_state() simulation = project.factory.simgr(initial_state, veritesting=True ) def is_successful (state ): stdout_output = state.posix.dumps(sys.stdout.fileno()) return b'Good Job.' in stdout_output def should_abort (state ): stdout_output = state.posix.dumps(sys.stdout.fileno()) return b'Try again.' in stdout_output simulation.explore(find=is_successful, avoid=should_abort) if simulation.found: solution_state = simulation.found[0 ] solution = solution_state.posix.dumps(sys.stdin.fileno()) print (solution) else : raise Exception('Could not find the solution' )if __name__ == '__main__' : main()
13_angr_static_binary
这个示例的逻辑和01
题是一样的,主要不同的地方是在于这个程序是静态链接编译的,所以程序中包含了一些libc
的函数实现,但是这里可能会存在两个问题:
这些函数里面隐藏一些出题人的坑;
这些函数里面的实现可能会依赖其他的系统函数或者实现方式不相同.所以12
题主要是让我们通过Hook
的方式重定向函数中被调用的libc
的函数
首先,Linux 下启动main() 函数需要通过__libc_start_main
对程序进行初始化,然后再跳转到main
() 函数;其次,在main
() 函数里面调用了printf
,scanf
,puts
,所以我们需要通过Hook 来重定向它们.
幸运的是,我们不需要重新实现这些函数的实现,Angr
代码库里面已经帮我们实现了一部分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 import angrimport sys project = angr.Project(sys.argv[1 ]) initial_state = project.factory.entry_state() simulation = project.factory.simgr(initial_state,veritesting = True ) project.hook(0x804ed40 , angr.SIM_PROCEDURES['libc' ]['printf' ]()) project.hook(0x804ed80 , angr.SIM_PROCEDURES['libc' ]['scanf' ]()) project.hook(0x804f350 , angr.SIM_PROCEDURES['libc' ]['puts' ]()) project.hook(0x8048d10 , angr.SIM_PROCEDURES['glibc' ]['__libc_start_main' ]())def is_successful (state ): stdout_output = state.posix.dumps(sys.stdout.fileno()) return 'Good Job.' in str (stdout_output) def should_abort (state ): stdout_output = state.posix.dumps(sys.stdout.fileno()) return 'Try again.' in str (stdout_output) simulation.explore(find = is_successful,avoid = should_abort)if simulation.found : solution_state = simulation.found[0 ] print (solution_state.posix.dumps(sys.stdin.fileno()))
通过上述题目,我们了解到了如何将静态链接中的库函数进行hook
,从而来获取libc
的函数库:
1 2 project.hook(<调用地址>, angr.SIM_PROCEDURES ['<系统库名>' ]['<系统函数名>' ]()) # hook库函数 angr.SIM_PROCEDURES [ 系统库名 ] [ 系统函数名 ] () => 获取Angr 内部实现的系统函数
14_angr_shared_library
在编译好的程序中我们可以看到有一个validate
函数对我们的输入进行了比较,但是进入这个函数却发现没有相关的实现方式
这是因为validate
在共享库里,即程序提供的so
中,事实上so
也算是一种可执行文件,但是没有经过符号链接,我们需要的便是通过angr
加载共享库,然后伪造参数来对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 def main (argv ): path_to_binary = sys.argv[1 ] base = 0x400000 project = angr.Project(path_to_binary, load_options={ 'main_opts' : { 'custom_base_addr' : base } }) buffer_pointer = claripy.BVV(0x3000000 , 32 ) validate_function_address = base + 0x6D7 initial_state = project.factory.call_state(validate_function_address, buffer_pointer,claripy.BVV(8 , 32 )) password = claripy.BVS('password' , 8 * 8 ) initial_state.memory.store(buffer_pointer, password) simulation = project.factory.simgr(initial_state) simulation.explore(find = base + 0x783 ) if simulation.found: solution_state = simulation.found[0 ] solution_state.add_constraints(solution_state.regs.eax != 0 ) solution = solution_state.se.eval (password) print (solution)
15_angr_arbitrary_read
单纯的从程序上看的话似乎程序一直是输入错误,但是我们到栈上看输入的变量,可以发现读入了20
个字符,但是栈空间中到s
的距离仅有16
,所以意味着多出来的4
字节会覆盖s
保存的地址,因此我们可以直接用这4字节进行替换到输出正确的地址处。
所以脚本可以如下进行编写:
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 def main (argv ): path_to_binary = argv[1 ] project = angr.Project(path_to_binary) initial_state = project.factory.entry_state() class ReplacementScanf (angr.SimProcedure ): def run (self, format_string, check_key_address,input_buffer_address ): scanf0 = claripy.BVS('scanf0' , 4 * 8 ) scanf1 = claripy.BVS('scanf1' , 20 * 8 ) for char in scanf1.chop(bits=8 ): self.state.add_constraints(char >= '0' , char <= 'z' ) self.state.memory.store(check_key_address, scanf0, endness=project.arch.memory_endness) self.state.memory.store(input_buffer_address, scanf1,endness=project.arch.memory_endness) self.state.globals ['solution0' ] = scanf0 self.state.globals ['solution1' ] = scanf1 scanf_symbol = '__isoc99_scanf' project.hook_symbol(scanf_symbol, ReplacementScanf()) def check_puts (state ): puts_parameter = state.memory.load(state.regs.esp + 4 , 4 , endness=project.arch.memory_endness) if state.se.symbolic(puts_parameter): good_job_string_address = 0x4D525854B copied_state = state.copy() copied_state.add_constraints(puts_parameter == good_job_string_address) if copied_state.satisfiable(): state.add_constraints(puts_parameter == good_job_string_address) return True else : return False else : return False simulation = project.factory.simgr(initial_state) def is_successful (state ): puts_address = 0x8048370 if state.addr == puts_address: return check_puts(state) else : return False simulation.explore(find=is_successful) 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' ],cast_to=bytes ) print (solution0,solution1)
通过这个题目我们可以学习到添加对输入的约束,和复制状态的上下文,以及序列化内容转化为字符串:
1 2 3 4 5 state.copy() state.satisfiable() solution_state.se.eval (求解变量,cast_to=bytes )
16_angr_arbitrary_write
从程序看,其最终目的是将password_buffer
里面的字符替换成KZYRKMKE
,但是程序中彬没有指向password_buffer
的变量,同时我们也发现与上一个题目一样函数存在溢出的漏洞,所以我们利用这个4字节
溢出将目的password_buffer
的值修改,达到输出正确的目的。
脚本如下:
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 def main (argv ): path_to_binary = argv[1 ] project = angr.Project(path_to_binary) initial_state = project.factory.entry_state() class ReplacementScanf (angr.SimProcedure ): def run (self, format_string, check_key ,input_buffer ): scanf0 = claripy.BVS('scanf0' , 4 * 8 ) scanf1 = claripy.BVS('scanf1' , 20 * 8 ) for char in scanf1.chop(bits=8 ): self.state.add_constraints(char >= '0' , char <= 'z' ) self.state.memory.store(check_key, scanf0, endness=project.arch.memory_endness) self.state.memory.store(input_buffer, scanf1, endness=project.arch.memory_endness) self.state.globals ['solution0' ] = scanf0 self.state.globals ['solution1' ] = scanf1 scanf_symbol = '__isoc99_scanf' project.hook_symbol(scanf_symbol, ReplacementScanf()) def check_strncpy (state ): strncpy_dest = state.memory.load(state.regs.esp + 4 , 4 , endness=project.arch.memory_endness) strncpy_src = state.memory.load(state.regs.esp + 8 , 4 , endness=project.arch.memory_endness) strncpy_len = state.memory.load(state.regs.esp + 12 , 4 , endness=project.arch.memory_endness) src_contents = state.memory.load(strncpy_src, strncpy_len) if state.se.symbolic(strncpy_dest) and state.se.symbolic(src_contents) : if state.satisfiable(extra_constraints=(src_contents[ -1 : -64 ] == 'KZYRKMKE' ,strncpy_dest == 0x4D52584C )): state.add_constraints(src_contents[ -1 : -64 ] == 'KZYRKMKE' ,strncpy_dest == 0x4D52584C ) return True else : return False else : return False simulation = project.factory.simgr(initial_state) def is_successful (state ): strncpy_address = 0x8048410 if state.addr == strncpy_address: return check_strncpy(state) else : return False simulation.explore(find=is_successful) 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' ],cast_to=bytes ) print (solution0,solution1)
17_angr_arbitrary_iump
一打开程序一看就只有一个输入,和一个输入错误,我们查看其函数,发现在某个地址处可以发现有输出Good
,我们可以判断出这个是将ret
的返回地址进行覆盖然后跳转到对应的输入正确函数地址处。
脚本如下:
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 def main (argv ): path_to_binary = argv[1 ] project = angr.Project(path_to_binary) initial_state = project.factory.entry_state() simulation = project.factory.simgr( initial_state, save_unconstrained=True , stashes={ 'active' : [initial_state], 'unconstrained' : [], 'found' : [], 'not_needed' : [] } ) class ReplacementScanf (angr.SimProcedure ): def run (self, format_string, input_buffer_address ): input_buffer = claripy.BVS('input_buffer' , 64 * 8 ) for char in input_buffer.chop(bits=8 ): self.state.add_constraints(char >= '0' , char <= 'z' ) self.state.memory.store(input_buffer_address, input_buffer, endness=project.arch.memory_endness) self.state.globals ['solution' ] = input_buffer scanf_symbol = '__isoc99_scanf' project.hook_symbol(scanf_symbol, ReplacementScanf()) while (simulation.active or simulation.unconstrained) and (not simulation.found): for unconstrained_state in simulation.unconstrained: def should_move (s ): return s is unconstrained_state simulation.move('unconstrained' , 'found' , filter_func=should_move) simulation.step() if simulation.found: solution_state = simulation.found[0 ] solution_state.add_constraints(solution_state.regs.eip == 0x4D525849 ) solution = solution_state.se.eval (solution_state.globals ['solution' ],cast_to = bytes ) print (solution)