基础知识点
angr-documentation 这个文档描述已经很详细,我只做后续常用的笔记
angr.Project
初始化一个 Project,它是 angr 的核心对象,管理整个二进制分析流程
- 加载二进制文件:解析 ELF(或 PE、Mach-O 等格式),建立基础结构。
- 反汇编(可选):构建控制流图(CFG)或加载符号表。
- 加载函数、段落、地址空间等:为后续分析提供基础。
- 自动加载库(默认开启):比如 libc.so,除非你传了 auto_load_libs=False
.factory
- blank_state():创建一个空的状态,可以用来作为初始状态,没有前置执行(手动设置寄存器、内存)
state = project.factory.blank_state(addr=0x400000)
创建一个空状态(不执行 main),可以手动设置寄存器、栈、内存。适合特定函数分析或构造特殊状态
- entry_state():创建一个初始状态,指向程序入口点。
state = project.factory.entry_state()
从程序入口(main 前的 _start 或 libc 初始化)开始执行。适合从程序一开始完整符号执行
- call_state(addr, arg1, arg2, …)
state = project.factory.call_state(0x401000, arg1, arg2)
模拟函数调用,比如分析函数 foo(arg1, arg2),可以直接指定地址和参数。
- full_init_state():创建一个完整的初始状态,包括栈、内存、文件、网络等
state = project.factory.full_init_state(args=['./00_angr_find'])
- simulation_manager(state),simgr(state):创建一个模拟器,用于执行程序
simgr = project.factory.simulation_manager(state)
创建状态管理器(路径探索器),可以用 .explore()、.step() 等方式调度执行路径。
.explore()
- find=None:指定目标地址,探索器会在路径中找到这个地址,并停止
- num_find=None:指定探索器最多找到多少个目标地址
- avoid=None:指定探索器不应该进入的地址
- step_func=None:指定探索器的步进函数,默认是 project.factory.successors
还有其他参数,这里只列举常用的
simulation = project.factory.simulation_manager(init_state)
simulation.explore(find=0x80492E0)
simulation.found
是一个 SimState 对象的列表,包含所有成功到达 find 地址的程序状态
获取执行成功的符号
- .posix.dumps()
info = simulation.found[0].posix.dumps(0) # simulation.found[0]获取成功对象的第一个元素,0表示stdin,这里获取了stdin的完整输入
info = simulation.found[0].posix.dumps(1) # 1表示stdout,这里获取了stdout的完整输出
- .solver.eval() 暂时没用到,有用到补充
实战案例(angr_ctf)
1. 00_angr_find
获取目标地址0x80492E0
获取main函数地址0x8049232
,或使用entry_state()
import angr
project = angr.Project('./00_angr_find',auto_load_libs=False)
init_state = project.factory.blank_state(addr = 0x8049232)
simulation = project.factory.simgr(init_state) #创建模拟器 simgr是simulation_manager的缩写
simulation.explore(find=0x80492E0) #explore()方法用于执行模拟器,直到找到目标地址或达到最大步数
print(simulation.found[0].posix.dumps(0).decode())
2. 01_angr_avoid
IDA无法反编译,直接查看字符串,找到Good Job
int __cdecl maybe_good(char *s1, char *s2)
{
if ( should_succeed && !strncmp(s1, s2, 8u) )
return puts("Good Job.");
else
return puts("Try again.");
}
成功需要两个条件,一个是should_succeed
为真,另一个是strncmp(s1, s2, 8u)
返回真
先看看should_succeed
是否被其他函数改变
看到被
avoid_me
函数给设置为0了,再次查看avoid_me
函数的交叉调用,发现在main函数里有大量调用
这就是需要avoid的地址,为0x8049233
查看Good Job
的调用
查看Try again
的调用
有两个函数调用了Try again
,其中一个是complex_function
,在main函数里有一次调用,检查发现这个函数是必定执行的
但是有判断跳转语句,只要避免进入
Try again
所在语句就行,也就是0x80491E6
import angr
p = angr.Project('./01_angr_avoid')
state = p.factory.entry_state()
simgr = p.factory.simgr(state)
succ_addr = 0x804926B
avoid_addr = [0x80491E6,0x804927A,0x8049233]
simgr.explore(find=succ_addr, avoid=avoid_addr)
if len(simgr.found) > 0:
print("Found path: ")
for state in simgr.found:
print(state.posix.dumps(0))
else:
print("No path found")
代码理论是可行的,但是攻击用我自己的环境编译的程序无法打通,攻击angr_ctf给出的编译程序可以成功
包括我用angr_ctf给出的题解代码修改地址后也无法攻击成功自己的程序,所以后续我会全部使用angr_ctf给出的编译好的程序
3. 02_angr_find_condition
IDA打开,查找字符串找到Good Job
,查看交叉引用,发现有大量的引用,随便进入一个F5查看
发现只有如图短短几句
解题像上一题一样就行
import angr
p = angr.Project('./02_angr_find_condition')
state = p.factory.entry_state()
simgr = p.factory.simgr(state)
succ_addr = 0x804BA96
avoid_addr = [0x804BA81,0x8048592]
simgr.explore(find=succ_addr, avoid=avoid_addr)
if len(simgr.found) > 0:
print(simgr.found[0].posix.dumps(0))
else:
print("No path found")
接下来调查一下为什么有无数的引用,却只有短短几行有用,动调开始
首先为了过掉complex_function
的判断,需要输入8个大写字母,所以可以尝试输入ABCDEFGH
.text:0804869C cmp [ebp+var_38], 0DEADBEEFh
.text:080486A3 jz loc_804AC9C ;比较[ebp+var_38]是否等于0DEADBEEF
做逆向应该要知道cmp a,b的含义就是返回a - b,jz
就是jmp if zero,也就是如果比较结果为0,则跳转
类推jnz
和jne
就是jmp if not zero和jmp if not equal,也就是如果比较结果不为0,则跳转
查看[ebp+var_38]
的内容
查看看[ebp+var_38]
最初在哪被修改
在main函数一开始就已经给[ebp+var_38]
赋值了0DEADBEEF,所以这个条件一定会成立
也就是jz语句必定执行,再往后调试会发现所有的cmp都是与0DEADBEEF比较
也就是每逢jz语句可以视为jmp,每逢jnz语句直接无视,一步一步跟到最后
找到了真正有用的
Good Job
的引用