分析
- 题目很程序很简单,输入字符串,将遇到\x00前的字符拷贝给s2数组,如果拷贝数量大于s2在栈上的空间就会发生溢出,可以覆盖到ret的位置从而控制程序流。
- 需要注意的是覆盖ret用的地址肯定会包含\x00,因此构造的rop链会断开,所以需要想办法。
- 解决办法为通过一串pop ret指令将栈上内容弹出,然后直接连接到buf所在位置继续rop链的执行。
- 输入buf后栈空间如下所示,可以看出在ret指令的位置填入一个具有四个pop再加一个ret的地址刚好能弹出buf中已经用掉的4*0x8个字节,然后就能续上后面的rop链
栈 | |
---|---|
s2 | aaaaaaaa |
aaaaaaaa | |
ebp | ebp_ebp_ |
ret | pop4_ret |
buf | aaaaaaaa |
aaaaaaaa | |
ebp_ebp_ | |
pop4_ret | |
rop链 | |
… |
泄漏libc的方法
题目没有给libc.so文件,这里尝试过使用LibcSeacher查找libc,但是失败了,因此本题采用DynELF查找system的地址。DynELF的原理是从内存中读取不同地址的内容从而逐步定位到libc的加载地址,从而得到system的地址,因此适用面比较广。
这里需要注意的是,题目中不仅有write输出,还有printf输出,而printf会先输出到缓冲区,等缓冲区满了或遇到fflush函数或遇到回车符才输出,因此先执行的printf会比后执行的write函数后输出内容,这对我们recv()字符造成了一些麻烦,需要仔细处理。
因此本文的解决方法是在发送payload前不使用recvuntil()函数定位发送的时机,反正题目只有一个输出的点,不会出问题。在leak函数里面通过recvuntil()消耗掉printf的输出,避免下次recv(8)时读错内容。
另:DynELF的leak函数最好用write来泄漏地址,因为puts遇到\x00就截断了,而write不受限制。但write有3个输入参数,因此通过ret2csu的方法来构造参数以及调用write。
exp
from pwn import *
from LibcSearcher import *
import time
pe = "./welpwn"
arch = 'amd64'
context.update(arch=arch,os='linux',log_level='INFO')
DEBUG = False
elf = ELF(pe)
if DEBUG:
if arch=='amd64':
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
else:
libc = ELF('/lib/i386-linux-gnu/libc.so.6')
r = process(pe)
else:
#libc = ELF('./libc.so.6')
r = remote('220.249.52.134'