angr 入门介绍(二)
字数 1035 2025-08-24 20:49:22
Angr符号执行框架入门教程(二)
2.1 使用Angr解决Enigma 2017 Crackme 0
目标分析
我们需要解决Enigma 2017 Crackme 0,通过分析可以确定:
- 红色路径:执行了wrong函数,不感兴趣
- 绿色路径:感兴趣的代码路径
- 蓝色部分:Angr开始执行分析的指令地址
关键点:
- 可以丢弃所有到达wrong函数的状态
- 需要避免0x8048670路径(需要提供输入否则关闭程序)
- fromhex()函数返回0的状态是我们感兴趣的
- 输入字符串指针在调用fromhex()前被压栈
解决方案脚本
import angr
import claripy
def main():
path_to_binary = "./crackme_0"
project = angr.Project(path_to_binary)
# 关键地址定义
start_addr = 0x8048692 # "PUSH EAX"指令地址
avoid_addr = [0x8048541, 0x8048624, 0x8048599, 0x8048585, 0x8048670]
success_addr = 0x80486d3 # 成功路径地址
# 初始化状态
initial_state = project.factory.blank_state(addr=start_addr)
# 创建符号位向量
password_length = 32 # 32字节字符串
password = claripy.BVS("password", password_length * 8)
fake_password_address = 0xffffcc80 # 任意栈地址
# 存储符号位向量并设置eax
initial_state.memory.store(fake_password_address, password)
initial_state.regs.eax = fake_password_address
# 开始仿真
simulation = project.factory.simgr(initial_state)
simulation.explore(find=success_addr, avoid=avoid_addr)
# 输出结果
if simulation.found:
solution_state = simulation.found[0]
solution = solution_state.solver.eval(password, cast_to=bytes)
print("[+] Success! Solution is: {}".format(solution.decode("utf-8")))
else:
print("[-] Bro, try harder.")
if __name__ == '__main__':
main()
3. 处理符号内存和动态内存
05_angr_symbolic_memory
程序分析
- 接收4个8字节字符串输入
- 输入保存在:[0xA1BA1C0, 0xA1BA1C8, 0xA1BA1D0, 0xA1BA1D8]
- complex_function函数循环操作字符串
- 处理后字符串与"NJPURZPCDYEAXCSJZJMPSOMBFDDLHBVN"比较
- 匹配则打印"Good Job"
解决方案脚本
import angr
import claripy
import sys
def main():
path_to_binary = "05_angr_symbolic_memory"
project = angr.Project(path_to_binary)
# 从scanf后的指令开始
start_address = 0x8048601
initial_state = project.factory.blank_state(addr=start_address)
# 创建4个64位符号位向量
password0 = claripy.BVS('password0', 64)
password1 = claripy.BVS('password1', 64)
password2 = claripy.BVS('password2', 64)
password3 = claripy.BVS('password3', 64)
# 存储到内存地址
password0_address = 0xa1ba1c0
initial_state.memory.store(password0_address, password0)
initial_state.memory.store(password0_address + 0x8, password1)
initial_state.memory.store(password0_address + 0x10, password2)
initial_state.memory.store(password0_address + 0x18, password3)
# 开始仿真
simulation = project.factory.simgr(initial_state)
def is_successful(state):
stdout_output = state.posix.dumps(sys.stdout.fileno())
return b'Good Job.\n' in stdout_output
def should_abort(state):
stdout_output = state.posix.dumps(sys.stdout.fileno())
return b'Try again.\n' in stdout_output
simulation.explore(find=is_successful, avoid=should_abort)
if simulation.found:
solution_state = simulation.found[0]
solution0 = solution_state.solver.eval(password0, cast_to=bytes)
solution1 = solution_state.solver.eval(password1, cast_to=bytes)
solution2 = solution_state.solver.eval(password2, cast_to=bytes)
solution3 = solution_state.solver.eval(password3, 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__':
main()
06_angr_symbolic_dynamic_memory
程序分析
- 使用malloc分配两个9字节buffer
- 输入两个8字节字符串
- 循环8次处理字符串
- 每次迭代用complex_function"加密"字符串
- 处理后字符串与内置字符串比较
关键点
- 使用伪造地址代替malloc返回的地址
- 关系转换:
- BEFORE: buffer0 -> malloc地址0 -> 字符串0
- AFTER: buffer0 -> 伪造地址0 -> 符号位向量0
解决方案脚本
import angr
import claripy
import sys
def main():
path_to_binary = "./06_angr_symbolic_dynamic_memory"
project = angr.Project(path_to_binary)
# 从scanf后的指令开始
start_address = 0x8048699
initial_state = project.factory.blank_state(addr=start_address)
# 创建两个64位符号位向量
password0 = claripy.BVS('password0', 64)
password1 = claripy.BVS('password1', 64)
# 伪造堆地址和指针地址
fake_heap_address0 = 0xffffc93c
pointer_to_malloc_memory_address0 = 0xabcc8a4
fake_heap_address1 = 0xffffc94c
pointer_to_malloc_memory_address1 = 0xabcc8ac
# 存储伪造地址和符号位向量
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)
initial_state.memory.store(fake_heap_address0, password0)
initial_state.memory.store(fake_heap_address1, password1)
# 开始仿真
simulation = project.factory.simgr(initial_state)
def is_successful(state):
stdout_output = state.posix.dumps(sys.stdout.fileno())
return b'Good Job.\n' in stdout_output
def should_abort(state):
stdout_output = state.posix.dumps(sys.stdout.fileno())
return b'Try again.\n' in stdout_output
simulation.explore(find=is_successful, avoid=should_abort)
if simulation.found:
solution_state = simulation.found[0]
solution0 = solution_state.solver.eval(password0, cast_to=bytes)
solution1 = solution_state.solver.eval(password1, 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__':
main()
关键知识点总结
- 符号位向量创建:使用
claripy.BVS(name, size)创建符号变量 - 内存操作:
state.memory.store(address, value)存储值到内存- 动态内存可以伪造地址代替malloc返回地址
- 寄存器操作:
state.regs.<register> = value设置寄存器值 - 路径探索:
simulation.explore(find, avoid)探索路径- 可以通过输出内容判断成功/失败路径
- 解具体值:
state.solver.eval(symbol, cast_to=bytes)将符号值转为具体值
这些技术可以应用于各种逆向工程和漏洞分析场景,特别是需要自动化求解输入条件的情况。