angr 使用详细步骤
0.引入angr 和claripy
1.创建项目project
project=angr.Project(path,auto_load_libs=False)
其中path为要运行的文件的路径比如这样
path="C:\\Users\\DELL\\Desktop\\angr_ctf-master\\solutions\\11_angr_sim_scanf\\11_angr_sim_scanf"
auto_load_libs=False的意思是不自动自动载入依赖的库
如果是位置无关文件,还要加上载入的基地址,比如这样
base = 0x66666666 proj = angr.Project(path, main_opts={'base_addr': base})
|
2.设置初始状态initial_state
initial_state = project.factory.entry_state()
其中entry_state()意思是从程序入口点开始的,在elf文件里也就是start函数的开始位置
如果想知道设置状态的开始地址可以这样
print(intial_state.addr)
打印出这个state的地址
当然我们可以不选择entry_state(),而是我们自定义的地址
比如这样
start_address = 0x08048980 initial_state = project.factory.blank_state(addr=start_address)
|
blank_state:构造一个“空状态”,它的大多数数据都是未初始化的。当使用未初始化的的数据时,一个不受约束的符号值将会被返回
.entry_state:构造一个已经准备好从函数入口点执行的状态
- 在这个地方还可以添加veritesting 具体用法看文章末尾部分
3. 创建符号位向量,并将其存入内存或者其他地方(非必须)
首先应当说的是,这一部分是为了解决scanf问题而存在的,针对不同的情况我们将会将变量存入不同的地方
创建符号位向量
符号位向量是angr用于将符号值注入程序的数据类型。这些将是angr将解决的方程式的“ x”,也就是约束求解时的自变量。可以通过 BVV(value,size) 和 BVS( name, size) 接口创建位向量
passwd_size_in_bits = 32 passwd0 = claripy.BVS('passwd0', passwd_size_in_bits) passwd1 = claripy.BVS('passwd1', passwd_size_in_bits) passwd2 = claripy.BVS('passwd2', passwd_size_in_bits)
|
应当注意的是,第二个参数时位(bit)的个数,而不是字节的数量
存入寄存器
init_state.regs.eax = password0 init_state.regs.ebx = password1 init_state.regs.edx = password2
|
存入内存(全局变量)
pwd0_address = 0x0A29FAA0 pwd1_address = 0x0A29FAA8 pwd2_address = 0x0A29FAB0 init_state.memory.store(pwd0_address, password0) init_state.memory.store(pwd1_address, password1) init_state.memory.store(pwd2_address, poassword2)
|
存入栈空间(局部变量)
这里涉及到栈平衡,需要先将esp抬高到局部变量之前的位置
init_state.regs.ebp = init_state.regs.esp init_state.regs.esp -= 8 v2 = init_state.solver.BVS("ebp-12", 32) v1 = init_state.solver.BVS("ebp-16", 32) init_state.stack_push(v2) init_state.stack_push(v1)
|
接收动态内存(new malloc)
我们需要将原本应该存变量的堆空间改成虚假的占空间
virtual_heap_address0 = 0x44444444 pointer_to_recv_mallocspace0 = 0x0A79A118 virtual_heap_address1 = 0x44444454 pointer_to_recv_mallocspace1 = 0x0A79A120 init_state.memory.store(pointer_to_recv_mallocspace0, virtual_heap_address0, endness=proj.arch.memory_endness) init_state.memory.store(pointer_to_recv_mallocspace1, virtual_heap_address1, endness=proj.arch.memory_endness)
pwd0 = init_state.solver.BVS('pwd0', 8*8) pwd1 = init_state.solver.BVS('pwd1', 8*8)
init_state.memory.store(virtual_heap_address0, pwd0) init_state.memory.store(virtual_heap_address1, pwd1)
|
4.对函数进行hook(非必须)
当然这个部分也不是必须的
若只有一个函数让路径爆炸,通过hook调用要爆炸的那个函数的指令,替换成执行我们的回调函数(里面手动添加约束条件)
对函数hook有两种方式,一种是通过原函数的地址进行hook,一种是通过原函数的名字进行hook
通过地址进行hook
先看一个例子
hook_address = 0x080486B8 step_length = 5
@proj.hook(hook_address, length=step_length) def our_function(state): input_address = 0x0804A054 size_bytes = 16 input_string = state.memory.load(input_address, size_bytes) cmp_string = "XKSPZSJKJYQCQXZV" state.regs.eax = claripy.If( input_string == cmp_string, claripy.BVV(1, 32), claripy.BVV(0, 32) )
|
这里来源于大佬的原文章,注释已经非常详细了
通过函数名进行hook
如果那个路径爆炸的函数被调用多次,直接通过符号名来hook函数所有的调用地址
class ReplacementCheckEquals(angr.SimProcedure): def run(self, to_check, length): user_input_buffer_address = to_check user_input_buffer_length = length user_input_string = self.state.memory.load( user_input_buffer_address, user_input_buffer_length ) check_against_string = 'ORSDDWXHZURJRBDH' return claripy.If( user_input_string == check_against_string, claripy.BVV(1, 32), claripy.BVV(0, 32) )
check_equals_symbol = 'check_equals_ORSDDWXHZURJRBDH' project.hook_symbol(check_equals_symbol, ReplacementCheckEquals())
|
再介绍一种hook scanf函数的方法,这里要涉及到将位向量转换成全局变量的过程
class ReplacementScanf(angr.SimProcedure): def run(self, format_string, param0, param1): scanf0 = claripy.BVS('scanf0', 32) scanf1 = claripy.BVS('scanf1', 32)
scanf0_address = param0 self.state.memory.store(scanf0_address, scanf0, endness=project.arch.memory_endness) scanf1_address = param1 self.state.memory.store(scanf1_address, scanf1, endness=project.arch.memory_endness)
self.state.globals['solutions'] = (scanf0, scanf1)
scanf_symbol = '__isoc99_scanf' project.hook_symbol(scanf_symbol, ReplacementScanf())
|
5. 设置Simulation Managers
SimState 对象包含程序运行时信息,如内存/寄存器/文件系统数据等。SM(Simulation Managers)是angr中最重要的控制接口,它使你能够同时控制一组状态(state)的符号执行,应用搜索策略来探索程序的状态空间。
simulation = project.factory.simgr(initial_state)
6. 运行,探索满足路径需要的值
simulation.explore(find=···,avoid=···)
这里有两种方式设置成功和避免的条件,一种是通过地址,一种是通过检验输出
通过地址的方式
即在find=和avoid=后面添加地址
simulation.explore(find=0x405000,avoid=0x404700)
通过检验输出的方式
很多时候,成功的标志都是输出一个提示符,失败也一样,因此我们可以通过检验输出来判断是否成功
def is_successful(state): stdout = state.posix.dumps(1) return b'Good Job.' in stdout def should_abort(state): stdout_output = state.posix.dumps(1) return b'Try again.' in stdout_output simulation.explore(find=is_successful, avoid=should_abort)
|
要注意这里find和avoid后面的函数不加()
这里的 .posix.dumps() 中 ,括号里面是0的时候代表输入,是1的时候代表输出
7. 获取执行结果
if simulation.found: solution_state = simulation.found[0] solution = solution_state.posix.dumps(0) print("[+] Success! Solution is: {}".format(solution.decode("utf-8"))) else: print("no!")
|
这个例子只获取了一个解,如果想要获取多解,可以遍历simulation.found
for i in simulation.found: solution_state=i ···
|
8. 添加约束条件(非必须)
在第7步中间可以添加约束条件,这往往用在路径爆炸的时候,explore的成功标志在在路径爆炸之前,然后在第7步的时候手动添加限制条件
比如这样:
if simulation.found: for x in simulation.found: solution_state = x constrained_input_address = 0x804a050 constrained_size_bytes = 16 constrained_bitvector = solution_state.memory.load(constrained_input_address, constrained_size_bytes) cmp_string = "BWYRUBQCMVSBRGFU" solution_state.solver.add(constrained_bitvector == cmp_string) solved_string = solution_state.se.eval(string, cast_to=bytes).decode('utf-8') print(solved_string)
|
先取变量,使用.solver.add添加约束条件,使用.se.eval获得符合条件的值
其他的一些东西
veritesting
简单来说就是Veritesting结合了静态符合执行与动态符号执行,减少了路径爆炸的影响,在angr里我们只要在构造模拟管理器时,启用Veritesting了就行
simulation=project.factory.simgr(initial_state, veritesting=True)
|
静态编译的处理方法
这里其实就是对库函数进行一些hook所在的步骤位置在第4步
angr.SIM_PROCEDURES['libc']['malloc'] angr.SIM_PROCEDURES['libc']['fopen'] angr.SIM_PROCEDURES['libc']['fclose'] angr.SIM_PROCEDURES['libc']['fwrite'] angr.SIM_PROCEDURES['libc']['getchar'] angr.SIM_PROCEDURES['libc']['strncmp'] angr.SIM_PROCEDURES['libc']['strcmp'] angr.SIM_PROCEDURES['libc']['scanf'] angr.SIM_PROCEDURES['libc']['printf'] angr.SIM_PROCEDURES['libc']['puts'] angr.SIM_PROCEDURES['libc']['exit']
|
proj.hook(0x08048D10, angr.SIM_PROCEDURES['glibc']['__libc_start_main']()) proj.hook(0x0804ED40, angr.SIM_PROCEDURES['libc']['printf']()) proj.hook(0x0804ED80, angr.SIM_PROCEDURES['libc']['scanf']()) proj.hook(0x0804F350, angr.SIM_PROCEDURES['libc']['puts']()) proj.hook(0x0805B450, angr.SIM_PROCEDURES['libc']['strcmp']())
|
栈溢出利用
angr可以实现对漏洞的利用,在这里不做详细解释(因为笔者不太会pwn)
附加 公式模板
import angr import sys def main(): path='D:\\CTF_tools\\ez_fardebug.exe' project =angr.Project(path) initial_state=project.factory.entry_state( add_options ={ angr.options.SYMBOL_FILL_UNCONSTRAINED_MEMORY, angr.options.SYMBOL_FILL_UNCONSTRAINED_REGISTERS } ) simulation =project.factory.simgr(initial_state) def is_successful(state): stdout_ouput=state.posix.dumps(sys.stdout.fileno()) return stdout_ouput == b'you got it' def should_abort(state): stdout_ouput=state.posix.dumps(sys.stdout.fileno()) return stdout_ouput == b'wrong' simulation.explore(find=is_successful,avoid=should_abort)
if simulation.found: solution_state =simulation.found[0] print(solution_state.posix.dumps(sys.stdin.fileno()).decode()) else: print("no!")
main()
|