WMCTF 2025

WMCTF 2025

Aristore

Misc

phishing email

Challenge

某公司安全工程师告警捕获一起邮件钓鱼样本,请帮助排查分析

A security engineer from a certain company has detected a phishing email sample. Please assist in investigation and analysis.

百度网盘:https://pan.baidu.com/s/1L1jBvqcpEI03uBlYsm0KAQ?pwd=ywr7

Google Dirve: https://drive.google.com/file/d/1U9RRjLNT6YRbHcXUesbPXL2a591bSX4m/view?usp=drive_link

Solution

从邮件附件得到以下 XML 代码:

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
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 800 600" width="800" height="600">
<!-- Fake invoice design to look legitimate -->
<rect width="800" height="600" fill="#f8f9fa"/>
<rect x="50" y="50" width="700" height="500" fill="white" stroke="#dee2e6" stroke-width="2"/>

<!-- Header -->
<text x="400" y="100" text-anchor="middle" font-family="Arial" font-size="24" font-weight="bold" fill="#212529">INVOICE #2025-0727</text>
<text x="400" y="130" text-anchor="middle" font-family="Arial" font-size="14" fill="#6c757d">Payment Due: August 15, 2025</text>

<!-- Company info -->
<text x="80" y="180" font-family="Arial" font-size="16" font-weight="bold" fill="#495057">From:</text>
<text x="80" y="200" font-family="Arial" font-size="14" fill="#495057">TechSolutions Corp</text>
<text x="80" y="220" font-family="Arial" font-size="14" fill="#495057">123 Business Ave</text>
<text x="80" y="240" font-family="Arial" font-size="14" fill="#495057">New York, NY 10001</text>

<text x="400" y="180" font-family="Arial" font-size="16" font-weight="bold" fill="#495057">To:</text>
<text x="400" y="200" font-family="Arial" font-size="14" fill="#495057">Your Company</text>
<text x="400" y="220" font-family="Arial" font-size="14" fill="#495057">456 Client Street</text>
<text x="400" y="240" font-family="Arial" font-size="14" fill="#495057">Boston, MA 02101</text>

<!-- Invoice details -->
<line x1="80" y1="280" x2="720" y2="280" stroke="#dee2e6" stroke-width="2"/>
<text x="80" y="310" font-family="Arial" font-size="14" font-weight="bold" fill="#495057">Description</text>
<text x="500" y="310" font-family="Arial" font-size="14" font-weight="bold" fill="#495057">Amount</text>

<text x="80" y="340" font-family="Arial" font-size="14" fill="#495057">IT Security Consultation</text>
<text x="500" y="340" font-family="Arial" font-size="14" fill="#495057">$2,500.00</text>

<text x="80" y="370" font-family="Arial" font-size="14" fill="#495057">Network Assessment</text>
<text x="500" y="370" font-family="Arial" font-size="14" fill="#495057">$1,800.00</text>

<line x1="450" y1="400" x2="720" y2="400" stroke="#dee2e6" stroke-width="1"/>
<text x="500" y="430" font-family="Arial" font-size="16" font-weight="bold" fill="#495057">Total: $4,300.00</text>

<text x="400" y="480" text-anchor="middle" font-family="Arial" font-size="12" fill="#dc3545">
Please click to view detailed payment instructions
</text>

<!-- Hidden malicious script with multiple layers of obfuscation -->
<script><![CDATA[
// Anti-debugging and detection evasion
var jXKuzdDMGk = false;
var detectionBypass = true;
var globalSeed = 0x5A4D;
var entropy = [];

// Advanced fingerprinting and detection evasion
(function antiDetection() {
// Check for WebDriver, PhantomJS, Burp Suite
if (navigator.webdriver || window.callPhantom || window._phantom ||
navigator.userAgent.includes("Burp") || navigator.userAgent.includes("HeadlessChrome") ||
navigator.userAgent.includes("Selenium") || window.chrome && chrome.runtime && chrome.runtime.onConnect) {
window.location = "about:blank";
return;
}

// Advanced environment fingerprinting
var canvas = document.createElement('canvas');
var ctx = canvas.getContext('2d');
ctx.textBaseline = 'top';
ctx.font = '14px Arial';
ctx.fillText('Browser fingerprint test', 2, 2);
var fingerprint = canvas.toDataURL();

// Generate entropy from browser characteristics
entropy = [
navigator.hardwareConcurrency || 4,
screen.colorDepth,
screen.pixelDepth,
new Date().getTimezoneOffset(),
fingerprint.length,
navigator.language.length,
window.devicePixelRatio * 1000 | 0
];

// Check for debugging environment indicators
if (window.outerHeight - window.innerHeight > 200 ||
window.outerWidth - window.innerWidth > 200 ||
fingerprint.length < 100) {
detectionBypass = false;
}

// Generate seed from entropy
globalSeed = entropy.reduce(function(acc, val) {
return ((acc << 5) - acc + val) & 0xFFFFFFFF;
}, 0x5A4D);
})();

// Block developer tools shortcuts
document.addEventListener("keydown", function (event) {
var blockedKeys = [
{ keyCode: 123 }, // F12
{ ctrl: true, keyCode: 85 }, // Ctrl + U
{ ctrl: true, shift: true, keyCode: 73 }, // Ctrl + Shift + I
{ ctrl: true, shift: true, keyCode: 67 }, // Ctrl + Shift + C
{ ctrl: true, shift: true, keyCode: 74 }, // Ctrl + Shift + J
{ ctrl: true, shift: true, keyCode: 75 }, // Ctrl + Shift + K
{ meta: true, alt: true, keyCode: 73 }, // Cmd + Alt + I (Mac)
{ meta: true, keyCode: 85 } // Cmd + U (Mac)
];

var isBlocked = blockedKeys.some(function(key) {
return (!key.ctrl || event.ctrlKey) &&
(!key.shift || event.shiftKey) &&
(!key.meta || event.metaKey) &&
(!key.alt || event.altKey) &&
event.keyCode === key.keyCode;
});

if (isBlocked) {
event.preventDefault();
return false;
}
});

// Block right-click context menu
document.addEventListener('contextmenu', function(event) {
event.preventDefault();
return false;
});

// Advanced anti-debugging using performance timing with variable thresholds
(function timingCheck() {
var baseThreshold = 50;
var dynamicThreshold = baseThreshold + (globalSeed % 100);
var checkCount = 0;

setInterval(function() {
var start = performance.now();
debugger;
var end = performance.now();
checkCount++;

// Variable threshold based on environment
var currentThreshold = dynamicThreshold + (checkCount * 10);

if (end - start > currentThreshold && detectionBypass) {
jXKuzdDMGk = true;
// Redirect with multiple decoy destinations
var decoyUrls = ['https://www.google.com', 'https://www.microsoft.com', 'about:blank'];
window.location.replace(decoyUrls[globalSeed % decoyUrls.length]);
}
}, 150 + (globalSeed % 100));
})();

function customPRNG(seed) {
var m = 0x80000000; // 2**31
var a = 1103515245;
var c = 12345;

seed = (a * seed + c) % m;
return seed / (m - 1);
}

function advancedXOR(data, keyBase) {
var result = '';
var expandedKey = '';


for (var i = 0; i < data.length; i++) {
var keyChar = keyBase.charCodeAt(i % keyBase.length);
var entropyVal = entropy[i % entropy.length];
var rotatedKey = ((keyChar ^ entropyVal) + globalSeed) % 256;
expandedKey += String.fromCharCode(rotatedKey);
}

for (var j = 0; j < data.length; j++) {
result += String.fromCharCode(data.charCodeAt(j) ^ expandedKey.charCodeAt(j));
}

return result;
}

// Main payload - heavily obfuscated with multiple transformation layers
setTimeout(function() {
if (!jXKuzdDMGk && detectionBypass) {
var decoyArray1 = [119,109,99,116,102,123,102,97,107,101,95,102,108,97,103,125]; // wmctf{fake_flag}
var decoyArray2 = [104,116,116,112,115,58,47,47,101,120,97,109,112,108,101,46,99,111,109];

var polymorphicData = [
'V01DVEZbZmFrZV9mbGFnXQ==',
'bm90X3RoZV9yZWFsX2ZsYWc=',
'ZGVjb3lfZGF0YQ==',
'4oyM4p2h77iP4p2j4oyM4p2d77iL4p2c4oyI4p2g77iN4p2a77iP4p2b4oyL4p2Y',
'4p2Z77iM4p2X77iO4p2W77iM4p2V77iK4p2U77iL4p2T77iM4p2S77iN4p2R',
'4p2Q77iL4p2P77iO4p2O77iM4p2N77iK4p2M77iL4p2L77iM4p2K77iN4p2J',
'4p2I77iL4p2H77iO4p2G77iM4p2F77iK4p2E77iL4p2D77iM4p2C77iN4p2B',
'4p2A77iL4pyx77iO4py977iM4py877iK4py777iL4py677iM4py577iN4py4'
];

// Layer 3: Environmental validation with complex checks
var envValidation = function() {
var checks = [
typeof window !== 'undefined',
typeof document !== 'undefined',
navigator.userAgent.length > 10,
screen.width > 0 && screen.height > 0,
Date.now() > 1700000000000, // After 2023
Math.abs(new Date().getTimezoneOffset()) < 1440, // Valid timezone
entropy.length === 7,
globalSeed !== 0x5A4D // Should be modified by fingerprinting
];

var validCount = checks.filter(Boolean).length;
return validCount >= 6; // Require most checks to pass
};

// Layer 4: Steganographic data hidden in mathematical sequences
var fibSequence = [1,1,2,3,5,8,13,21,34,55,89,144,233,377,610,987,1597,2584];
var primeSequence = [2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61];

// Hidden data in sequence differences (steganography)
var hiddenIndices = [];
for (var i = 1; i < fibSequence.length; i++) {
var diff = fibSequence[i] - fibSequence[i-1];
if (diff > 0 && diff < polymorphicData.length) {
hiddenIndices.push(diff % polymorphicData.length);
}
}


var generateDynamicKey = function() {
var timeComponent = (Date.now() % 86400000).toString(36); // Daily changing component
var envComponent = (globalSeed ^ 0xDEADBEEF).toString(36);
var browserComponent = (navigator.userAgent.length * screen.colorDepth).toString(36);


var staticKey = 'WMCTF_2025_SVG_ANALYSIS';
return staticKey;
};

var decryptionPipeline = function() {
if (!envValidation()) {
console.log('Environment validation failed');
return null;
}

try {
var dynamicKey = generateDynamicKey();
var realDataIndices = [3, 4, 5, 6, 7]; // Skip decoy data
var encryptedParts = [];

for (var i = 0; i < realDataIndices.length; i++) {
var idx = realDataIndices[i];
if (idx < polymorphicData.length) {
encryptedParts.push(polymorphicData[idx]);
}
}

console.log('Found encrypted parts:', encryptedParts.length);

var stage1Results = [];
for (var j = 0; j < encryptedParts.length; j++) {
var part = encryptedParts[j];

// Convert Unicode escape sequences to characters
var decoded = part.replace(/4oyM|4p2[a-zA-Z0-9]|77i[a-zA-Z0-9]/g, function(match) {

var charMap = {
'4p2V': 'A', '4p2P': 'D', '4p2F': 'E', '4p2g': 'G', '4p2a': 'P',
'4p2c': 'S', '4oyI': 'V', '4p2T': 'a', '77iP': 'c', '4p2S': 'c',
'4p2L': 'c', '4p2D': 'a', '4p2O': 'e', '4p2M': 'e', '4p2d': 'f',
'77iO': 'g', '4p2b': 'h', '4p2Z': 'h', '4oyL': 'i', '77iM': 'i',
'4p2J': 'i', '4p2B': 'i', '4p2R': 'k', '4p2h': 'm', '4p2X': 'n',
'4p2H': 'n', '4pyx': 'n', '4p2I': 'o', '4p2A': 'o', '4p2C': 's',
'4p2Y': 's', '4p2j': 't', '77iK': 't', '4p2U': 't', '4p2K': 't',
'4p2N': 't', '4p2E': 'v', '4oyM': 'w', '77iL': '{', '4py9': '}',
'77iN': '_', '4p2W': '_', '4p2Q': '_', '4p2G': '_', '4py8': '!',
'4py7': '!', '4py6': '!', '4py5': '!', '4py4': '!'
};
return charMap[match] || '';
});

stage1Results.push(decoded);
}


var combined = stage1Results.join('');
console.log('Stage 1 result:', combined);

var finalResult = '';
for (var k = 0; k < combined.length; k++) {
var char = combined.charCodeAt(k);
var keyChar = dynamicKey.charCodeAt(k % dynamicKey.length);

var transformed = char ^ (keyChar % 32); // Reduced XOR for readability
finalResult += String.fromCharCode(transformed);
}

return finalResult;

} catch (error) {
console.log('Decryption failed:', error.message);
return null;
}
};


var mathematicalObfuscation = function() {

var phi = 1.618033988749895; // Golden ratio
var pi = 3.141592653589793; // Pi
var e = 2.718281828459045; // Euler's number


var mathKey = Math.floor(phi * 1000) + Math.floor(pi * 1000) + Math.floor(e * 1000);


window.mathSegments = [
btoa(String.fromCharCode(mathKey % 256) + segments[0]),
btoa(String.fromCharCode((mathKey * 2) % 256) + segments[1]),
btoa(String.fromCharCode((mathKey * 3) % 256) + segments[2]),
btoa(String.fromCharCode((mathKey * 4) % 256) + segments[3]),
btoa(String.fromCharCode((mathKey * 5) % 256) + segments[4])
];

return mathKey;
};


var mathKey = mathematicalObfuscation();


if (detectionBypass && !jXKuzdDMGk && verification()) {
constructPayload();


window.extractFlag = function() {
try {
if (window.hiddenData) {
var encoded = atob(window.hiddenData);
var key = 'WMCTF2025';
var decoded = '';
for (var i = 0; i < encoded.length; i++) {
decoded += String.fromCharCode(
encoded.charCodeAt(i) ^ key.charCodeAt(i % key.length)
);
}
console.log('Extracted flag:', decoded);
return decoded;
}
} catch (e) {
console.log('Flag extraction failed');
}
};
}
}
}, 1000);

// Decoy functions to confuse analysis
function generateFakeTraffic() {
var fakeUrls = [
'https://api.example.com/data',
'https://cdn.jsdelivr.net/npm/package',
'https://fonts.googleapis.com/css'
];
// These would normally make requests but are disabled for CTF
}

function createFakeElements() {
// Create invisible elements with misleading data
var hiddenDiv = document.createElement('div');
hiddenDiv.style.display = 'none';
hiddenDiv.innerHTML = atob('RmFrZSBmbGFnOiBXTUNURntub3RfdGhlX3JlYWxfZmxhZ30=');
document.body.appendChild(hiddenDiv);
}

// Initialize decoy functions
generateFakeTraffic();
createFakeElements();

// Add click handler for the invoice
document.addEventListener('click', function() {
if (detectionBypass && !jXKuzdDMGk) {
// This would normally redirect to phishing site
// window.location.href = 'https://fake-payment-portal.com';
console.log('Invoice clicked - in real attack, this would redirect to phishing site');
}
});
]]></script>
</svg>

其中夹杂着恶意的 js 代码。

分析恶意代码发现真正的核心数据有两部分:

  1. 加密数据:polymorphicData 数组。代码明确指出有效部分为索引 37
  2. 解码映射表:char_map 变量,它定义了加密编码(如 '4oyM')到明文字符(如 'w')的对应关系。

直接执行 decryptionPipeline 中的字符替换会得到一个以 wmctwf{ 的字符串,这和实际的 flag 开头 ``wmctf{` 十分接近,推测后续的 XOR 是用于干扰的,因此只要专注于第一步的解密即可。

观察发现编码 '4oyM'(对应字符 'w')在加密数据中出现了两次,这使得结果 wmctwf{ 中出现了第二个 w,由此推断 char_map 中的每一个编码在整个解码过程中只能被使用一次。

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
import re

def solve_ctf_with_uniqueness_rule():
polymorphicData = [
'V01DVEZbZmFrZV9mbGFnXQ==',
'bm90X3RoZV9yZWFsX2ZsYWc=',
'ZGVjb3lfZGF0YQ==',
'4oyM4p2h77iP4p2j4oyM4p2d77iL4p2c4oyI4p2g77iN4p2a77iP4p2b4oyL4p2Y',
'4p2Z77iM4p2X77iO4p2W77iM4p2V77iK4p2U77iL4p2T77iM4p2S77iN4p2R',
'4p2Q77iL4p2P77iO4p2O77iM4p2N77iK4p2M77iL4p2L77iM4p2K77iN4p2J',
'4p2I77iL4p2H77iO4p2G77iM4p2F77iK4p2E77iL4p2D77iM4p2C77iN4p2B',
'4p2A77iL4pyx77iO4py977iM4py877iK4py777iL4py677iM4py577iN4py4'
]

char_map = {
'4p2V': 'A', '4p2P': 'D', '4p2F': 'E', '4p2g': 'G', '4p2a': 'P',
'4p2c': 'S', '4oyI': 'V', '4p2T': 'a', '77iP': 'c', '4p2S': 'c',
'4p2L': 'c', '4p2D': 'a', '4p2O': 'e', '4p2M': 'e', '4p2d': 'f',
'77iO': 'g', '4p2b': 'h', '4p2Z': 'h', '4oyL': 'i', '77iM': 'i',
'4p2J': 'i', '4p2B': 'i', '4p2R': 'k', '4p2h': 'm', '4p2X': 'n',
'4p2H': 'n', '4pyx': 'n', '4p2I': 'o', '4p2A': 'o', '4p2C': 's',
'4p2Y': 's', '4p2j': 't', '77iK': 't', '4p2U': 't', '4p2K': 't',
'4p2N': 't', '4p2E': 'v', '4oyM': 'w', '77iL': '{', '4py9': '}',
'77iN': '_', '4p2W': '_', '4p2Q': '_', '4p2G': '_', '4py8': '!',
'4py7': '!', '4py6': '!', '4py5': '!', '4py4': '!'
}

# 1. 合并数据源
combined_encrypted_data = "".join([polymorphicData[i] for i in [3, 4, 5, 6, 7]])

# 2. 解码
used_keys = set()
sorted_keys = sorted(char_map.keys(), key=len, reverse=True)
pattern = re.compile('|'.join(re.escape(k) for k in sorted_keys))
final_flag_chars = []

for match in re.finditer(pattern, combined_encrypted_data):
key = match.group(0)
if key not in used_keys:
final_flag_chars.append(char_map[key])
used_keys.add(key)

# 3. 获得 flag
flag = "".join(final_flag_chars)
print(flag)

solve_ctf_with_uniqueness_rule()

运行得到 flag:

1
wmctf{SVG_Phishing_Attack_Detection_Evasion}

Voice_hacker

Challenge

你能偷取他的声音吗?

Can you steal his voice?

百度网盘:https://pan.baidu.com/s/1ygNDlVCkRZZt6NKjGU-1xA?pwd=tzv8

Google Dirve: https://drive.google.com/file/d/1OTCqmrymIT85vJwJAWIl9s1elORY0-Rh/view?usp=drive_link

Solution

附件是流量包文件 out.pcap,其中只包含UDP流量。从题目描述以及靶机要求录音 CTF,启动! 不难判断出这些流量传输的是音频数据。综合来看本题的目标是从流量包中提取出音频文件,将其克隆后生成内容为”CTF,启动!“的 wav 音频然后调用接口上传。

先用 tshark 把 UDP 包的 Payload 提取出来:

1
tshark -r out.pcap -T fields -e data > out.txt

先分析头三行数据:

1
2
3
800000000000000012345678fdfbfbfbfcfdfefdfdfdfcfcfdfdfdfdfdfdfdfdfdfefdfdfefefefefffefefefefefefefefefefefefefefefffeffff7e7e7e7eff7efffefefefdfefefefeffffff7e7e7e7e7e7e7e7efffefefefefefefefefeffff7e7e7e7d7e7e7efffffefefdfefdfefefeff7e7e7e7e7e7e7e7efffffefefefdfdfefeffff7e7e7d7d7d7e7e7efffefefefdfdfefefefeff7e7e7d7d7d7d7e7e7efffefefdfdfdfdfdfe
80000001000000a012345678fffeff7e7d7d7d7d7d7e7efffefdfdfdfdfdfdfdfeffff7e7d7d7d7d7e7e7efffefefdfdfdfdfefefeffff7e7e7e7e7e7e7efffefefefdfefdfdfefefeff7e7e7e7e7e7e7e7efffefefefefefefefeffffffff7e7e7eff7e7efffefefefefefefefefeffffff7e7e7e7e7e7e7e7efffefefefefefefefefeffff7e7e7e7e7e7e7e7efffefefefefefefefeffffffff7e7e7e7e7e7e7efffffefefefefefefefe
800000020000014012345678feffff7e7e7e7e7e7efffffffefefefdfdfefefeffff7e7e7e7d7d7e7efffffefefdfdfdfdfefeffff7e7e7e7e7e7efffefefefefdfdfdfdfeffff7e7e7d7d7e7e7e7efffefefdfdfdfdfefeffff7e7e7e7d7e7e7efffefefefdfdfdfefefeff7e7e7d7d7e7e7e7efffefefdfdfdfdfdfeff7e7e7d7d7d7d7e7e7efffefefdfdfcfdfdfeff7e7e7d7d7d7d7d7e7efefefdfdfcfcfdfdfeff7e7d7d7c7c7d7d7e

注意到前12个字节 80 00 00 00 00 00 00 00 12 34 56 78 具有非常明显的 RTP 协议特征,这是专门用于在 IP 网络上传输音视频的标准协议。

我们来解析这个RTP头部:

  • 80: 版本号(V=2)
  • 00: 标记位(M=0),载荷类型 (Payload Type, PT) = 0
  • 00 00: 序列号 (Sequence Number) = 0
  • 00 00 00 00: 时间戳 (Timestamp) = 0
  • 12 34 56 78: 同步源标识符 (SSRC),用于标识唯一的媒体流

其中最关键的信息是 Payload Type (PT)。根据RFC 3551标准,PT = 0 代表 PCMU (Pulse Code Modulation, μ-law),也就是 G.711 μ-law 编码。

G.711 μ-law 编码具有以下特性:

  • 采样率 (Sample Rate): 8000 Hz
  • 采样深度 (Bit Depth): 8 bits per sample
  • 声道数 (Channels): 1 (单声道, Mono)
  • 性质: 一种非线性的对数压缩编码,常用于电话系统

通过对比前三条流量的RTP头部,我们发现了清晰的规律:

包编号 序列号 (Seq Num) 时间戳 (Timestamp) SSRC 音频数据长度
1 0 0 0x12345678 160 字节
2 1 160 (0xa0) 0x12345678 160 字节
3 2 320 (0x140) 0x12345678 160 字节
  • 每个包的序列号递增 1,表明是连续的 RTP 包
  • SSRC 不变,表明所有包都属于同一个音频流
  • 每个包的时间戳增加 160,每个RTP包携带了 160 字节的音频数据,由于 G.711 每个采样点占 1 字节,所以 160 字节就代表 160 个采样点

用脚本从中提取出音频:

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
import wave
import struct

# --- G.711 μ-law to 16-bit Linear PCM Decoder ---
# 这是一个标准的查找表,用于将8位的μ-law字节解码为16位的线性采样值
_ULAW_DECODE_TABLE = [
-32124, -31100, -30076, -29052, -28028, -27004, -25980, -24956,
-23932, -22908, -21884, -20860, -19836, -18812, -17788, -16764,
-15996, -15484, -14972, -14460, -13948, -13436, -12924, -12412,
-11900, -11388, -10876, -10364, -9852, -9340, -8828, -8316,
-7932, -7676, -7420, -7164, -6908, -6652, -6396, -6140,
-5884, -5628, -5372, -5116, -4860, -4604, -4348, -4092,
-3900, -3772, -3644, -3516, -3388, -3260, -3132, -3004,
-2876, -2748, -2620, -2492, -2364, -2236, -2108, -1980,
-1884, -1820, -1756, -1692, -1628, -1564, -1500, -1436,
-1372, -1308, -1244, -1180, -1116, -1052, -988, -924,
-876, -844, -812, -780, -748, -716, -684, -652,
-620, -588, -556, -524, -492, -460, -428, -396,
-372, -356, -340, -324, -308, -292, -276, -260,
-244, -228, -212, -196, -180, -164, -148, -132,
-120, -112, -104, -96, -88, -80, -72, -64,
-56, -48, -40, -32, -24, -16, -8, 0,
32124, 31100, 30076, 29052, 28028, 27004, 25980, 24956,
23932, 22908, 21884, 20860, 19836, 18812, 17788, 16764,
15996, 15484, 14972, 14460, 13948, 13436, 12924, 12412,
11900, 11388, 10876, 10364, 9852, 9340, 8828, 8316,
7932, 7676, 7420, 7164, 6908, 6652, 6396, 6140,
5884, 5628, 5372, 5116, 4860, 4604, 4348, 4092,
3900, 3772, 3644, 3516, 3388, 3260, 3132, 3004,
2876, 2748, 2620, 2492, 2364, 2236, 2108, 1980,
1884, 1820, 1756, 1692, 1628, 1564, 1500, 1436,
1372, 1308, 1244, 1180, 1116, 1052, 988, 924,
876, 844, 812, 780, 748, 716, 684, 652,
620, 588, 556, 524, 492, 460, 428, 396,
372, 356, 340, 324, 308, 292, 276, 260,
244, 228, 212, 196, 180, 164, 148, 132,
120, 112, 104, 96, 88, 80, 72, 64,
-56, -48, -40, -32, -24, -16, -8, 0
]

def decode_ulaw_to_pcm16(ulaw_data):
"""将一整段 G.711 u-law 字节数据解码为 16-bit 线性 PCM 字节数据"""
pcm_frames = []
for ulaw_byte in ulaw_data:
# 从查找表中获取对应的16位PCM值
pcm_sample = _ULAW_DECODE_TABLE[ulaw_byte]
# 将16位整数打包成2个字节(小端序)
pcm_frames.append(struct.pack('<h', pcm_sample))
return b''.join(pcm_frames)

def main():
input_filename = "out.txt"
output_filename = "out.wav"

with open(input_filename, "r") as f:
lines = [line.strip() for line in f if line.strip()]

# 步骤 1: 剥离12字节的RTP头,并拼接所有G.711 u-law音频数据
raw_ulaw_data = b''.join(
bytes.fromhex(line[24:]) for line in lines if len(line) > 24
)

# 步骤 2: 将所有u-law数据解码为16位线性PCM数据
pcm_data = decode_ulaw_to_pcm16(raw_ulaw_data)

# 步骤 3: 创建并写入一个标准的WAV文件
with wave.open(output_filename, "wb") as wav_file:
# 设置WAV文件参数
wav_file.setnchannels(1) # 单声道 (mono)
wav_file.setframerate(8000) # 采样率 8000 Hz
wav_file.setsampwidth(2) # 采样宽度为2字节 (16-bit)

# 写入已解码的PCM数据
wav_file.writeframes(pcm_data)

if __name__ == "__main__":
main()

提取出来的音频是一段对话。先用音频处理软件将其中的女声去除,仅保留男声。然后在网上随便找一个克隆声音的项目,把处理好的音频文件丢上去让它读 CTF,启动!,最后用下面这个脚本上传音频进行检测即可:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import requests

url = 'http://49.232.42.74:31952/api/authenticate'
audio_path = 'out.wav'

# 构造 multipart/form-data
with open(audio_path, 'rb') as f:
files = {
'audio': ('recording.wav', f, 'audio/wav')
}
response = requests.post(url, files=files)

print('状态码:', response.status_code)
print('响应内容:', response.text)

得到的输出中包含 flag:

1
2
3
4
5
6
状态码: 200
响应内容: {
"flag": "WMCTF{01a9a4f1-e748-43fa-8d6d-bba372016adc}",
"message": "\u8ba4\u8bc1\u6210\u529f",
"success": true
}
1
WMCTF{01a9a4f1-e748-43fa-8d6d-bba372016adc}

GitHacker

Challenge

To be a GitHacker.

百度网盘:https://pan.baidu.com/s/18iCGk0EHLha1rUAwdMEKMA?pwd=y8c4

Google Dirve: https://drive.google.com/file/d/1sppWgVmrz8t0C6YeXqJIHKUCaPFgCc-U/view?usp=drive_link

View Hint: Hint 1

file is not image
文件不是图片

Solution

先检查引用日志:

1
2
3
4
5
6
7
8
9
$ git reflog
484c0d3 (HEAD -> master) HEAD@{0}: commit: have a good time!
93ab7b9 HEAD@{1}: reset: moving to HEAD~1
d504bbf HEAD@{2}: commit: encryptedFile
93ab7b9 HEAD@{3}: commit: change password
6ec92bc HEAD@{4}: reset: moving to HEAD~1
a026274 HEAD@{5}: commit: password
6ec92bc HEAD@{6}: commit: encryptedFile
6b6285c HEAD@{7}: commit (initial): Init
%%{init: {"gitGraph": {"mainBranchName": "master"}}}%%
gitGraph
   commit id: "6b6285c"
   commit id: "6ec92bc" tag: "添加文件 image.png"

   %% --- 第一次 reset ---
   branch "abandoned-path-1"
   checkout "abandoned-path-1"
   commit id: "a026274" tag: "password:EasyP@ssw0rd_from_Git_History"
   
   checkout master
   commit id: "93ab7b9" tag: "change password"

   %% --- 第二次 reset ---
   branch "abandoned-path-2"
   checkout "abandoned-path-2"
   commit id: "d504bbf" tag: "添加文件 image.jpg"

   checkout master
   commit id: "484c0d3"

按顺序逐个检查:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ git show 6b6285c
commit 6b6285cc1283144db890f1b27cc8c7b6ccd4b643
Author: toto <toto@WMCTF2025.com>
Date: Sat Aug 9 17:13:39 2025 +0800

Init

diff --git a/README.md b/README.md
new file mode 100644
index 0000000..43122ff
--- /dev/null
+++ b/README.md
@@ -0,0 +1 @@
+"# Welcome to WMCTF 2025"
1
2
3
4
5
6
7
8
9
10
11
$ git show 6ec92bc
commit 6ec92bcfdf3044bf21dcfa74500cbb929c0f0037
Author: toto <toto@WMCTF2025.com>
Date: Sat Aug 9 17:14:06 2025 +0800

encryptedFile

diff --git a/image.png b/image.png
new file mode 100644
index 0000000..27e6acd
Binary files /dev/null and b/image.png differ
1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ git show a026274
commit a026274fb418ec88af16444644fccab9b8a7e8dd
Author: toto <toto@WMCTF2025.com>
Date: Sat Aug 9 17:14:40 2025 +0800

password

diff --git a/password.md b/password.md
new file mode 100644
index 0000000..1ab140f
--- /dev/null
+++ b/password.md
@@ -0,0 +1 @@
+"EasyP@ssw0rd_from_Git_History"
1
2
3
4
5
6
$ git show 93ab7b9
commit 93ab7b9e28a4d442ec77a3fb37d64912bbddfdad
Author: toto <toto@WMCTF2025.com>
Date: Sat Aug 9 17:19:32 2025 +0800

change password
1
2
3
4
5
6
7
8
9
10
11
$ git show d504bbf
commit d504bbf75693fc83f6cf5c873306b7fc67edd804
Author: toto <toto@WMCTF2025.com>
Date: Sat Aug 9 17:23:51 2025 +0800

encryptedFile

diff --git a/image.jpg b/image.jpg
new file mode 100644
index 0000000..a3a40e3
Binary files /dev/null and b/image.jpg differ
1
2
3
4
5
6
$ git show 484c0d3
commit 484c0d313c560eb48986ef96690ef2f034addc90 (HEAD -> master)
Author: toto <toto@WMCTF2025.com>
Date: Sat Aug 9 17:25:29 2025 +0800

have a good time!

先分别把文件给还原了:

1
2
3
4
5
$ git checkout 6ec92bc -- image.png

$ git checkout a026274 -- password.md

$ git checkout d504bbf -- image.jpg

经尝试,image.pngimage.jpg 均为 VeraCrypt 的加密容器。

其中,image.png 的密码是 a026274 中的 password.md 的内容 EasyP@ssw0rd_from_Git_History,在该容器内的 flag.txt 中找到 flag 的前半段:

1
2
3
Congratulations! This is half of the gift I gave you:
WMCTF{G00d_J0b_F1nding_Th3_0ld_V3rsi0n_
Don't forget the way you came!Please keep trying to find the remaining gifts!

经比对哈希发现 image.pngimage.jpg 是两个不同的文件,且密码 EasyP@ssw0rd_from_Git_History 无法解密容器 image.jpg

没有找到第二个密码,参考这个帮助文档 [更改密码和密钥文件](https://veracrypt.io/zh-cn/Changing Passwords and Keyfiles.html),综合上面的 commit 填写的是 “change password” 而非 “change master key” 可以推测第二个容器只更改了密码,主密钥没变,因此使用 VeraCrypt 自带的 备份加密头信息恢复加密头信息 功能即可解开第二个容器。步骤如下:

  1. 打开 VeraCrypt
  2. 选择文件 image.png
  3. 选好之后依次点击菜单栏的 工具 -> 备份加密卷头信息 -> 输入密码 EasyP@ssw0rd_from_Git_History -> 此加密卷不包含隐藏加密卷 -> -> 保存到任意一个文件(这里我保存到 header) -> 继续
  4. 此时在你指定的目录下就会出现文件 header,回到 VeraCrypt 主界面
  5. 选择文件 image.jpg
  6. 选好之后依次点击菜单栏的 工具 -> 恢复加密卷头信息 -> 从外部备份文件中恢复加密头信息 -> -> 选择前面生成的文件 header -> 输入密码 EasyP@ssw0rd_from_Git_History -> 继续 -> 确定
  7. 最后直接挂载 image.jpg 即可,密码是 EasyP@ssw0rd_from_Git_History

在该容器内的 flag.txt 中找到 flag 的后半段:

1
2
3
4
5
Congratulations! You've solved it!
Figuring out the volume header manipulation trick is a sign of a true expert.
You've earned this. Well played!

And_Y0u_M4ster_The_VeraCrypt_H34der_Trick!}

拼起来得到完整 flag:

1
WMCTF{G00d_J0b_F1nding_Th3_0ld_V3rsi0n_And_Y0u_M4ster_The_VeraCrypt_H34der_Trick!}
  • 标题: WMCTF 2025
  • 作者: Aristore
  • 创建于 : 2025-09-22 10:30:00
  • 更新于 : 2025-09-22 10:27:48
  • 链接: https://www.aristore.top/posts/WMCTF2025/
  • 版权声明: 版权所有 © Aristore,禁止转载。
评论