初探利用angr进行漏洞挖掘(下)
字数 868 2025-08-24 16:48:07
利用angr进行漏洞挖掘:UAF与Double Free检测
前言
本文详细介绍了如何使用符号执行工具angr来检测Use-After-Free (UAF)和Double Free漏洞。这两种漏洞都是二进制安全中常见的堆漏洞类型,通过angr的符号执行能力可以自动化地发现这些漏洞。
漏洞类型概述
Use-After-Free (UAF)
UAF是指程序在释放内存后仍然继续使用该内存区域的漏洞,分为两种类型:
- UAF-R:重用已释放堆块进行读操作
- UAF-W:重用已释放堆块进行写操作
Double Free
Double Free是指对同一块堆内存执行两次free操作,这会导致内存管理数据结构损坏,可能被利用来实现任意代码执行。
示例代码分析
#include <stdio.h>
#include <stdlib.h>
char bss[0x10] = {0};
int main(int argc, char const *argv[]) {
char buf[0x10] = {0};
int times = 4;
unsigned long *ptr = &bss;
while(times--) {
puts("input:");
read(0, buf, 8);
switch(atoi(buf)) {
case 1:
puts("malloc!");
*ptr = malloc(0x30);
break;
case 2:
if(*ptr) {
puts("free!");
free(*ptr);
} else {
puts("fail to free");
return;
}
break;
case 3:
if(*ptr) {
puts("edit!");
read(0, *ptr, 8);
} else {
puts("fail to edit");
return;
}
break;
case 4:
if(*ptr) {
puts("show!");
write(1, *ptr, 8);
} else {
puts("fail to show");
return;
}
break;
}
}
return 0;
}
这是一个典型的堆漏洞示例程序,通过菜单选项可以实现:
- malloc分配内存
- free释放内存
- edit编辑内存内容
- show显示内存内容
angr检测方法
基本思路
- Double Free检测:通过hook malloc和free函数,记录每次分配和释放的堆地址,检测是否有重复释放的情况
- UAF检测:记录已释放的内存地址,监控内存读写操作,检测是否有对已释放内存的访问
Double Free检测实现
Hook malloc函数
from angr.sim_type import SimTypeTop, SimTypeLength
class malloc_hook(angr.procedures.libc.malloc.malloc):
def run(self, sim_size):
self.argument_types = {0: SimTypeLength(self.state.arch)}
self.return_type = self.ty_ptr(SimTypeTop(sim_size))
addr = self.state.heap._malloc(sim_size) # 申请得到的堆块地址
size = self.state.solver.eval(sim_size) # 申请得到的堆块大小
if "malloc_list" in self.state.globals:
malloc_list = self.state.globals["malloc_list"]
else:
self.state.globals["malloc_list"] = {}
malloc_list = self.state.globals["malloc_list"]
malloc_list[addr] = size # 以字典方式存储堆地址和大小
return addr
Hook free函数
class free_hook(angr.procedures.libc.free.free):
def run(self, ptr):
self.argument_types = {0: self.ty_ptr(SimTypeTop())}
f_ptr = self.state.solver.eval(ptr) # 要free的堆块地址
if "free_list" in self.state.globals:
free_list = self.state.globals["free_list"]
if f_ptr in free_list: # 检测是否已存在于free_list
print("double free:")
print("stdout:\n", self.state.posix.dumps(1))
print("stdin:\n", self.state.posix.dumps(0))
else:
self.state.globals["free_list"] = {}
free_list = self.state.globals["free_list"]
if "malloc_list" in self.state.globals:
malloc_list = self.state.globals["malloc_list"]
if f_ptr in malloc_list: # 确保只free已分配的内存
free_list[f_ptr] = malloc_list[f_ptr]
return self.state.heap._free(ptr)
UAF检测实现
UAF读检测 (UAF-R)
def Check_UAF_R(state):
if "free_list" not in state.globals:
# 第一次调用free前的处理
if "before_free" in state.globals:
before_free = state.globals["before_free"]
else:
state.globals["before_free"] = []
before_free = state.globals["before_free"]
# 记录当前action
action_now = reversed(state.history.actions.hardcopy)
for act in action_now:
if act not in before_free:
before_free.append(act)
else:
# 已经调用过free的情况
before_free = state.globals["before_free"]
action_now = reversed(state.history.actions.hardcopy)
# 获取新增的action
action = [i for i in action_now if i not in before_free]
malloc_list = state.globals["malloc_list"]
free_list = state.globals["free_list"]
for act in action:
if act.type == 'mem' and act.action == 'read':
addr = check_addr(state, act) # 检查访问地址
if addr == 0:
print("error addr:", act.addr)
break
for f in free_list:
if f == addr: # 检测是否访问了已释放内存
print("\n[========find a UAF read=======]")
print("[UAF-R]stdout:")
print(state.posix.dumps(1))
print("[UAF-R]trigger arbitrary read input:")
print(state.posix.dumps(0))
break
UAF写检测 (UAF-W)
def Check_UAF_W(state):
# 结构与UAF-R类似,主要区别是检测写操作
if "free_list" not in state.globals:
if "before_free" in state.globals:
before_free = state.globals["before_free"]
else:
state.globals["before_free"] = []
before_free = state.globals["before_free"]
action_now = reversed(state.history.actions.hardcopy)
for act in action_now:
if act not in before_free:
before_free.append(act)
else:
before_free = state.globals["before_free"]
action_now = reversed(state.history.actions.hardcopy)
action = [i for i in action_now if i not in before_free]
malloc_list = state.globals["malloc_list"]
free_list = state.globals["free_list"]
for act in action:
if act.type == 'mem' and act.action == 'write':
addr = check_addr(state, act)
if addr == 0:
print("error:", act.addr)
break
for f in free_list:
if f == addr: # 检测是否写入已释放内存
print("\n[========find a UAF write=======]")
print("[UAF-W]stdout:")
print(state.posix.dumps(1))
print("[UAF-W]trigger arbitrary write input:")
print(state.posix.dumps(0))
break
主程序框架
if __name__ == '__main__':
filename = "./heap1"
p = angr.Project(filename, auto_load_libs=False)
# Hook关键函数
p.hook_symbol('malloc', malloc_hook())
p.hook_symbol('free', free_hook())
# 设置分析选项
extras = {
so.REVERSE_MEMORY_NAME_MAP,
so.TRACK_ACTION_HISTORY,
so.ZERO_FILL_UNCONSTRAINED_MEMORY
}
state = p.factory.entry_state(add_options=extras)
simgr = p.factory.simulation_manager(state, save_unconstrained=True)
simgr.use_technique(angr.exploration_techniques.Spiller())
while simgr.active:
for act in simgr.active:
Check_UAF_R(act) # 检测UAF读
Check_UAF_W(act) # 检测UAF写
simgr.step()
去重优化
为了减少重复漏洞报告,可以采用编辑距离算法比较函数调用链的相似度,设置阈值(如3)来过滤过于相似的路径。
实际应用中的限制
- 路径爆炸问题:复杂程序会导致路径数量呈指数增长
- 内存消耗大:分析需要大量内存资源
- 适用场景有限:更适合CTF题目或简单程序分析
总结
本文详细介绍了使用angr检测UAF和Double Free漏洞的方法,包括:
- 通过hook关键函数记录内存操作
- 监控内存访问行为检测异常
- 完整的实现代码示例
虽然这种方法在实际复杂软件分析中存在限制,但对于CTF题目或简单程序的自动化漏洞挖掘仍有一定价值。