AWDP中uaf的patch研究
字数 2887
更新时间 2026-03-23 16:06:51
AWDP中UAF漏洞的Patch研究教学文档
一、 背景与目标
本文档基于一篇关于AWDP(Attack-Defense CTF比赛)中针对Use-After-Free(UAF)漏洞进行Patch(补丁)研究的文章,旨在详细讲解对存在UAF漏洞的二进制程序进行人工修补的思路、方法与具体步骤。教学目标是使读者能够掌握在汇编层面定位、分析并修复UAF漏洞的技能。
二、 漏洞程序分析
- 目标程序:以一道CTF题目(SWPUCTF_2019_p1KkHeap)为例进行分析。题目链接可通过文内百度网盘获取。
- 漏洞定位:通过逆向分析,在函数
sub_FD1()中发现了UAF漏洞。 - 漏洞代码:
int sub_FD1() { unsigned __int64 index; // 用户输入的索引值 ... printf("id: "); index = (int)sub_1076(); // 读取用户输入的索引 if ( index > 7 ) sub_E04(); free(*((void **)&heaplist + index)); // 释放堆块 sign[index] = 0; // 将标志位清零 --count; return puts("Done!"); } - 漏洞成因:该函数释放了
heaplist数组中指定索引指向的堆块,但并未将数组中存储该指针的槽位(即heaplist[index])本身清零。这导致该指针成为了一个“悬垂指针”,后续如果程序错误地再次使用了这个指针,就会引发Use-After-Free。
三、 Patch的核心目标
Patch的目的不是去清零已被释放的堆块内存内容,而是将存储该堆块指针的变量(即 heaplist[index])置为NULL,从而消除悬垂指针,从根本上杜绝UAF的发生。
四、 关键汇编语法知识(Intel风格)
在进行汇编Patch前,必须理解以下关键语法,这些是阅读和修改IDA Pro反汇编代码的基础:
- 操作数顺序:
mov dst, src,目的操作数在前,源操作数在后。 - 内存访问:中括号
[]表示访问该地址处的内存值。mov rax, [rbp+index]:计算地址rbp+index,取出该地址处的值,放入rax。这是在读取栈上的变量。
- 取有效地址指令
lea:lea用于计算地址,并不访问该地址的内存。lea rdx, ds:0[rax*4]:计算表达式rax*4的结果,存入rdx。这常用于计算数组元素的偏移(index * sizeof(int))。lea rax, sign:将全局数组sign的首地址存入rax。这与mov rax, [sign](取出sign第一个元素的值)截然不同。
- 操作数大小指定:
dword ptr(4字节)、qword ptr(8字节) 等用于明确内存操作的大小。mov dword ptr [rdx+rax], 0:向地址rdx+rax处写入一个4字节的0。此例中即sign[index] = 0。
- 通用地址表达式:
[base + index*scale + disp]base:基址寄存器index:索引寄存器scale:缩放因子(1, 2, 4, 8)disp:常量偏移- 此结构是汇编中实现数组访问(
array[i])的核心。
- 全局变量访问:
mov eax, cs:count表示读取全局变量count的值到eax。cs:是段前缀,理解时可忽略,重点在于识别是对全局变量的操作。 - 函数调用约定(64位Linux):遵循System V AMD64 ABI,函数第一个参数通过
rdi寄存器传递。因此,在调用free()之前,rdi寄存器中存放的值就是要释放的堆块地址。
五、 Patch实施步骤详解
-
定位与备份:
- 在IDA中打开目标二进制文件,定位到存在漏洞的
free()调用所在的汇编代码位置。 - 强烈建议在修改前,备份原始的汇编指令,以便出错时恢复。
- 在IDA中打开目标二进制文件,定位到存在漏洞的
-
分析上下文与寄存器状态:
- 使用GDB在
free调用处设断点,运行程序并输入一个测试值(建议为非零值,便于观察)。 - 观察此时各寄存器的状态,特别是:
rdi:存放要释放的堆块地址(即heaplist[index]的值)。- 哪个寄存器或内存位置存放着
heaplist数组的基地址。 - 哪个寄存器存放着用户输入的
index值。
- 目标:理清如何通过
index计算出heaplist数组元素heaplist[index]的地址。
- 使用GDB在
-
设计Patch代码:
- 核心任务:在
free调用之后,插入汇编指令,将heaplist[index]这个内存单元(通常是一个8字节的指针)清零。 - 这通常需要:
- 获取
heaplist基地址。 - 根据
index计算偏移(index * 8,因为是指针数组)。 - 将计算得到的地址处的8字节内容置为0(
mov qword ptr [healist_base + index*8], 0)。
- 获取
- 核心任务:在
-
写入Patch:
- 在IDA的汇编视图中,找到
free调用之后的合适位置(通常是在sign[index] = 0操作附近,但要确保逻辑正确)。 - 利用IDA的
Edit -> Patch program -> Assemble功能,写入设计好的汇编指令。 - 注意:IDA的汇编器对语法要求严格,可能需要多次尝试。确保指令格式正确,特别是
qword ptr和[]的使用。如果直接输入mov [rdx+rax], 0可能无法识别,需要写成mov qword ptr [rdx+rax], 0。
- 在IDA的汇编视图中,找到
-
空间处理与代码压缩:
- 插入的Patch代码需要占用空间。原程序的代码段可能没有预留空白区域。
- 策略一(压缩):尝试优化、压缩原有的非关键功能代码(例如一些非核心的逻辑判断、输出字符串等),为Patch腾出空间。目标是使修改后的代码块总长度不变或巧妙利用原有的指令间隙。
- 策略二(替换):如果空间确实紧张,可以寻找程序中未被使用或可被替代的函数(例如一些无关紧要的辅助函数或“漏洞函数”),将其指令替换为
nop或直接改写为我们需要的Patch代码,并修改调用逻辑。
-
验证与测试:
- Patch完成后,在IDA中按
F5查看反编译的伪代码,确认heaplist[index]在free后被正确置零。 - 将修改后的程序保存到新文件。
- 运行测试用例,验证UAF漏洞是否已被成功修补(例如,尝试再次使用被释放的指针应导致崩溃或返回空指针,而非实现利用)。
- Patch完成后,在IDA中按
六、 总结与技巧
- 核心思想:Patch的目标是消除悬垂指针,而非处理已释放的内存。
- 重点难点:在于精确计算目标指针的存储地址,这需要对程序的数据结构(全局数组布局)和汇编层次的地址计算有清晰理解。
- 空间策略:将Patch代码设计得尽可能紧凑,并优先考虑压缩原有非核心代码来获取空间。在AWDP这类比赛中,Patch检测通常不严格,合理利用
nop指令或未用函数空间是常用技巧。 - 调试是关键:务必使用GDB动态调试,观察运行时寄存器和内存的值,这是确保Patch逻辑正确的唯一可靠方法。不要仅仅依赖静态分析。
相似文章
相似文章