Pwnable.tm题解
simple pwn
Pwnable.tw题解
最近重建了博客,放了一下之前写的,通过科学上网体验更佳,有些图我用Twitter做的外链
start
这道题目网上有很多的题解,但是基本都没有说的很清楚,所以我决定以非常入门,非常清晰的思路去讲解一下这道Pwn
的入门题目:
1 | public _start |
直接来上汇编代码吧,这其中有三个系统调用,并且没有main
函数,所以应该是内联汇编写的,大概翻译一下就是:
1 | //read 和 write 系统调用都是采用中断的方式调用的 |
这个时候的栈结构大概就是(至于为什么是这样就不解释了):
write
系统调用的作用就是把当前栈顶的数据给打印出来,bin
里的调用打印长度为0x14
,这也就刚好和我们看到的对上了,但是下面的read
系统调用却可以读进0x3c
个字节,显然会造成一个栈溢出,所以首先我们要做的就是leak
基址
因为write
可以打印栈顶0x14
的数据,所以应该劫持控制流到这个函数的位置,所以我们第一步构造的payload
应该为
1 | # 'A'*20是因为有add esp,0x14,sys_write_addr可以从ida中获取 |
这样当继续执行汇编指令到add esp,0x14
是,此时esp
就指向原来的exit
,此时已经被sys_write_addr
覆盖,然后执行ret
,将栈顶弹给eip
,相当于跳回了:
1 | mov ecx, esp ; addr <-------jmp_addr |
因为ret
相当于pop esp
,所以此时esp
应该指向最早push
的那个esp
,通过gdb-peda
调试我们可以发现最早push
进栈的那个esp
中存储的是自己下面的那个数的地址,此刻的栈状况应该为:
然后执行sys_write
,就会打印出来:
1 | //这里的esp为最早push的esp值,指向0x1 |
接着又会进入到sys_read
了,一定要记住一点,sys_read
也是从当前栈顶开始读入,因为我在看其他人的题解的时候就发现有人犯了这样的错误,认为接下来的read
还是从原来的栈顶开始读入,参考连接:
CSDN上还有一些其他题解,要么就是没讲到这个点上,要么就是说的是错的,所以在这里我着重强调了一下
接下来我们又有一次输入的机会,因为同样有add esp, 0x14
,所以同样要构造'A' * 20
,接下来应该是shellcode
的地址,也就是一个指向自身地址+0x4
的值,而esp
刚好满足这个条件,但是还要根据offset
调整一下,我画张图应该就能理解了:
我自己假设了一些地址,就是为了更方便理解,这样子我相信大家应该都不会有什么疑惑了,顺带提一点,我们的shellcode
长度不能超过0x3c-0x14-0x4
然后放一下我的poc吧:
1 | from pwn import * |