Angr_CTF 学习记录
项目地址:https://github.com/jakespringer/angr_ctf
-
00_angr_find
拖 ida,大致流程就是字符串加密后等于给定值,可以直接爆破。
Angr 的基本使用步骤:
1.创建 project 2.设置 state 3.新建符号量:BVS (bitvector symbolic ) 或 BVV (bitvector value) 4.把符号量设置到内存或者其他地方 5.设置 Simulation Managers,进行路径探索的对象 6.运行,探索满足路径需要的值 7.约束求解,获取执行结果
python 虚拟环境配置:
$ export WORKON_HOME=~/Envs $ mkdir -p $WORKON_HOME $ source /usr/share/virtualenvwrapper/virtualenvwrapper.sh $ mkvirtualenv angr
解题脚本:
import angr import sys def Go(): # 创建 project path_to_binary = "./00_angr_find" project = angr.Project(path_to_binary, auto_load_libs=False) # 设置 state initial_state = project.factory.entry_state() # 设置 Simulation Managers simulation = project.factory.simgr(initial_state) # 运行,探索满足路径需要的值 print_good_address = 0x8048678 simulation.explore(find=print_good_address) if simulation.found: solution_state = simulation.found[0] solution = solution_state.posix.dumps(sys.stdin.fileno()) print("[+] Success! Solution is: {}".format(solution.decode("utf-8"))) else: raise Exception('Could not find the solution') if __name__ == "__main__": Go()
创建 project:angr 的二进制装载组件是 CLE,负责装载二进制对象并把这个对象交给其他的 angr 组件。Project 类就是二进制文件的实体,auto_load_libs 设置是否载入依赖的库。
设置 state:state 代表程序的一个实例镜像,模拟执行某个时刻的状态。保存运行状态的上下文信息,如内存/寄存器等。
设置 Simulation Managers:SimState 代表程序的一个实例镜像,模拟执行某个时刻的状态。SM(Simulation Managers)是 angr 中最重要的控制接口,它使你能够同时控制一组状态 (state) 的符号执行,应用搜索策略来探索程序的状态空间。
运行,探索满足路径需要的值:当使用 find 参数启动 .explore() 方法时,程序将会一直执行,知道发现一个和 find 参数指定的条件相匹配的状态。find 参数的内容可以是想要执行到的某个地址、或者想要执行到的地址列表、或者一个获取 state 作为参数并判断这个 state 是否满足某些条件的函数。
-
01_angr_avoid
让执行流进入 maybe_good,避免进入 avoid_me
import angr import sys def Go(): path_to_binary = "./01_angr_avoid" project = angr.Project(path_to_binary, auto_load_libs=False) initial_state = project.factory.entry_state() simulation = project.factory.simgr(initial_state) avoid_me_address = 0x080485A8 maybe_good_address = 0x080485E0 simulation.explore(find=maybe_good_address, avoid=avoid_me_address) if simulation.found: solution_state = simulation.found[0] solution = solution_state.posix.dumps(sys.stdin.fileno()) print("[+] Success! Solution is: {}".format(solution.decode("utf-8"))) else: raise Exception('Could not find the solution') if __name__ == "__main__": Go()
-
02_angr_find_condition
和 00,差不多,但是需要根据 stdout 判断应该避免或保留的内容。
from asyncio import FastChildWatcher import angr import sys def Go(): path_to_binary = "./00_angr_find" project = angr.Project(path_to_binary, auto_load_libs=False) initial_state = project.factory.entry_state() simulation = project.factory.simgr(initial_state) def is_successful(state): stdout = state.posix.dumps(sys.stdout.fileno()) if b'Good Job.' in stdout: return True else: return False def should_about(state): stdout = state.posix.dumps(sys.stdout.fileno()) if b'Try again.' in stdout: return True else: return False simulation.explore(find=is_successful, avoid=should_about) if simulation.found: solution_state = simulation.found[0] solution = solution_state.posix.dumps(sys.stdin.fileno()) print("[+] Success! Solution is: {}".format(solution.decode("utf-8"))) else: raise Exception('Could not find the solution') if __name__ == "__main__": Go()
-
03_angr_simbolic_registers
scanf 使用了多个寄存器输入,所以这里的控制输入可以直接修改三个寄存器,就不需要从 main 函数入口进入了,所以这里的状态创建选择了 blank_state(),创建一个空的状态。
名称 描述 entry_state()
构造一个已经准备好从函数入口点( main
)执行的状态blank_state()
构造一个“空状态”,它的大多数数据都是未初始化的。当使用未初始化的的数据时,一个不受约束的符号值将会被返回,记住当需要从程序中任意一点执行的时候使用 call_state()
构造一个已经准备好执行某个函数的状态 full_init_state()
构造一个已经执行过所有与需要执行的初始化函数,并准备从函数入口点执行的状态。比如,共享库构造函数(constructor)或预初始化器。当这些执行完之后,程序将会跳到入口点 import angr import sys import claripy def Go(): path_to_binary = "./03_angr_symbolic_registers" project = angr.Project(path_to_binary, auto_load_libs=False) start_address = 0x08048980 initial_state = project.factory.blank_state(addr=start_address) 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) # 访问寄存器 initial_state.regs.eax = passwd0 initial_state.regs.ebx = passwd1 initial_state.regs.edx = passwd2 simulation = project.factory.simgr(initial_state) def is_successful(state): stdout = state.posix.dumps(sys.stdout.fileno()) if b'Good Job.\n' in stdout: return True else: return False def should_abort(state): stdout = state.posix.dumps(sys.stdout.fileno()) if b'Good Job.\n' in stdout: return True else: return False simulation.explore(find=is_successful, avoid=should_abort) if simulation.found: for i in simulation.found: solution_state = i # 约束求解 solution0 = format(solution_state.solver.eval(passwd0), 'x') solution1 = format(solution_state.solver.eval(passwd1), 'x') solution2 = format(solution_state.solver.eval(passwd2), 'x') solution = solution0 + " " + solution1 + " " + solution2 print("[+] Success! Solution is: {}".format(solution)) else: raise Exception('Could not find the solution') if __name__ == "__main__": Go()
其中,符号位向量(bitvector)是 angr 用于将符号值注入程序的数据类型,也就是约束求解时的自变量。BVS 的第一个参数是用来引用位向量的名称,第二个是位向量本身的大小。
最后通过 state.solver.eval(symbol) 对各个断言进行评测来求出一个合法的符值。
-
04_angr_symbolic_stack
ida 看源码,发现输入的内容是在栈上,需要对栈上的值进行符号化处理:
import angr import sys import claripy def Go(): path_to_binary = "./04_angr_symbolic_stack" project = angr.Project(path_to_binary, auto_load_libs=False) start_address = 0x8048697 initial_state = project.factory.blank_state(addr = start_address) # 栈初始状态 initial_state.regs.ebp = initial_state.regs.esp # 4 字节的 s1 和 s2 passwd_size_in_bits = 32 passwd0 = claripy.BVS('passwd0', passwd_size_in_bits) passwd1 = claripy.BVS('passwd1', passwd_size_in_bits) # 抬高栈,开辟 4 + 4 = 8 的栈空间 padding_length_in_bytes = 0x8 initial_state.regs.esp -= padding_length_in_bytes initial_state.stack_push(passwd0) initial_state.stack_push(passwd1) simulation = project.factory.simgr(initial_state) def is_successful(state): stdout_output = state.posix.dumps(1) if b'Good Job.\n' in stdout_output: return True else: return False def should_abort(state): stdout_output = state.posix.dumps(1) if b'Try again.\n' in stdout_output: return True else: return False simulation.explore(find=is_successful, avoid=should_abort) if simulation.found: for i in simulation.found: solution_state = i solution0 = (solution_state.solver.eval(passwd0)) solution1 = (solution_state.solver.eval(passwd1)) print("[+] Success! Solution is: {0} {1}".format(solution0, solution1)) else: raise Exception('Could not find the solution') if __name__ == "__main__": Go()
插入的两个值位于:[EBP - 0x10],[EBP - 0xC],大致构造思路就是在压入堆栈之前布置好堆栈指针。
关于 eval:
solver.eval(expression) 将会解出一个可行解 solver.eval_one(expression)将会给出一个表达式的可行解,若有多个可行解,则抛出异常。 solver.eval_upto(expression, n)将会给出最多n个可行解,如果不足n个就给出所有的可行解。 solver.eval_exact(expression, n)将会给出n个可行解,如果解的个数不等于n个,将会抛出异常。 solver.min(expression)将会给出最小可行解 solver.max(expression)将会给出最大可行解
-
05_angr_symbolic_memory
输入的内容存在 .bss 段,符号化内存。
from enum import auto import angr import sys import claripy def Go(): path_to_binary = "./05_angr_symbolic_memory" project = angr.Project(path_to_binary, auto_load_libs=False) start_address = 0x08048601 initial_state = project.factory.blank_state(addr=start_address) # 4 个 8 字节,64 bit 的字符串 passwd_size_in_bits = 64 passwd0 = claripy.BVS('passwd0', passwd_size_in_bits) passwd1 = claripy.BVS('passwd1', passwd_size_in_bits) passwd2 = claripy.BVS('passwd2', passwd_size_in_bits) passwd3 = claripy.BVS('passwd3', passwd_size_in_bits) passwd0_address = 0x0A1BA1C0 initial_state.memory.store(passwd0_address, passwd0) initial_state.memory.store(passwd0_address + 0x8, passwd1) initial_state.memory.store(passwd0_address + 0x10, passwd2) initial_state.memory.store(passwd0_address + 0x18, passwd3) simulation = project.factory.simgr(initial_state) def is_successful(state): stdout_output = state.posix.dumps(1) if b'Good Job.\n' in stdout_output: return True else: return False def should_abort(state): stdout_output = state.posix.dumps(1) if b'Try again.\n' in stdout_output: return True else: return False simulation.explore(find=is_successful, avoid=should_abort) if simulation.found: for i in simulation.found: solution_state = i solution0 = solution_state.solver.eval(passwd0, cast_to=bytes) solution1 = solution_state.solver.eval(passwd1, cast_to=bytes) solution2 = solution_state.solver.eval(passwd2, cast_to=bytes) solution3 = solution_state.solver.eval(passwd3, cast_to=bytes) solution = solution0 + b" " + solution1 + b" " + solution2 + b" " + solution3 print("[+] Success! Solution is: {}".format(solution.decode("utf-8"))) else: raise Exception('Could not find the solution') if __name__ == "__main__": Go()
-
06_angr_symbolic_dynamic_memory
要控制的内容在堆上,这里开头跳过 malloc 直接控制内存。
from ctypes import pointer from enum import auto import angr import sys import claripy def Go(): path_to_binary = "./06_angr_symbolic_dynamic_memory" project = angr.Project(path_to_binary, auto_load_libs=False) start_address = 0x8048699 initial_state = project.factory.blank_state(addr=start_address) passwd_size_in_bits = 64 passwd0 = claripy.BVS('passwd0', passwd_size_in_bits) passwd1 = claripy.BVS('passwd1', passwd_size_in_bits) # buffer0 和 buffer1 的地址分别是 0xabcc8a4 和 0xabcc8ac fake_heap_address0 = 0xffffc93c pointer_to_malloc_memory_address0 = 0xabcc8a4 fake_heap_address1 = 0xffffc94c pointer_to_malloc_memory_address1 = 0xabcc8ac # 默认大端序,需要修改为小端序 # buffer -> fake_addr initial_state.memory.store(pointer_to_malloc_memory_address0, fake_heap_address0, endness=project.arch.memory_endness) initial_state.memory.store(pointer_to_malloc_memory_address1, fake_heap_address1, endness=project.arch.memory_endness) # fake_addr -> string initial_state.memory.store(fake_heap_address0, passwd0) initial_state.memory.store(fake_heap_address1, passwd1) simulation = project.factory.simgr(initial_state) def is_successful(state): stdout_output = state.posix.dumps(1) if b'Good Job.\n' in stdout_output: return True else: return False def should_abort(state): stdout_output = state.posix.dumps(1) if b'Try again.\n' in stdout_output: return True else: return False simulation.explore(find=is_successful, avoid=should_abort) if simulation.found: for i in simulation.found: solution_state = i solution0 = solution_state.solver.eval(passwd0, cast_to=bytes) solution1 = solution_state.solver.eval(passwd1, cast_to=bytes) print("[+] Success! Solution is: {0} {1}".format(solution0.decode('utf-8'), solution1.decode('utf-8'))) else: raise Exception('Could not find the solution') if __name__ == "__main__": Go()
这里不需要将内存分配到堆中,实际上可以伪造任何地址,原程序的逻辑可以解释为:
buffer -> malloc -> string
但我们实际上没有执行 malloc 操作,因此需要在 buffer 真实存储的位置写一个 fake_addr(任意值),并让指向 string 即可。
-
07_angr_symbolic_file
大致逻辑是先从内存中读了一段数据写到文件中,再从文件中读取数据存入 buffer,然后判断 buffer 是否符合条件。
需要用 angr 模拟一个文件系统,其中该文件被我们自己的模拟文件所替代,然后将该文件进行符号化处理。
import angr import sys import claripy def Go(): path_to_binary = "./07_angr_symbolic_file" project = angr.Project(path_to_binary, auto_load_libs=False) start_address = 0x80488EA initial_state = project.factory.blank_state(addr=start_address) filename = 'OJKSQYDP.txt' # %64s symbolic_file_size_bytes = 64 passwd0 = claripy.BVS('password', symbolic_file_size_bytes * 8) # 三个参数分别是:模拟的文件名称,模拟的文件内容(符号化变量或 string),文件大小 passwd_file = angr.storage.SimFile(filename, content=passwd0, size=symbolic_file_size_bytes) # 将文件插入到文件系统中,需要文件名和符号化的文件 initial_state.fs.insert(filename, passwd_file) simulation = project.factory.simgr(initial_state) def is_successful(state): stdout_output = state.posix.dumps(1) if b'Good Job.\n' in stdout_output: return True else: return False def should_abort(state): stdout_output = state.posix.dumps(1) if b'Try again.\n' in stdout_output: return True else: return False simulation.explore(find=is_successful, avoid=should_abort) if simulation.found: for i in simulation.found: solution_state = i solution0 = solution_state.solver.eval(passwd0, cast_to=bytes) print("[+] Success! Solution is: {0}".format(solution0)) #print(solution0) else: raise Exception('Could not find the solution') if __name__ == "__main__": Go()
SimFile 是一种存储抽象,定义符号或其他形式的字节序列,可以与文件系统,套接字,管道或终端等交互。
-
08_angr_constraints
当一个程序存在循环结构时,即使逻辑十分简单,也可能会产生规模巨大的执行路径,如果其中包括了分支结构,会导致程序分支路径数成指数级增长(路径爆炸),因此需要添加更多的约束条件控制路径爆炸。
程序的逻辑是 buffer(0x804A050) 经过处理后要和 0x08048565 这片内存的内容相同,那么只要在程序运行到地址 0x08048565 的时候直接进行比较即可,而不需要进入函数进行逐一字符比较。
from asyncio.proactor_events import constants from mimetypes import init import angr import sys import claripy def Go(): path_to_binary = "./08_angr_constraints" project = angr.Project(path_to_binary, auto_load_libs=False) start_address = 0x8048625 buff_addr = 0x0804A050 address_to_constraint = 0x08048565 initial_state = project.factory.blank_state(addr=start_address) char_size_in_bits = 8 passwd_len = 16 passwd0 = claripy.BVS('passwd0', char_size_in_bits*passwd_len) initial_state.memory.store(buff_addr, passwd0) # 先把程序运行到 check 函数的状态 simulation = project.factory.simgr(initial_state) simulation.explore(find=address_to_constraint) if simulation.found: solution_state = simulation.found[0] constrained_parameter_address = buff_addr constrained_parameter_size_bytes = 16 # 读出 buffer 中的数据 constrained_parameter_bitvector = solution_state.memory.load( constrained_parameter_address, constrained_parameter_size_bytes ) #利用 slover 求解引擎提供的 add 方法加入约束条件 constrained_parameter_desired_value = 'AUPDNNPROEZRJWKB' solution_state.solver.add(constrained_parameter_bitvector == constrained_parameter_desired_value) solution0 = solution_state.solver.eval(passwd0, cast_to=bytes) print("[+] Success! Solution is: {0}".format(solution0)) else: raise Exception('Could not find the solution') if __name__ == "__main__": Go()
-
09_angr_hooks
直接利用 hook 改写 complex_function 为我们自己的函数,从而跳过循环直接进行比较
from dis import Instruction import angr import sys import claripy def Go(): path_to_binary = "./09_angr_hooks" project = angr.Project(path_to_binary, auto_load_libs=False) initial_state = project.factory.entry_state() check_equals_called_address = 0x080486B3 instruction_to_skip_length = 5 # 第一个参数是 hook 调用的函数地址,第二个参数是指定执行引擎在完成 hook 后应跳过多少字节 # 本题中下一条指令的地址是 0x080486B8,与函数地址相差 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 = 16 # 先读出 buffer 处的数据 user_input_string = state.memory.load( user_input_buffer_address, user_input_buffer_length ) check_against_string = 'XKSPZSJKJYQCQXZV' register_size_bit = 32 # 要保证程序可以继续执行,所以新函数的返回值要和原函数相同,这里发现返回值在 eax 中,成功是 1,不成功是 0 state.regs.eax = claripy.If( user_input_string == check_against_string, claripy.BVV(1, register_size_bit), claripy.BVV(0, register_size_bit) ) simulation = project.factory.simgr(initial_state) def is_successful(state): stdout_output = state.posix.dumps(1) if b'Good Job.\n' in stdout_output: return True else: return False def should_abort(state): stdout_output = state.posix.dumps(1) if b'Try again.\n' in stdout_output: return True else: return False simulation.explore(find=is_successful, avoid=should_abort) if simulation.found: for i in simulation.found: solution_state = i solution = solution_state.posix.dumps(0) print("[+] Success! Solution is: {0}".format(solution.decode('utf-8'))) else: raise Exception('Could not find the solution') if __name__ == "__main__": Go()
-
10_angr_simprocedures
这里的 check_equals 被调用了很多次,所以要利用函数名而不是地址 hook 函数,angr 提供了专门的 API 来 Hook 函数所有的调用地址。
import angr import claripy import sys def Go(): paht_to_binary = "./10_angr_simprocedures" project = angr.Project(paht_to_binary, auto_load_libs=False) initial_state = project.factory.entry_state() # 继承 angr.SimProcedure,用于编写替代函数 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()) simulation = project.factory.simgr(initial_state) def is_successful(state): stdout_output = state.posix.dumps(1) if b'Good Job.\n' in stdout_output: return True else: return False def should_abort(state): stdout_output = state.posix.dumps(1) if b'Try again.\n' in stdout_output: return True else: return False simulation.explore(find=is_successful, avoid=should_abort) if simulation.found: for i in simulation.found: solution_state = i solution = solution_state.posix.dumps(0) print("[+] Success! Solution is: {0}".format(solution.decode('utf-8'))) else: raise Exception('Could not find the solution') if __name__ == "__main__": Go()
-
1
import angr import claripy import sys def Go(): paht_to_binary = "./11_angr_sim_scanf" project = angr.Project(paht_to_binary, auto_load_libs=False) initial_state = project.factory.entry_state() 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()) simulation = project.factory.simgr(initial_state) def is_successful(state): stdout_output = state.posix.dumps(1) if b'Good Job.\n' in stdout_output: return True else: return False def should_abort(state): stdout_output = state.posix.dumps(1) if b'Try again.\n' in stdout_output: return True else: return False simulation.explore(find=is_successful, avoid=should_abort) if simulation.found: for i in simulation.found: solution_state = i stored_solutions = solution_state.globals['solutions'] scanf0_solution = solution_state.solver.eval(stored_solutions[0]) scanf1_solution = solution_state.solver.eval(stored_solutions[1]) print("[+] Success! Solution is: {0} {1}".format(scanf0_solution,scanf1_solution)) else: raise Exception('Could not find the solution') if __name__ == "__main__": Go()
-
11_angr_sim_scanf
和上个题相比就是把要 hook 的函数换成了 scanf
import angr import claripy import sys def Go(): paht_to_binary = "./11_angr_sim_scanf" project = angr.Project(paht_to_binary, auto_load_libs=False) initial_state = project.factory.entry_state() 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()) simulation = project.factory.simgr(initial_state) def is_successful(state): stdout_output = state.posix.dumps(1) if b'Good Job.\n' in stdout_output: return True else: return False def should_abort(state): stdout_output = state.posix.dumps(1) if b'Try again.\n' in stdout_output: return True else: return False simulation.explore(find=is_successful, avoid=should_abort) if simulation.found: for i in simulation.found: solution_state = i stored_solutions = solution_state.globals['solutions'] scanf0_solution = solution_state.solver.eval(stored_solutions[0]) scanf1_solution = solution_state.solver.eval(stored_solutions[1]) print("[+] Success! Solution is: {0} {1}".format(scanf0_solution,scanf1_solution)) else: raise Exception('Could not find the solution') if __name__ == "__main__": Go()
其中的符号向量 scanf0, scanf1 在函数中定义,可以调用带有全局状态的 globals 产检中保存对符号值的引用。
-
12_angr_veritesting
动态符号执行(DSE)是路径生成公式,会摘要路径汇合点上两条分支的情况,产生很高的负载但公式容易理解。
静态符号执行(SSE)是语句生成公式,为两条分支fork两条独立的执行路径,结果就是生成公式很容易,也能覆盖更多的路径,但公式更长更难解。
Veritesting 可以在 SSE 和 DSE 之间切换,减少负载和公式求解难度,并解决静态方法需要摘要或其他方法才能处理的系统调用和间接跳转。
调用的话只要在构造模拟管理器的时候启用 Veritesting。
import angr import claripy import sys def Go(): path_to_binary = "./12_angr_veritesting" project = angr.Project(path_to_binary, auto_load_libs=False) initial_state = project.factory.entry_state() simulation = project.factory.simgr(initial_state, veritesting=True) def is_successful(state): stdout_output = state.posix.dumps(1) if b'Good Job.\n' in stdout_output: return True else: return False def should_abort(state): stdout_output = state.posix.dumps(1) if b'Try again.\n' in stdout_output: return True else: return False simulation.explore(find=is_successful, avoid=should_abort) if simulation.found: for i in simulation.found: solution_state = i solution = solution_state.posix.dumps(0) print("[+] Success! Solution is: {0}".format(solution)) else: raise Exception('Could not find the solution') if __name__ == "__main__": Go()
没有 hook 也没有改定位,加了个参数就能很快跑出来。
如果不加的话跑到一般虚拟机就崩了。。。。。。
-
13_angr_static_binary
这个题的文件是静态编译的。通常 Angr 会自动用工作速度快得多的 simprodure 代替标准库函数,但静态编译需要手动 Hook 所有使用的标准库的 C 函数。在 simprocedure 已经提供了一些常用库函数的 hook,然后需要把用到的库函数的地址一一 hook 即可。
import angr import claripy import sys def Go(): path_to_binary = "./13_angr_static_binary" project = angr.Project(path_to_binary, auto_load_libs=False) initial_state = project.factory.entry_state() 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']()) simulation = project.factory.simgr(initial_state, veritesting=True) def is_successful(state): stdout_output = state.posix.dumps(1) if b'Good Job.\n' in stdout_output: return True else: return False def should_abort(state): stdout_output = state.posix.dumps(1) if b'Try again.\n' in stdout_output: return True else: return False simulation.explore(find=is_successful, avoid=should_abort) if simulation.found: for i in simulation.found: solution_state = i solution = solution_state.posix.dumps(0) print("[+] Success! Solution is: {0}".format(solution)) else: raise Exception('Could not find the solution') if __name__ == "__main__": Go()
-
14_angr_shared_library
这个题中有外部导入的动态库。动态链接库中的共享对象最终装载地址在编译的时候是不确定的,需要装载器根据当前地址空间的空闲情况,动态分配一块足够大小的虚拟地址空闲。
程序没开 PIE 保护,基址为 0x4000000,而共享库中的所有地址都是 base+offset
import angr import claripy import sys def Go(): path_to_binary = "./lib14_angr_shared_library.so" base = 0x4000000 project = angr.Project(path_to_binary, load_options={ 'main_opts': { # backend —— 使用哪个后台,可以是一个对象,也可以是一个名字(字符串) # custom_base_addr —— 使用的基地址 # custom_entry_point —— 使用的入口点 # custom_arch —— 使用的处理器体系结构的名字 'custom_base_addr': base } }) # 创建 validate 用到的缓冲区 buffer_pointer = claripy.BVV(0x3000000, 32) validate_function_address = base + 0x6d7 # 传入的两个参数 (char *s1, int a2) 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) # 0x783 是 validate 的结束地址 success_address = base + 0x783 simulation.explore(find=success_address) if simulation.found: for i in simulation.found: # 如果 EAX 不为 0 说明就是正确结果 solution_state = i solution_state.add_constraints(solution_state.regs.eax != 0) solution = solution_state.solver.eval(password, cast_to=bytes) print("[+] Success! Solution is: {0}".format(solution)) else: raise Exception('Could not find the solution') if __name__ == "__main__": Go()
-
15_angr_arbitrary_read
简单 pwn,目标是输出 Good job. 这串字符在内存中,但没有指针指向,所以需要用溢出覆盖指针的地址。这里 v4 在 s 上面,并能溢出 4 字节,恰好可以覆盖 s 指针,使其指向 Good job. 即可。
import angr import claripy import sys def Go(): path_to_binary = "./15_angr_arbitrary_read" project = angr.Project(path_to_binary, auto_load_libs=False) initial_state = project.factory.entry_state() class ReplacementScanf(angr.SimProcedure): def run(self, format_string, param0, param1): # scanf 传入一个 8*4=32bit 的 key 和 20 个字符的 s scanf0 = claripy.BVS('scanf0', 32) scanf1 = claripy.BVS('scanf1', 20*8) # 每 8 位做切片,要求在 ascii 范围中 for char in scanf1.chop(bits=8): self.state.add_constraints(char >= 'A', char <= 'Z') scanf0_address = param0 scanf1_address = param1 self.state.memory.store(scanf0_address, scanf0, endness=project.arch.memory_endness) self.state.memory.store(scanf1_address, scanf1) self.state.globals['solutions'] = (scanf0, scanf1) # hook scanf 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 = 0x594e4257 is_vulnerable_expression = puts_parameter == good_job_string_address copied_state = state.copy() copied_state.add_constraints(is_vulnerable_expression) if copied_state.satisfiable(): state.add_constraints(is_vulnerable_expression) return True else: return False else: return False simulation = project.factory.simgr(initial_state) def is_successful(state): # 这里选用的 puts 地址是 PLT 中的地址,一旦调用了 puts 就从这个状态中获取栈中信息(esp+4 就是 puts 的参数)并进行判断 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] (scanf0, scanf1) = solution_state.globals['solutions'] solution0 = (solution_state.solver.eval(scanf0)) solution1 = (solution_state.solver.eval(scanf1, cast_to=bytes)) print("[+] Success! Solution is: {0} {1}".format(solution0, solution1)) else: raise Exception('Could not find the solution') if __name__ == "__main__": Go()
-
16_angr_arbitrary_write
需要实现任意地址写。s 定义 16 字节但能写入 20 字节,可以覆盖掉下面 dest 的地址,覆盖的条件就是 key 满足条件。
import angr import claripy import sys def Go(): path_to_binary = "./16_angr_arbitrary_write" project = angr.Project(path_to_binary, auto_load_libs=False) initial_state = project.factory.entry_state() class ReplacementScanf(angr.SimProcedure): def run(self, format_string, param0, param1): scanf0 = claripy.BVS('scanf0', 32) scanf1 = claripy.BVS('scanf1', 20*8) for char in scanf1.chop(bits=8): self.state.add_constraints(char >= 'A', char <= 'Z') scanf0_address = param0 scanf1_address = param1 self.state.memory.store(scanf0_address, scanf0, endness=project.arch.memory_endness) self.state.memory.store(scanf1_address, scanf1) self.state.globals['solutions'] = (scanf0, 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) # 这里获取的只是 src 的地址 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 的内容 src_contents = state.memory.load(strncpy_src, strncpy_len) if state.solver.symbolic(src_contents) and state.solver.symbolic(strncpy_dest): password_string = 'NDYNWEUJ' buffer_address = 0x57584344 # 调整小端序 does_src_hold_password = src_contents[-1:-64] == password_string does_dest_equal_buffer_address = strncpy_dest == buffer_address # 字符串内容确定后,还需要判断 dest 地址是不是要求地址 if state.satisfiable(extra_constraints=(does_src_hold_password, does_dest_equal_buffer_address)): state.add_constraints(does_src_hold_password, does_dest_equal_buffer_address) return True else: return False else: return False simulation = project.factory.simgr(initial_state) def is_successful(state): strncpy_address = 0x08048410 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] (scanf0, scanf1) = solution_state.globals['solutions'] solution0 = (solution_state.solver.eval(scanf0)) solution1 = (solution_state.solver.eval(scanf1, cast_to=bytes)) print("[+] Success! Solution is: {0} {1}".format(solution0, solution1)) else: raise Exception('Could not find the solution') if __name__ == "__main__": Go()
-
17_angr_arbitrary_jump
scanf 存在栈溢出,需要跳转到指定的 print_good 函数中。
import angr import claripy import sys def Go(): path_to_binary = "./17_angr_arbitrary_jump" project = angr.Project(path_to_binary) initial_state = project.factory.entry_state() class ReplacementScanf(angr.SimProcedure): def run(self, format_string, param): scanf = claripy.BVS('scanf0', 64*8) for char in scanf.chop(bits=8): self.state.add_constraints(char >= '0', char <= 'Z') self.state.memory.store(param, scanf, endness=project.arch.memory_endness) self.state.globals['solutions'] = (scanf) scanf_symbol = '__isoc99_scanf' project.hook_symbol(scanf_symbol, ReplacementScanf()) # 修改 Angr 引擎默认配置 simulation = project.factory.simgr( initial_state, # 指定 Angr 不抛出不受约束的状态,并将其移动到其他 stashes 中 save_unconstrained=True, stashes = { 'active' : [initial_state], 'unconstrained' : [], 'found' : [], 'not_needed' : [] } ) # 检查无约束状态是否可利用 def check_vulnerable(state): return state.solver.symbolic(state.regs.eip) def has_found_solution(): return simulation.found # 检查是否还有未受约束的状态需要检查 def has_unconstrained_to_check(): return simulation.unconstrained # active是可以进一步探索的所有状态的列表 def has_active(): return simulation.active # 解决方案 # 如果是约束状态下的解则失败,继续对无约束状态进行检查 while (has_active() or has_unconstrained_to_check()) and (not has_found_solution()): 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 == 0x4d4c4749) solution = solution_state.solver.eval( solution_state.globals['solutions'], cast_to=bytes) print(solution[::-1]) else: raise Exception('Could not find the solution') if __name__ == '__main__': Go()
当一条指令有太多可能的分支时,就会出现无约束状态,当指令指针完全是符号指针时,就会发生这种情况,这意味着用户输入可以控制计算机执行的代码地址。
mov user_input, eax jmp eax
这个题中出现的栈溢出就可以使程序进入这种无约束状态。Angr 在遇到无约束状态时会将其抛出,所以需要禁用 Angr 行为,借助这个无约束状态跳转到我们选择的位置。
angr 的状态:
active:程序仍能进一步执行 deadended:程序结束 errored:Angr执行中出现错误的状态 unconstrained:不受约束的状态 found:找到路径答案的状态 not_needed:所有其它情况
在 simulation.explore 中的 find 参数指定的方法不会在无约束状态下被调用,所以需要自己写解决方案。
-
参考文献