PWN部分

关于 pwn 的修复这个东西我们这里考虑两种场景,第一个给了源码时的修复,第二个是没给源码时的修复

有源码时的基础修复

先考虑有源码时的修复,这个其实很好操作,主要是熟练 gcc g++ 的各种指令,还有就是要能快速发现漏洞点,由于 pwn 题中经常是溢出类题型和 UAF 类题型偏多,所以要分别考虑这两种情况,先看有码的溢出类题型(要非常注意 strcat strcpy 等会造成 off by null 的函数

1
2
3
4
5
int main(){
char buf[24];
scanf("%s", buf);
return 0;
}

像这种那肯定是改 %s 为 %23s,不能有更长的了,或者适当增扩 buf 的大小,而且它方法也是类似的,有源码的情况下那可太好修复了

无源码时的程序修改

那么无源码应该怎么修复?这似乎就是 pwn 里面的 patch 了

先列出来几张表,下面是 jmp 的相关指令,对于负数等情况需要用到

指令 机器码 指令 机器码
jmp EB XX jz 74 XX
je 74 XX jne 75 XX
jg 7F XX jge 7D XX
jl 7C XX jle 7E XX
ja 77 XX jae 73 XX
jb 72 XX jbe 76 XX
jna 76 XX jnb 73 XX
jnae 72 XX jnc 73 XX
jnb 73 XX jng 7E XX
jnge 7C XX jnl 7D XX

canary修复方案

不溢出的时候调用 __stack_chk_fail,溢出的时候不调用:这是一个逆向思维,一般人对于canary的印象都是

UAF修补方法

1
2
3
mov qword ptr [rax+18h], 0
`mov eax, 0`改成`xor eax, eax`可以节省出2个字节
add rax, rdx ; mov rdx, [rax]`,改成`mov rdx, [rax+rdx]

Patch后上传压缩文件指令

1
tar -zcvf fix.tar.gz *

对于 scanf(“%s”) 我们应该如何 patch?

第一个是利用 eh_frame 段,这个段在程序正常运行的时候一般用不到,但是它是被赋予了 X 权限的,也就是说可执行,你比如说对于这个

pwn-awd知识库/image-20251010105737300

这里 call 了 __isoc99_scanf 的 PLT,那么我们在 eh_frame 上自己写一个函数,让它 call 到我们自己的函数上去,而我们自己的函数我们就严格限制输入大小,注意这里传入的第二个参数为缓冲区地址

我们这里非常简单暴力,自己通过 syscall 来输入,然后我们去修改 eh_frame,编写如下脚本用于生成字节码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from pwn import *

context.arch = 'amd64'

code = '''
xor rdi, rdi
mov rdx, 23
xor rax, rax
syscall
ret
'''

for i in list(asm(code, arch='amd64')):
print(hex(i)[2:].rjust(2, '0'), end = ' ')
print()

然后去修改 call scanf

Alt text

最后,修改 call scanf 到 call eh_frame 上来,由于这里我们开了 PIE,所以需要 call 一个相对坐标

先看看原本的地方

Alt text

我们需要把这个函数除了对我们有用的部分全部劫持到我们在 eh_frame 上的代码,多余的部分全部改为 nop

Alt text

至于这个 0xec5 怎么来的,首先,lea 的长度为 7,所以 lea 的下一条指令的地址是 0x118b,使用 0x2050 - 0x118b 得到 0xec5

现在雀食是可以跳转过来执行了,但是还有最后的问题,即 eh_frame 没有执行权限,这个我们通过 IDA 修改 ELF 的头来实现

Alt text

Type 一定得是 LOAD,而我们的补丁打在第三个 LOAD 里,所以我将 Flags 修改为了 7,当然啊,这样做其实是非常不负责的,但是做题嘛,怎么快怎么来,如果想要负责的话,那就得加一个 LOAD,并且还需要处理好 PIE,至少现在是正常跑起来了也不会溢出

Alt text

1
2
3
4
5
6
7
8
9
10
11
12
13
from pwn import *

context.arch = 'amd64'

code = '''
lea r15, [rip + 0xec5]
lea rsi, [rbp - 0x20]
call r15
'''

for i in list(asm(code, arch='amd64')):
print(hex(i)[2:].rjust(2, '0'), end = ' ')
print()

练习源码文件

1
2
3
4
5
6
7
8
#include<stdio.h>

int main(){
char buf[24];
scanf("%s", buf);
printf(buf);
return 0;
}

改printf为puts,如果程序中没有puts的话

修 printf,这里我们通过篡改 printf 为 puts 来实现,但是源程序里并没有 puts,所以需要更改 dyn

我们找到 printf 的 dynsym

Alt text

我们去修改 printf 为 puts

Alt text

然后 patch,再次打开程序的时候可以发现就变成 puts 了

Alt text

堆题的一些通用修复

我们都知道堆题高度依赖于 free 函数,如果说 malloc 等函数的不正确使用是造成漏洞的主要原因(比如说溢出就属于长度没控制好),那 free 就是触发漏洞的关键,当然了,还有别的触发方式,我们这里不深入

那么我们如何修复 free 呢?很简单啊, nop 掉就完事了,这样的做法有时候在比赛中比较管用,但也不是啥时候都管用,因此我们还有另外几种方案

nop free**

因为现在的 PWN 题运行过程动不动就有个 5s 以上(比如说要打 IO 爆破),而 checker 的运行只有短短 1~5 秒,那么为什么不从 alarm 上下手呢?

修改alarm函数的时间差

alarm 函数会在时间到了以后强制结束进程,那么我们可以 patch 程序最开始的 alarm(60) 为 **alarm(3)**,这样就有可能造成 checker 通过而 exp 不通过,从而通过 check 逻辑