Skip to content

Commit bc18aed

Browse files
authored
Merge pull request #26 from excuses0217/main
add:wp of [GCCCTF 2025]crypto,law,reverse
2 parents 70f71fc + 9a1b585 commit bc18aed

12 files changed

+690
-0
lines changed

crypto/[GCCCTF 2025]伊莫鸡.md

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
## 基本信息
2+
3+
- 题目名称: [GCCCTF 2025]伊莫鸡
4+
- 考点清单: HTML实体编码、换表base64
5+
6+
## 一、看到什么
7+
8+
全是表情符号,还有一串64个不重复的字符表
9+
10+
## 二、想到什么解题思路
11+
12+
表情符号可以想到base100,64个不重复的字符表可能是换表base64的对应的表
13+
14+
## 三、尝试过程和结果记录
15+
16+
表情符号在网站上解密可以得到第一个字符串,base100
17+
给出两串字符串:
18+
19+
1. `lWEQ...;yW` —— HTML 实体编码。
20+
2. `a6pQqIUurcibPENJSlHxjZkydT3tgY4oFeDXL5mf1V+zR7wv9CnBWh/M2sOAG80K` —— 64 个字符且不重复。
21+
提示说明第二串为“非标准 Base64”,意味着它是**自定义 Base64 字母表**
22+
23+
**解题思路**
24+
25+
1. 首先将 HTML 实体转回字符,得到:
26+
`lWEQShlU4hgBPjP9xxEoEB6oELEQSBYUyBr9PXjeryW`
27+
2. 观察第二串长度正好 64,推测为自定义 Base64 字母表。
28+
标准 Base64 表为:`A–Z a–z 0–9 + /`
29+
3. 将自定义表的每个字符按索引映射回标准 Base64 表。
30+
4. 对第一串中字符逐一替换为对应标准 Base64 字符。
31+
5. 对替换结果做标准 Base64 解码,得到明文。
32+
33+
**关键代码**
34+
35+
```python
36+
import base64, html
37+
html_text = "lWEQShlU4hgBPjP9xxEoEB6oELEQSBYUyBr9PXjeryW"
38+
custom = "a6pQqIUurcibPENJSlHxjZkydT3tgY4oFeDXL5mf1V+zR7wv9CnBWh/M2sOAG80K"
39+
standard = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
40+
mapping = {custom[i]: standard[i] for i in range(64)}
41+
decoded = html.unescape(html_text)
42+
mapped = "".join(mapping.get(ch, ch) for ch in decoded)
43+
print(base64.b64decode(mapped + "=" * ((4 - len(mapped) % 4) % 4)).decode())
44+
```
45+
46+
**flag**
47+
48+
```
49+
GCCCTF{W31C0M3_70_6CCC7F_2025!!}
50+
```
51+

crypto/[GCCCTF 2025]密钥危机.md

Lines changed: 323 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 220 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,220 @@
1+
## 基本信息
2+
3+
- 题目名称: [GCCCTF 2025] constraint
4+
- 考点清单: 脱壳技术,约束求解
5+
6+
## 一、看到什么
7+
8+
这是一道综合性的Reverse题目,涉及壳识别、脱壳、静态分析和约束求解。题目给出一个Windows PE 64位可执行文件calme.exe。
9+
10+
## 二、想到什么解题思路
11+
12+
使用IDA Pro打开calme.exe,发现以下特征:
13+
14+
1. 程序段名为 **UPX0****UPX1****UPX2**
15+
2. 入口点函数非常复杂,包含大量位操作和解压逻辑
16+
3. 代码段中有大量0xFF填充
17+
18+
这些特征表明程序使用了 **UPX壳** 进行压缩。
19+
20+
## 三、尝试过程和结果记录
21+
22+
UPX是一个常见的可执行文件压缩工具,可以直接使用UPX工具进行解包:
23+
24+
```bash
25+
# 安装UPX(macOS)
26+
brew install upx
27+
28+
# 解包
29+
upx -d calme.exe -o calme_unpacked.exe
30+
```
31+
32+
**解包结果**
33+
34+
- 原始大小:137KB (140592 bytes)
35+
- 解包后:311KB (318256 bytes)
36+
- 压缩率:44.18%
37+
38+
解包成功后,在IDA Pro中打开`calme_unpacked.exe`进行分析。
39+
40+
### 静态分析
41+
42+
使用IDA Pro分析解包后的程序,找到两个关键函数:
43+
44+
#### main函数 (0x4016ee)
45+
46+
```c
47+
int main(int argc, const char **argv, const char **envp)
48+
{
49+
char Buffer[272];
50+
FILE *v3;
51+
52+
printf("Enter the flag: ");
53+
v3 = __acrt_iob_func(0);
54+
55+
if (!fgets(Buffer, 256, v3))
56+
return 1;
57+
58+
// 去除换行符
59+
size_t len = strlen(Buffer);
60+
if (len > 0 && Buffer[len-1] == '\n')
61+
Buffer[len-1] = '\0';
62+
63+
if (verify_flag(Buffer)) {
64+
puts("Correct!");
65+
return 0;
66+
} else {
67+
puts("Wrong!");
68+
return 1;
69+
}
70+
}
71+
```
72+
73+
#### verify_flag函数 (0x401560)
74+
75+
这是核心验证函数,包含多个约束条件:
76+
77+
```c
78+
bool verify_flag(char* a1)
79+
{
80+
unsigned char v5, v6, v7, v8, v9, v10, v11, v12;
81+
82+
// 1. 检查长度:必须为16字符
83+
if (strlen(a1) != 16)
84+
return false;
85+
86+
// 2. 检查前缀:必须是"GCCCTF{"
87+
if (memcmp(a1, "GCCCTF{", 7) != 0)
88+
return false;
89+
90+
// 3. 检查结尾:必须是'}'
91+
if (a1[15] != '}')
92+
return false;
93+
94+
// 4. 提取8个字节 (flag[7]到flag[14])
95+
for (int i = 0; i < 8; i++)
96+
*(&v5 + i) = a1[i + 7];
97+
98+
// 5. 线性方程组约束
99+
if (7*v5 + 3*v6 + 11*v7 + 13*v9 != 2145)
100+
return false;
101+
if (8*v6 + 12*v8 + 9*v10 + 4*v12 != 2491)
102+
return false;
103+
if (6*v5 + 5*v7 + 8*v11 + 7*v12 != 2299)
104+
return false;
105+
if (9*v8 + 11*v9 + 6*v10 + 10*v11 != 3165)
106+
return false;
107+
108+
// 6. 异或约束
109+
return (v10 ^ (v5 ^ v6 ^ v7 ^ v8 ^ v9)) == 95;
110+
}
111+
```
112+
113+
### 建立数学模型
114+
115+
从验证函数中提取出约束条件:
116+
117+
**变量定义**
118+
119+
- Flag格式:`GCCCTF{xxxxxxxx}`
120+
- 需要求解8个字节:v5, v6, v7, v8, v9, v10, v11, v12
121+
122+
**约束条件**
123+
124+
1. **线性方程组**(4个方程,8个未知数):
125+
126+
```
127+
7*v5 + 3*v6 + 11*v7 + 0*v8 + 13*v9 + 0*v10 + 0*v11 + 0*v12 = 2145
128+
0*v5 + 8*v6 + 0*v7 + 12*v8 + 0*v9 + 9*v10 + 0*v11 + 4*v12 = 2491
129+
6*v5 + 0*v6 + 5*v7 + 0*v8 + 0*v9 + 0*v10 + 8*v11 + 7*v12 = 2299
130+
0*v5 + 0*v6 + 0*v7 + 9*v8 + 11*v9 + 6*v10 + 10*v11 + 0*v12 = 3165
131+
```
132+
133+
2. **异或约束**
134+
135+
```
136+
v10 ^ (v5 ^ v6 ^ v7 ^ v8 ^ v9) = 95
137+
```
138+
139+
3. **字符范围约束**
140+
141+
- 所有变量必须是可打印ASCII字符(33-126)
142+
143+
### 编写求解脚本
144+
145+
使用Z3 SMT求解器来求解约束题目
146+
147+
```python
148+
#!/usr/bin/env python3
149+
"""
150+
CTF题目解题脚本
151+
使用Z3 SMT求解器来求解约束题目
152+
"""
153+
154+
from z3 import *
155+
156+
def solve_constraint_challenge():
157+
"""使用Z3求解约束系统"""
158+
159+
print("=== Constraint Solver ===")
160+
161+
# 创建Z3求解器
162+
solver = Solver()
163+
solver.set("timeout", 10000)
164+
165+
# 定义8个整数变量
166+
b = [Int(f'b{i}') for i in range(8)]
167+
168+
# 字符类型约束
169+
for i in range(8):
170+
solver.add(Or(
171+
And(b[i] >= 97, b[i] <= 122), # 小写字母
172+
And(b[i] >= 65, b[i] <= 90), # 大写字母
173+
And(b[i] >= 48, b[i] <= 57) # 数字
174+
))
175+
176+
# 线性约束(每个约束至少4个变量)
177+
solver.add(7*b[0] + 3*b[1] + 11*b[2] + 13*b[4] == 2145)
178+
solver.add(8*b[1] + 12*b[3] + 9*b[5] + 4*b[7] == 2491)
179+
solver.add(6*b[0] + 5*b[2] + 8*b[6] + 7*b[7] == 2299)
180+
solver.add(9*b[3] + 11*b[4] + 6*b[5] + 10*b[6] == 3165)
181+
182+
# XOR约束(使用位操作约束,确保XOR约束是必需的)
183+
# 为前6个字节的每一位创建布尔变量
184+
bits = [[Bool(f'bit_{i}_{j}') for j in range(8)] for i in range(6)]
185+
186+
# 约束:位变量组合成字节值
187+
for i in range(6):
188+
byte_val = Sum([If(bits[i][j], 2**j, 0) for j in range(8)])
189+
solver.add(b[i] == byte_val)
190+
191+
# XOR约束:每一位的XOR结果等于目标值的对应位
192+
target_xor = 0x5f
193+
target_bits = [bool(target_xor & (1 << j)) for j in range(8)]
194+
for j in range(8):
195+
bit_xor = bits[0][j]
196+
for i in range(1, 6):
197+
bit_xor = Xor(bit_xor, bits[i][j])
198+
solver.add(bit_xor == target_bits[j])
199+
200+
# 求解
201+
if solver.check() == sat:
202+
model = solver.model()
203+
flag_bytes = [model[b[i]].as_long() for i in range(8)]
204+
flag_content = ''.join(chr(b) for b in flag_bytes)
205+
return f"GCCCTF{{{flag_content}}}"
206+
else:
207+
return None
208+
209+
def main():
210+
flag = solve_constraint_challenge()
211+
if flag:
212+
print(flag)
213+
else:
214+
print("No solution found")
215+
216+
if __name__ == "__main__":
217+
main()
218+
```
219+
220+
flag: **GCCCTF{F14gH3re}**

web/[GCCCTF 2025]守法公民.md

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
## 基本信息
2+
3+
- 题目名称: [GCCCTF 2025]守法公民
4+
- 考点清单: js分析,越权漏洞
5+
6+
## 一、看到什么
7+
8+
进到题目是一个网络安全法知识问答平台,给出提示达到10000分就可以解锁大奖,但是每题才一分,要是直接答题肯定是来不及的。
9+
10+
先注册一个账号:
11+
12+
可以发现所有发送到后端的数据都是加密的,格式为:
13+
14+
```json
15+
{
16+
“data”:“xxxxxxxxxxxxxxxxxx”
17+
}
18+
```
19+
20+
## 二、想到什么解题思路
21+
22+
点开浏览器调试页面发现js代码混淆,我们要先分析加密逻辑。
23+
24+
## 三、尝试过程和结果记录
25+
26+
调试页面有反调试,可以通过“Nerver pause here”把这些debugger失效
27+
28+
![image-20251103105905177](images/[GCCCTF 2025]守法公民-1.png)
29+
30+
尝试重置密码,然后发现有一个函数名aesEncrypt,尝试在此打断点
31+
32+
![image-20251103110250612](images/[GCCCTF 2025]守法公民-2.png)
33+
34+
可以看到传入的数据是:
35+
36+
```
37+
{\"user_id\":\"8f599954593c4970b28dcac52ff9df71\",\"new_password\":\"1\",\"confirm_password\":\"1\"}
38+
```
39+
40+
可能是重置密码时需要带上本人id,以及重置的密码然后发送给后端,这里可能存在越权漏洞。
41+
42+
在这里发现了CryptoJS的加密部分:
43+
44+
![image-20251103110605697](images/[GCCCTF 2025]守法公民-3.png)
45+
46+
一般格式是这样的:
47+
48+
```
49+
const ciphertext = CryptoJS.AES.encrypt(data, key, {
50+
iv,
51+
mode: CryptoJS.mode.CBC,
52+
padding: CryptoJS.pad.Pkcs7
53+
});
54+
```
55+
56+
这里需要key,data,iv,mode,padding
57+
58+
对照混淆代码看哪里是,然后去console打印
59+
60+
_`_0x2d4799`对应data,_`_0x41afe6`对应key,`_0x304595`对应iv,`CryptoJS['mode']['CBC']`就是mode,`CryptoJS['pad'][_0x3a81be(0x166)]`是padding
61+
62+
依次去打印,注意iv和key是字节,可以base64编码一下再输出
63+
64+
![image-20251103111828675](images/[GCCCTF 2025]守法公民-4.png)
65+
66+
然后就可以据此构造加密数据包修改管理员密码,现在还差一个管理员的uuid才能构造。
67+
68+
想到公告栏是管理员发的,查看网页源代码,
69+
70+
![image-20251103112134612](images/[GCCCTF 2025]守法公民-5.png)
71+
72+
由此可以开始构造包,先抓一个修改密码的包解密:
73+
74+
```javascript
75+
JSON.parse(CryptoJS.AES.decrypt("ReZVa6ElTwQSlWYY5IlkbKE6VFBNyMc4KROEd1eVTV4rDn2Y1lc5LB6hOPv2w1YGqviCVP1kQJPI9dWxtfl1dkBeew5PcKzqDxexmT/HpAyH8vLrr2JBgTLJq5XyzPFO", CryptoJS.enc.Base64.parse("Qqk39LaSxFml8cwZLxFNCk1NXun6i2nXebB2rouRgNA="), {iv: CryptoJS.enc.Base64.parse("F1wyy3xQ42vTbm2JZ+6yow=="),mode: CryptoJS.mode.CBC,padding: CryptoJS.pad.Pkcs7}).toString(CryptoJS.enc.Utf8));
76+
```
77+
78+
然后修改uuid和password,构造以下加密包:
79+
80+
```javascript
81+
CryptoJS.AES.encrypt(JSON.stringify({user_id: '722e87fa398e4faeb228fe27d6b2a7a6', new_password: '2', confirm_password: '2'}), CryptoJS.enc.Base64.parse("Qqk39LaSxFml8cwZLxFNCk1NXun6i2nXebB2rouRgNA="), {iv: CryptoJS.enc.Base64.parse("F1wyy3xQ42vTbm2JZ+6yow=="), mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7}).toString();
82+
```
83+
84+
![image-20251103112508998](images/[GCCCTF 2025]守法公民-6.png)
85+
86+
把这个发过去:
87+
88+
fFtkxNNsnuawsN2sm453BBWFPrv5dBY/rNGZBu9w1xJ4G2jta74pxOIFOc607+rMM8D+OIrDcP6i16QCkjHfO2P1UkMc2j0rXJcOAWQ33irprAdmLjAFKdj2M+pfVPaz
89+
90+
![image-20251103112632563](images/[GCCCTF 2025]守法公民-7.png)
91+
92+
然后用 admin/2 登录
93+
94+
有一个分数设置,设置一个比较高的分数,然后随便答题就可以拿到flag了
95+
96+
![image-20251103112755005](images/[GCCCTF 2025]守法公民-8.png)
13.5 KB
Loading
87.4 KB
Loading
101 KB
Loading
61.2 KB
Loading
156 KB
Loading
59.7 KB
Loading

0 commit comments

Comments
 (0)