初探利用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是指程序在释放内存后仍然继续使用该内存区域的漏洞,分为两种类型:

  1. UAF-R:重用已释放堆块进行读操作
  2. 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检测方法

基本思路

  1. Double Free检测:通过hook malloc和free函数,记录每次分配和释放的堆地址,检测是否有重复释放的情况
  2. 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)来过滤过于相似的路径。

实际应用中的限制

  1. 路径爆炸问题:复杂程序会导致路径数量呈指数增长
  2. 内存消耗大:分析需要大量内存资源
  3. 适用场景有限:更适合CTF题目或简单程序分析

总结

本文详细介绍了使用angr检测UAF和Double Free漏洞的方法,包括:

  1. 通过hook关键函数记录内存操作
  2. 监控内存访问行为检测异常
  3. 完整的实现代码示例

虽然这种方法在实际复杂软件分析中存在限制,但对于CTF题目或简单程序的自动化漏洞挖掘仍有一定价值。

利用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操作,这会导致内存管理数据结构损坏,可能被利用来实现任意代码执行。 示例代码分析 这是一个典型的堆漏洞示例程序,通过菜单选项可以实现: malloc分配内存 free释放内存 edit编辑内存内容 show显示内存内容 angr检测方法 基本思路 Double Free检测 :通过hook malloc和free函数,记录每次分配和释放的堆地址,检测是否有重复释放的情况 UAF检测 :记录已释放的内存地址,监控内存读写操作,检测是否有对已释放内存的访问 Double Free检测实现 Hook malloc函数 Hook free函数 UAF检测实现 UAF读检测 (UAF-R) UAF写检测 (UAF-W) 主程序框架 去重优化 为了减少重复漏洞报告,可以采用编辑距离算法比较函数调用链的相似度,设置阈值(如3)来过滤过于相似的路径。 实际应用中的限制 路径爆炸问题 :复杂程序会导致路径数量呈指数增长 内存消耗大 :分析需要大量内存资源 适用场景有限 :更适合CTF题目或简单程序分析 总结 本文详细介绍了使用angr检测UAF和Double Free漏洞的方法,包括: 通过hook关键函数记录内存操作 监控内存访问行为检测异常 完整的实现代码示例 虽然这种方法在实际复杂软件分析中存在限制,但对于CTF题目或简单程序的自动化漏洞挖掘仍有一定价值。