[AWDP-PWN] patch技巧
前言
备赛ing,顺手写一篇博客汇总一下AWDPfix的笔记
patch技巧
1.整数溢出
原理:
一般情况下,整数溢出漏洞是在传参时用unsigned int而判断时用int,二者取值范围不同
| 类型 | 位数 | 取值范围 | 上限的十六进制 |
|---|---|---|---|
unsigned int | 32 | 0 ~ 4294967295 | 0xFFFFFFFF |
int | 32 | -2147483648 ~ 2147483647 | 0x7FFFFFFF |
这就导致了以下情况的出现:unsigned int 传参输入 4294967295 → int 判断时转化为-1 → 判定通过 → 实现整数溢出漏洞利用
或者还有
scanf的格式符与变量类型不匹配
例如:
unsigned int n; // 4 字节scanf("%ld", &n); // %ld 要求 long*,x86-64 上 long 是 8 字节这里有两类漏洞:
1.使用无符号整型的变量n传参,但是格式符 %ld 能接受负数 → 存在整数溢出漏洞
2.宽度问题:%ld 按 8 字节写入,而 &n 只指向 4 字节的对象。结果就是多写的 4 字节会落到 &n 高地址方向的相邻栈空间,能够实现溢出或者覆盖相邻内存空间的数据而达到攻击效果
patch:
在if判断函数对应的汇编语言下,将有符号跳转指令改为无符号跳转指令; 对于格式符与变量类型的问题,则修改对应参数使其与变量类型匹配 (还是要根据实际漏洞来进行修改,否则不一定符合原程序逻辑)。
如:JLE → JBE (有符号小于等于跳转改为无符号小于等于跳转)
附跳转汇编指令表:
无符号跳转指令
| 汇编指令 | 描述 |
|---|---|
| JA | 无符号大于则跳转 |
| JNA | 无符号不大于则跳转 |
| JAE | 无符号大于等于则跳转(同JNB) |
| JNAE | 无符号不大于等于则跳转(同JB) |
| JB | 无符号小于则跳转 |
| JNB | 无符号不小于则跳转 |
| JBE | 无符号小于等于则跳转(同JNA) |
| JBNE | 无符号不小于等于则跳转(同JA) |
有符号跳转指令
| 汇编指令 | 描述 |
|---|---|
| JG | 有符号大于则跳转 |
| JNG | 有符号不大于则跳转 |
| JGE | 有符号大于等于则跳转(同JNL) |
| JNGE | 有符号不大于等于则跳转(同JL) |
| JL | 有符号小于则跳转 |
| JNL | 有符号不小于则跳转 |
| JLE | 有符号小于等于则跳转(同JNG) |
| JNLE | 有符号不小于等于则跳转(同JG) |
2.栈溢出
原理:
使用了gets()、read()这类无长度检查或者长度检查过于宽松的函数
patch:
x64下这类函数函数是通过寄存器传参的,因此将第三个参数(即读入大小)patch成一个无法溢出的大小即可;
(32位的题比赛应该比较少见,但是还是写一下吧)x86下则为:找到size对应的压栈指令,将压入的值patch为一个无法溢出的大小
3.格式化字符串漏洞
原理:
程序中存在着格式化字符串输出函数,如printf。如果使用printf时只有格式化字符串参数,没有后续的参数1、参数2等,并且格式化字符串参数可控,就可以触发任意地址读写的漏洞。
patch:
1.简单情况下可以将call printf 直接patch为 call puts(但是由于puts会在字符串基础上加上一个\n,存在check过不了的可能)
2.将 printf(format) patch为 printf("%s",format):
1)利用eh_frame段(其他可读的段空闲空间应该也行)写入格式符(如%s)
2)修改汇编代码:一般手写的printf函数编译后前面总是有个(删了之后不影响程序的)mov eax,0指令,可以靠它修改
常常出现nop掉原参数后patch时导致字节空间不够的情况,这个时候就需要灵活调整一下汇编指令的位置,删掉一些不必要的汇编指令(比如上面说的mov eax,0)
另外,遇到开启PIE的时候,不好直接写格式符进rdi里面,可以按以下步骤来:
先把第一行汇编patch为lea edi,[rip]
然后把回车后变出来的loc_()括号内的偏移地址和你在可用段上修改的%s处地址计算偏移,出来的偏移地址记为offset。把第一行重新patch为lea edi,[rip+offset],回车后printf第一个参数的位置就修改成功了。
然后第二个参数灵活按照原函数引用的参数来写入(以buf为例):mov rsi,dword ptr [buf]
*重点:一定要算好nop掉的前面原来地址的字节数长度足够写入,不然空间凑不出来会挤掉后面的call _printf!
4.堆溢出
原理:
程序在修改堆块内容时,写入的字节比原来malloc申请的字节更多,因此攻击者可以此实现控制相邻堆块的数据等操作
patch:
修改edit时的size大小,让size始终保持原来申请堆块时的size即可
5.UAF
原理:
调用free后指针未置零从而产生漏洞
patch:
在call free后添加指针置零的指令
这里分为两种情况:
1)call free后到下一条有效汇编指令的字节空间长度足够容纳指针置零指令且不影响原程序功能时(比如原本就有个非目标内存块指针置零操作指令,不影响原程序功能;或者是存在其他对齐填充功能之类可被替换的冗余指令等),就能在原有指令的基础上直接修改
2)call free后到下一条有效汇编指令的字节空间不足以写入指针置零指令时,就需要把call free改为JMP func_addr,使其跳转到.eh_frame段的自定义汇编代码上。然后从对应.eh_frame空闲段起始地址(即func_addr)开始,构造以下汇编代码:
call free ;调用free@pltmov/lea rax, [rip+heap_ptr] ;取heap[0]地址放进raxmov qword ptr [rax+rbx*8], 0 ;将对应free掉的堆块指针置零jmp next_one_addr ;跳转回原来地方的下一条汇编地址注意:
1.第二条汇编指令里的mov/lea如何取缔:如果程序把heap_ptr设置为存放堆基址的变量(void **heap_ptr),用mov;如果程序里使用的是如void *heap[16]这类内联数组,那么就用lea
2.第三条汇编指令里的rbx仅在程序把heap_idx存进rbx寄存器的情况下才符合,实际情况以对应题目idx赋值处为准
还有一种方法:直接改原程序汇编,把存放数组元素地址的寄存器从 rax 改为 rbx(rax 会被 call free 清掉,rbx 是 callee-saved 不会被破坏),并在 call free 后直接对 rbx 执行 mov qword ptr [rbx], 0 完成置零。
示例:


6.off by one
原理:
1.字符串操作问题:
主要涉及这几种关键函数/运算符上:
1)strlen返回字符串长度不含\0,如果直接用其分配长度后与strcpy连用,就会存在\0单字节越界的情况,如:
char *p = malloc(strlen(s)); // 只够放 x 个字符strcpy(p, s); // 但 strcpy 要写 x+1 字节(含 \0)→ 越界 1 字节2)sizeof取满未-1,在字符串末尾多一个\0
3)strcpy、strncpy、memcpy等一系列与复制/移动具体内容有关的函数,都可能存在off by one的风险
2.if循环边界判断符号取等(如<写成<=)导致多执行一次,实现越界读/写
patch:
核心都是改比较符或比较常量(jle→jl或 cmp 比较数-1)
通防
通防工具:TTY-flag/evilPatcher (sandbox通防)
使用指令:python3 evilPatcher.py <待修补的ELF程序> <沙箱规则文件.asm>
结语
暂时先总结这么多,有新的patch技巧会不定期更新(
文章分享
如果这篇文章对你有帮助,欢迎分享给更多人!


