CISCN

wasm-login

打开 index.html 看前端逻辑,发现关键验证代码:

验证以ccaf33e3512e31f3开头的flag.
认证过程是

这里.
然后是wasm,使用010对realse.wasm反编译可以得到

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
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
//--------------------------------------
// 010 Editor v5.0.2 Binary Template
//--------------------------------------
// File: WASM.bt
// Author: Harli Aquino
// E-mail: maharlito.aquino@cyren.com/d34ddr34m3r@gmail.com
// Version: 0.1
// Purpose: WebAssembly (WASM) Template
// Category: Internet
// File Mask: *.wasm
// ID Bytes: 00 61 73 6D
//--------------------------------------
// History:
// 0.1 2018-09-05 Harli : Initial version
//--------------------------------------
// References:
// WASM Parsing - https://github.com/athre0z/wasm
// LEB128 - https://github.com/strazzere/010Editor-stuff/blob/master/Templates/DEXTemplate.bt
//--------------------------------------
// License: This file is released into the public domain. People may
// use it for any purpose, commercial or otherwise.
//--------------------------------------


//////////////////////////////////////////////////
// Borrowed LEB128 stuff from DEX template
//////////////////////////////////////////////////

// struct to read a uleb128 value. uleb128's are a variable-length encoding for
// a 32 bit value. some of the uleb128/sleb128 code was adapted from dalvik's
// libdex/Leb128.h

typedef struct {
ubyte val <comment="uleb128 element">;
if(val > 0x7f) {
ubyte val <comment="uleb128 element">;
if (val > 0x7f) {
ubyte val <comment="uleb128 element">;
if(val > 0x7f) {
ubyte val <comment="uleb128 element">;
if(val > 0x7f) {
ubyte val <comment="uleb128 element">;
}
}
}
}
} uleb128 <read=ULeb128Read, comment="Unsigned little-endian base 128 value">;

// get the actual uint value of the uleb128
uint uleb128_value(uleb128 &u) {
local uint result;
local ubyte cur;

result = u.val[0];
if(result > 0x7f) {
cur = u.val[1];
result = (result & 0x7f) | (uint)((cur & 0x7f) << 7);
if(cur > 0x7f) {
cur = u.val[2];
result |= (uint)(cur & 0x7f) << 14;
if(cur > 0x7f) {
cur = u.val[3];
result |= (uint)(cur & 0x7f) << 21;
if(cur > 0x7f) {
cur = u.val[4];
result |= (uint)cur << 28;
}
}
}
}

return result;
}

typedef struct uleb128 uleb128p1;

int uleb128p1_value(uleb128 &u) {
return (int)uleb128_value(u) - 1;
}

string ULeb128Read(uleb128 &u) {
local string s;
s = SPrintf(s, "0x%X", uleb128_value(u));
return s;
}

// sleb128
typedef struct {
ubyte val <comment="sleb128 element">;
if(val > 0x7f) {
ubyte val <comment="sleb128 element">;
if (val > 0x7f) {
ubyte val <comment="sleb128 element">;
if(val > 0x7f) {
ubyte val <comment="sleb128 element">;
if(val > 0x7f) {
ubyte val <comment="sleb128 element">;
}
}
}
}
} sleb128 <read=SLeb128Read, comment="Signed little-endian base 128 value">;

// get the actual uint value of the uleb128
int sleb128_value(sleb128 &u) {
local int result;
local ubyte cur;

result = u.val[0];
if(result <= 0x7f) {
result = (result << 25) >> 25;
} else {
cur = u.val[1];
result = (result & 0x7f) | ((uint)(cur & 0x7f) << 7);
if(cur <= 0x7f) {
result = (result << 18) >> 18;
} else {
cur = u.val[2];
result |= (uint)(cur & 0x7f) << 14;
if(cur <= 0x7f) {
result = (result << 11) >> 11;
} else {
cur = u.val[3];
result |= (uint)(cur & 0x7f) << 21;
if(cur <= 0x7f) {
result = (result << 4) >> 4;
} else {
cur = u.val[4];
result |= (uint)cur << 28;
}
}
}
}

return result;
}

string SLeb128Read(sleb128 &u) {
local string s;
s = SPrintf(s, "%i", sleb128_value(u));
return s;
}

//--------------------------------------
// WASM Parsing Starts Here
//--------------------------------------


typedef enum <ubyte> {
SEC_UNK = 0,
SEC_TYPE = 1,
SEC_IMPORT = 2,
SEC_FUNCTION = 3,
SEC_TABLE = 4,
SEC_MEMORY = 5,
SEC_GLOBAL = 6,
SEC_EXPORT = 7,
SEC_START = 8,
SEC_ELEMENT = 9,
SEC_CODE = 10,
SEC_DATA = 11,
} SECTION_TYPE;

typedef struct {
char Magic[0x04];
int Version;
} _ModuleHeader<read=_HeaderInfo>;

string _HeaderInfo(_ModuleHeader &header) {
local string s;
local char magic[3];
local int i;
for (i=0;i<3;i++){
magic[i]=header.Magic[i+1];
}
SPrintf(s, "Magic: \\x00%s, Version: %d", magic, header.Version);
return s;
}

typedef struct {
uleb128 Type;
} _FunctionImportEntryData;

typedef struct {
uleb128 Flags;
uleb128 Initial;
if (uleb128_value(Flags) & 1) {
uleb128 Maximum;
}
} _ResizableLimits;


typedef struct {
uleb128 ElementType;
_ResizableLimits Limits;
} _TableType;

typedef struct {
_ResizableLimits Limits;
} _MemoryType;

typedef struct {
uleb128 ContentType;
uleb128 Mutability;
} _GlobalType;

typedef struct {
uleb128 ModuleLen;
if (uleb128_value(ModuleLen)) {
uleb128 ModuleStr[uleb128_value(ModuleLen)]<optimize=false>;
}
uleb128 FieldLen;
char FieldStr[uleb128_value(FieldLen)]<optimize=false>;
uleb128 Kind;
switch (uleb128_value(Kind)) {
case 0:
uleb128 Type;
break;
case 1:
uleb128 ElementType;
_ResizableLimits Limits;
break;
case 2:
_ResizableLimits Limits;
break;
case 3:
uleb128 ContentType;
uleb128 Mutability;
break;
}
} _ImportEntry<optimize=false, read=_ShowImportName>;

string _ShowImportName(_ImportEntry &entry) {
return entry.FieldStr;
}
typedef struct {
uleb128 Count;
_ImportEntry Entries[uleb128_value(Count)]<optimize=false>;
} _ImportSection<optimize=false>;

typedef struct {
uleb128 Form;
uleb128 ParamCount;
uleb128 ParamTypes[uleb128_value(ParamCount)]<optimize=false>;
uleb128 ReturnCount;
if (uleb128_value(ReturnCount)>0) {
uleb128 ReturnType;
}
} _FuncType<optimize=false>;

typedef struct {
uleb128 Count;
_FuncType Entries[uleb128_value(Count)]<optimize=false>;
} _TypeSection<optimize=false>;

typedef struct {
uleb128 Count;
uleb128 Types[uleb128_value(Count)]<optimize=false>;
} _FunctionSection<optimize=false>;

typedef struct {
uleb128 Count;
_TableType Types[uleb128_value(Count)]<optimize=false>;
} _TableSection<optimize=false>;

typedef struct {
uleb128 Count;
_MemoryType Entries[uleb128_value(Count)]<optimize=false>;
} _MemorySection;

typedef struct {
local int pos = FTell();
while(ReadByte(pos)!=0x0B){
pos=pos+1;
}
FSeek(pos+1);
} _InitExpr <optimize=false>;

typedef struct {
_GlobalType Type;
_InitExpr Init;
} _GlobalEntry <optimize=false>;

typedef struct {
uleb128 Count;
_GlobalEntry Globals[uleb128_value(Count)] <optimize=false>;
//local int pos = FTell()-2;
//if (ReadByte(pos)==0x0B){
// FSeek(pos+1);
//}
} _GlobalSection;

typedef struct {
uleb128 FieldLen;
if (uleb128_value(FieldLen)) {
char FieldStr[uleb128_value(FieldLen)];
}
uleb128 Kind;
uleb128 Index;
} _ExportEntry<optimize=false, read=_ShowExportName>;

string _ShowExportName(_ExportEntry &entry) {
return entry.FieldStr;
}
typedef struct {
uleb128 Count;
_ExportEntry Entries[uleb128_value(Count)]<optimize=false>;
} _ExportSection;

typedef struct {
uleb128 Index;
} _StartSection;

typedef struct {
uleb128 Index;
_InitExpr Offset;
uleb128 NumElem;
uleb128 Elems[uleb128_value(NumElem)]<optimize=false>;
} _ElementSegment;

typedef struct {
uleb128 Count;
_ElementSegment Entries[uleb128_value(Count)]<optimize=false>;
} _ElementSection;

typedef struct {
uleb128 Count;
uleb128 Type;
} _LocalEntry;

typedef struct {
uleb128 BodySize;
local int local_start = FTell();
uleb128 LocalCount;
_LocalEntry Locals[uleb128_value(LocalCount)]<optimize=false>;
uleb128 Code[uleb128_value(BodySize) - (FTell()-local_start)];
} _FunctionBody <optimize=false>;

typedef struct {
uleb128 Count;
_FunctionBody Bodies[uleb128_value(Count)]<optimize=false>;
} _CodeSection;

typedef struct {
uleb128 Index;
_InitExpr Offset;
uleb128 Size;
byte Data[uleb128_value(Size)]<optimize=false>;
} _DataSegment <optimize=false>;

typedef struct {
uleb128 Count;
_DataSegment Entries[uleb128_value(Count)]<optimize=false>;
} _DataSection;

typedef struct {
uleb128 Index;
uleb128 NameLen;
if (NameLen) {
uleb128 NameStr[NameLen]<optimize=false>;
}
} _Naming <optimize=false>;

typedef struct {
uleb128 Count;
_Naming Names[uleb128_value(Count)]<optimize=false>;
} _NameMap;

typedef struct {
uleb128 Index;
_NameMap LocalMap;
} _LocalNames;

typedef struct {
uleb128 Count;
_LocalNames Funcs[uleb128_value(Count)]<optimize=false>;
} _LocalNameMap;


typedef struct {
uleb128 Id;
if (uleb128_value(Id) == SEC_UNK){
uleb128 NameLen;
char Name[uleb128_value(NameLen)]<optimize=false>;
}
uleb128 PayloadLen;
switch ((SECTION_TYPE) uleb128_value(Id)){
case SEC_UNK:
uleb128 Payload[uleb128_value(PayloadLen)]<optimize=false>;
break;
case SEC_TYPE:
_TypeSection Payload;
break;
case SEC_IMPORT:
_ImportSection Payload;
break;
case SEC_FUNCTION:
_FunctionSection Payload;
break;
case SEC_TABLE:
_TableSection Payload;
break;
case SEC_MEMORY:
_MemorySection Payload;
break;
case SEC_GLOBAL:
_GlobalSection Payload;
break;
case SEC_EXPORT:
_ExportSection Payload;
break;
case SEC_START:
_StartSection Payload;
break;
case SEC_ELEMENT:
_ElementSection Payload;
break;
case SEC_CODE:
_CodeSection Payload;
break;
case SEC_DATA:
_DataSection Payload;
break;
default:
break;
}
//uleb128 Overhang[uleb128_value(PayloadLen) - sizeof(Name) - sizeof(NameLen) - sizeof(Payload)];
} _Section <read=_GetSectionType>;

string _GetSectionType(_Section &section){
switch ((SECTION_TYPE) uleb128_value(section.Id)){
case SEC_UNK:
return "UNKNOWN";
break;
case SEC_TYPE:
return "TYPE";
break;
case SEC_IMPORT:
return "IMPORT";
break;
case SEC_FUNCTION:
return "FUNCTION";
break;
case SEC_TABLE:
return "TABLE";
break;
case SEC_MEMORY:
return "MEMORY";
break;
case SEC_GLOBAL:
return "GLOBAL";
break;
case SEC_EXPORT:
return "EXPORT";
break;
case SEC_START:
return "START";
break;
case SEC_ELEMENT:
return "ELEMENT";
break;
case SEC_CODE:
return "CODE";
break;
case SEC_DATA:
return "DATA";
break;
default:
return "UNKNOWN";
break;
}
return "UNKNOWN";
}

_ModuleHeader ModuleHeader;
while (FTell()!=FileSize()) {
if (ReadByte(FTell())==0x00) {
break;
}
_Section Section;
}

//--------------------------------------
// ~nuninuninu~
//--------------------------------------

wasm2watrelease.wasm 转成文本格式,分析核心函数。
根据这个可以在数据段找到自定义base64表是: NhR4UJ+z5qFGiTCaAIDYwZ0dLl6PEXKgostxuMv8rHBp3n9emjQf1cWb2/VkS7yO
SHA256的初始值

那么根据题目描述
把时间戳定在
第三个周末:12月20日(周六)、21日(周日)到周一凌晨:12月22日 00:00 - 06:00 左右
写个爆破来解
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
const fs = require('fs');
const crypto = require('crypto');

async function main() {
const wasmBuffer = fs.readFileSync('./build/release.wasm');
const compiled = await WebAssembly.compile(wasmBuffer);

async function testWithTimestamp(t) {
const instance = await WebAssembly.instantiate(compiled, {
env: {
abort() { throw Error('Abort'); },
"console.log"(text) {},
"Date.now"() { return t; },
},
});

const exports = instance.exports;
const memory = exports.memory;

function lift(p) {
if (!p) return null;
const end = p + new Uint32Array(memory.buffer)[p - 4 >>> 2] >>> 1;
const m16 = new Uint16Array(memory.buffer);
let start = p >>> 1, s = "";
while (end - start > 1024) s += String.fromCharCode(...m16.subarray(start, start += 1024));
return s + String.fromCharCode(...m16.subarray(start, end));
}

function lower(v) {
if (v == null) return 0;
const len = v.length;
const ptr = exports.__new(len << 1, 2) >>> 0;
const m16 = new Uint16Array(memory.buffer);
for (let i = 0; i < len; ++i) m16[(ptr >>> 1) + i] = v.charCodeAt(i);
return ptr;
}

const refs = new Map();
function retain(p) { if(p){const r=refs.get(p);if(r)refs.set(p,r+1);else refs.set(exports.__pin(p),1);}return p; }
function release(p) { if(p){const r=refs.get(p);if(r===1){exports.__unpin(p);refs.delete(p);}else if(r)refs.set(p,r-1);} }

const up = retain(lower("admin"));
const pp = lower("admin");
try {
return lift(exports.authenticate(up, pp) >>> 0);
} finally {
release(up);
}
}

console.log("=== Date verification ===");
console.log("2025-12-20:", new Date('2025-12-20').toDateString()); // Saturday
console.log("2025-12-21:", new Date('2025-12-21').toDateString()); // Sunday
console.log("2025-12-22:", new Date('2025-12-22').toDateString()); // Monday

// 北京时间 2025-12-22 00:00:00 到 06:00:00
const start = new Date('2025-12-22T00:00:00+08:00').getTime();
const end = new Date('2025-12-22T06:00:00+08:00').getTime();

console.log(`\nSearch range (Beijing time 12/22 00:00-06:00):`);
console.log(`Start: ${start} = ${new Date(start).toISOString()}`);
console.log(`End: ${end} = ${new Date(end).toISOString()}`);


console.log("\n=== Quick scan (every 1000ms) ===");
let foundRange = null;
for (let t = start; t <= end; t += 1000) {
const result = await testWithTimestamp(t);
const md5 = crypto.createHash('md5').update(result).digest('hex');
if (md5.startsWith("ccaf33e3512e31f3")) {
console.log(`\nFound at ${t}! MD5: ${md5}`);
foundRange = t;
break;
}
if ((t - start) % 600000 === 0) {
const mins = (t - start) / 60000;
console.log(`Progress: ${new Date(t).toISOString()} (${mins} mins checked) - ${md5.substring(0,8)}...`);
}
}

if (!foundRange) {
console.log("Not found in second-level scan.");
console.log("\nTrying millisecond search in first hour...");


const hourEnd = start + 3600000;
for (let t = start; t <= hourEnd; t++) {
const result = await testWithTimestamp(t);
const md5 = crypto.createHash('md5').update(result).digest('hex');
if (md5.startsWith("ccaf33e3512e31f3")) {
console.log("\n=== FOUND! ===");
console.log("Timestamp:", t);
console.log("Date:", new Date(t).toISOString());
console.log("Result:", result);
console.log("MD5:", md5);
console.log("\nFLAG: flag{" + md5 + "}");
return;
}
if (t % 100000 === 0) {
console.log(`Progress: ${t} - ${md5.substring(0,8)}...`);
}
}
}
}

main().catch(console.error);

得到的时间戳是2025-12-2201:29:10.699(JST)

babygame

用GDRE反编译关键地方是

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
extends CenterContainer

@onready var flagTextEdit: Node = $PanelContainer / VBoxContainer / FlagTextEdit
@onready var label2: Node = $PanelContainer / VBoxContainer / Label2

static var key = "FanAglFanAglOoO!"
var data = ""

func _on_ready() -> void :
Flag.hide()

func get_key() -> String:
return key

func submit() -> void :
data = flagTextEdit.text

var aes = AESContext.new()
aes.start(AESContext.MODE_ECB_ENCRYPT, key.to_utf8_buffer())
var encrypted = aes.update(data.to_utf8_buffer())
aes.finish()

if encrypted.hex_encode() == "d458af702a680ae4d089ce32fc39945d":
label2.show()
else:
label2.hide()

func back() -> void :
get_tree().change_scene_to_file("res://scenes/menu.tscn")

那么在这里就可以找到密文了,这里的key是金币没收完成的情况.
AES的key是来自
这里可以通过CE的变速功能来通关,或者就是该金币的检验那里
(本来是试过用这里的key)解出来的不像是密文就尝试用CE修改游戏倍速了.改的0.5把金币都收集完了然后过关得到

这里一下知道像key了.
尝试得到

eternum

run.sh给了服务器的地址.
tcp.pcap解析后是:

1
2
3
MAGIC = b"ET3RNUMX"  (8 bytes)
LEN = big-endian u32 (4 bytes)
DATA = LEN bytes

在client→server之间有奇怪的数据,但是前面还不知道可以干什么.分析kworker文件
DIE查了之后发现是有UPX壳的,解了之后IDA里面找到

找到这样的内容,往下那么可以猜测还需要提出内容
全部的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
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
#!/usr/bin/env python3
import struct, socket, base64, re
from collections import defaultdict
from cryptography.hazmat.primitives.ciphers.aead import AESGCM

PCAP_FILE = "tcp.pcap"
KEY = b"xfqGcVjrOWp5tUGCPFQq448nPDjILTe7"
MAGIC = b"ET3RNUMX"

# ------------------ PCAP parsing (no dpkt/scapy) ------------------
def parse_pcap(buf: bytes):
magic = struct.unpack("<I", buf[:4])[0]
endian = "<"
if magic == 0xd4c3b2a1:
endian = ">"
elif magic != 0xa1b2c3d4:
raise ValueError("not pcap")
off = 24
pkts = []
while off + 16 <= len(buf):
ts_sec, ts_usec, incl_len, orig_len = struct.unpack(endian + "IIII", buf[off:off+16])
off += 16
pkts.append(buf[off:off+incl_len])
off += incl_len
return pkts

def parse_eth(pkt):
if len(pkt) < 14: return None
et = struct.unpack("!H", pkt[12:14])[0]
return et, pkt[14:]

def parse_ipv4(b):
if len(b) < 20: return None
ver = b[0] >> 4
ihl = (b[0] & 0xf) * 4
if ver != 4 or len(b) < ihl: return None
total = struct.unpack("!H", b[2:4])[0]
proto = b[9]
src = socket.inet_ntoa(b[12:16])
dst = socket.inet_ntoa(b[16:20])
return src, dst, proto, b[ihl:total]

def parse_tcp(b):
if len(b) < 20: return None
sport, dport, seq = struct.unpack("!HHI", b[:8])
offres = b[12]
doff = (offres >> 4) * 4
if len(b) < doff: return None
return sport, dport, seq, b[doff:]

def reassemble(segs):
segs = sorted(segs)
out = b""
cur = None
for seq, pl in segs:
if not pl: continue
if cur is None:
out = pl
cur = seq + len(pl)
elif seq >= cur:
out += b"\x00"*(seq-cur) + pl
cur = seq + len(pl)
else:
overlap = cur - seq
if overlap < len(pl):
out += pl[overlap:]
cur += len(pl) - overlap
return out

def extract_frames(stream: bytes):
frames = []
i = 0
while True:
j = stream.find(MAGIC, i)
if j < 0: break
if j + 12 > len(stream): break
ln = struct.unpack("!I", stream[j+8:j+12])[0]
st = j + 12
ed = st + ln
if ed > len(stream): break
frames.append(stream[st:ed])
i = ed
return frames

# ------------------ protobuf minimal decode ------------------
def pb_read_varint(buf, pos):
val = 0
shift = 0
while True:
b = buf[pos]; pos += 1
val |= (b & 0x7f) << shift
if not (b & 0x80):
return val, pos
shift += 7

def pb_parse(buf):
pos = 0
fields = []
while pos < len(buf):
key, pos = pb_read_varint(buf, pos)
field = key >> 3
wire = key & 7
if wire == 0:
v, pos = pb_read_varint(buf, pos)
fields.append((field, wire, v))
elif wire == 2:
l, pos = pb_read_varint(buf, pos)
v = buf[pos:pos+l]
pos += l
fields.append((field, wire, v))
elif wire == 5:
v = int.from_bytes(buf[pos:pos+4], "little"); pos += 4
fields.append((field, wire, v))
elif wire == 1:
v = int.from_bytes(buf[pos:pos+8], "little"); pos += 8
fields.append((field, wire, v))
else:
raise ValueError("unsupported wire")
return fields

def pb_to_dict(buf):
d = {}
for f,w,v in pb_parse(buf):
d.setdefault(f, []).append((w,v))
return d

# ------------------ main solve ------------------
def main():
aes = AESGCM(KEY)
pcap = open(PCAP_FILE, "rb").read()
pkts = parse_pcap(pcap)

flows = defaultdict(list)
for pkt in pkts:
eth = parse_eth(pkt)
if not eth: continue
et, p = eth
if et != 0x0800: continue
ip = parse_ipv4(p)
if not ip: continue
src, dst, proto, ipp = ip
if proto != 6: continue
tcp = parse_tcp(ipp)
if not tcp: continue
sport, dport, seq, pl = tcp
if pl:
flows[(src,sport,dst,dport)].append((seq,pl))

streams = {k: reassemble(v) for k,v in flows.items()}

# locate the two directions that contain our frame magic
candidates = [(k,st) for k,st in streams.items() if MAGIC in st]
if len(candidates) < 2:
raise SystemExit("cannot find both streams")

# heuristic: pick two distinct streams
frames = []
for k, st in candidates:
fs = extract_frames(st)
if fs:
frames.append((k, fs))

# decrypt & scan for base32(flag)
base32_re = re.compile(rb"[A-Z2-7]{30,}")
for k, fs in frames:
for idx, payload in enumerate(fs):
if len(payload) < 13:
continue
nonce = payload[:12]
ct = payload[12:]
try:
pt = aes.decrypt(nonce, ct, b"")
except Exception:
continue

# inner protobuf bytes are at offset 9..-4 in this challenge
if len(pt) > 13:
pb = pt[9:-4]
try:
d = pb_to_dict(pb)
except Exception:
d = None

# try to find base32 inside plaintext
m = base32_re.search(pt)
if m:
b32 = m.group(0)
try:
flag = base64.b32decode(b32).decode(errors="ignore").strip()
if "flag{" in flag:
print(flag)
return
except Exception:
pass

print("flag not found")

if __name__ == "__main__":
main()

VVMMMMMM

思路:x86_64主程序负责解密并把一段RV64shellcode丢进虚拟CPU(Unicorn)里执行;shellcode用输入生成12个32-bit值并与内置常量比较。因为它的输出与输入是线性XOR关系,所以用”全0输入跑一遍”就能拿到mask,再XOR回去得到正确输入,从而得到flag。
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
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
#!/usr/bin/env python3
#Arise now, ye Tarnished! Unleash the might of the Ancient Dragons from their chains of bondage!
import os, pty, re, select, signal, struct, subprocess, sys, time

BIN = sys.argv[1] if len(sys.argv) > 1 else "./vvvmmm"
BLOB_LEN = 0x296
BLOB_SIG = b"\x1d\x71\xa2\xec\xa6\xe8\xca\xe4" # rv64c prologue-ish
CONST_PAT = b"e4Y8YRXVzg2HRrCUy35CM0Txq91HzMGZ" # 32 bytes
INP, CST, STK = 0x10000000, 0x10001000, 0x20000000
PAGE = 0x1000

u64 = lambda x: x & 0xFFFFFFFFFFFFFFFF
sext = lambda x,b: ((x & ((1<<b)-1)) ^ (1<<(b-1))) - (1<<(b-1))
mulhu = lambda a,b: ((a&0xFFFFFFFFFFFFFFFF)*(b&0xFFFFFFFFFFFFFFFF) >> 64) & 0xFFFFFFFFFFFFFFFF
def remu(a,b): a&=0xFFFFFFFFFFFFFFFF; b&=0xFFFFFFFFFFFFFFFF; return a if b==0 else a%b
def remuw(a,b): a&=0xFFFFFFFF; b&=0xFFFFFFFF; return a if b==0 else a%b

class Mem:
def __init__(s): s.p={}
def map(s,a,n):
a&=~(PAGE-1); e=(a+n+PAGE-1)&~(PAGE-1)
for x in range(a,e,PAGE): s.p.setdefault(x, bytearray(PAGE))
def _po(s,a):
b=a&~(PAGE-1);
if b not in s.p: raise RuntimeError(f"unmapped {a:#x}")
return s.p[b], a-b
def rb(s,a,n):
o=bytearray()
for i in range(n):
p,off=s._po(a+i); o.append(p[off])
return bytes(o)
def wb(s,a,d):
for i,v in enumerate(d):
p,off=s._po(a+i); p[off]=v
def u8(s,a): return s.rb(a,1)[0]
def u16(s,a): return int.from_bytes(s.rb(a,2),'little')
def u32(s,a): return int.from_bytes(s.rb(a,4),'little')
def u64(s,a): return int.from_bytes(s.rb(a,8),'little')
def w32(s,a,v): s.wb(a, struct.pack("<I", v & 0xFFFFFFFF))
def w64(s,a,v): s.wb(a, struct.pack("<Q", v & 0xFFFFFFFFFFFFFFFF))

def rvc(cpu, ins):
x, SP, RA = cpu['x'], 2, 1
op, f3 = ins & 3, (ins>>13)&7

# q0: c.lw/c.sw
if op==0:
if f3==2: # c.lw
rd=8+((ins>>2)&7); rs1=8+((ins>>7)&7)
u=((ins>>6)&1)<<2 | ((ins>>10)&7)<<3 | ((ins>>5)&1)<<6
a=u64(x[rs1]+u); x[rd]=sext(cpu['m'].u32(a),32) & 0xFFFFFFFFFFFFFFFF; return
if f3==6: # c.sw
rs2=8+((ins>>2)&7); rs1=8+((ins>>7)&7)
u=((ins>>6)&1)<<2 | ((ins>>10)&7)<<3 | ((ins>>5)&1)<<6
cpu['m'].w32(u64(x[rs1]+u), x[rs2]); return

# q1: c.addi/c.li/c.addi16sp/c.j/c.beqz/c.bnez/c.andi/c.srli/c.srai/c.sub/xor/or/and (+subw/addw)
if op==1:
if f3==0: # c.addi/nop
rd=(ins>>7)&0x1F; imm=sext(((ins>>2)&0x1F)|((ins>>12)&1)<<5,6)
if rd: x[rd]=u64(x[rd]+imm); return
return
if f3==2: # c.li
rd=(ins>>7)&0x1F; imm=sext(((ins>>2)&0x1F)|((ins>>12)&1)<<5,6)
if rd: x[rd]=u64(imm); return
return
if f3==3: # c.addi16sp
rd=(ins>>7)&0x1F
if rd==SP:
imm=((ins>>6)&1)<<4 | ((ins>>2)&1)<<5 | ((ins>>5)&1)<<6 | ((ins>>3)&3)<<7 | ((ins>>12)&1)<<9
imm=sext(imm,10); x[SP]=u64(x[SP]+imm); return
if f3==5: # c.j
imm=((ins>>3)&7)<<1 | ((ins>>11)&1)<<4 | ((ins>>2)&1)<<5 | ((ins>>7)&1)<<6 | ((ins>>6)&1)<<7 \
| ((ins>>9)&3)<<8 | ((ins>>8)&1)<<10 | ((ins>>12)&1)<<11
cpu['pc']=u64(cpu['pc']-2+sext(imm,12)); return
if f3 in (6,7): # c.beqz/c.bnez
rs1=8+((ins>>7)&7)
imm=((ins>>3)&3)<<1 | ((ins>>10)&3)<<3 | ((ins>>2)&1)<<5 | ((ins>>5)&3)<<6 | ((ins>>12)&1)<<8
imm=sext(imm,9)
z = (x[rs1]==0)
if (f3==6 and z) or (f3==7 and not z): cpu['pc']=u64(cpu['pc']-2+imm)
return
if f3==4:
subop=(ins>>10)&3
rd=8+((ins>>7)&7)
if subop==0: # srli
sh=((ins>>2)&0x1F)|((ins>>12)&1)<<5; x[rd]=u64(x[rd]>>sh); return
if subop==1: # srai
sh=((ins>>2)&0x1F)|((ins>>12)&1)<<5; x[rd]=u64(sext(x[rd],64)>>sh); return
if subop==2: # andi
imm=sext(((ins>>2)&0x1F)|((ins>>12)&1)<<5,6); x[rd]=u64(x[rd] & u64(imm)); return
if subop==3:
f2=(ins>>5)&3; b12=(ins>>12)&1; rs2=8+((ins>>2)&7)
a,b=x[rd],x[rs2]
if b12==1 and f2 in (0,1): # subw/addw
aa=a&0xFFFFFFFF; bb=b&0xFFFFFFFF
res=((aa-bb) if f2==0 else (aa+bb)) & 0xFFFFFFFF
x[rd]=u64(sext(res,32)); return
if f2==0: x[rd]=u64(a-b)
elif f2==1: x[rd]=u64(a^b)
elif f2==2: x[rd]=u64(a|b)
elif f2==3: x[rd]=u64(a&b)
else: raise RuntimeError("bad c.alu")
return

# q2: c.slli/c.lwsp/c.swsp/c.jr/c.jalr/c.mv/c.add/ebreak + (optional c.ldsp/c.sdsp not needed here)
if op==2:
if f3==0: # c.slli
rd=(ins>>7)&0x1F; sh=((ins>>2)&0x1F)|((ins>>12)&1)<<5
if rd: x[rd]=u64(x[rd]<<sh); return
return
if f3==2: # c.lwsp
rd=(ins>>7)&0x1F
u=((ins>>4)&7)<<2 | ((ins>>12)&1)<<5 | ((ins>>2)&3)<<6
x[rd]=u64(sext(cpu['m'].u32(u64(x[SP]+u)),32)) if rd else 0; return
if f3==6: # c.swsp
rs2=(ins>>2)&0x1F
u=((ins>>9)&0xF)<<2 | ((ins>>7)&3)<<6
cpu['m'].w32(u64(x[SP]+u), x[rs2]); return
if f3==4:
rs1=(ins>>7)&0x1F; rs2=(ins>>2)&0x1F; b12=(ins>>12)&1
if b12==0:
if rs2==0: cpu['pc']=x[rs1]; return # jr
x[rs1]=x[rs2]; return # mv
else:
if rs1==0 and rs2==0: raise StopIteration
if rs2==0: x[RA]=cpu['pc']; cpu['pc']=x[rs1]; return # jalr
x[rs1]=u64(x[rs1]+x[rs2]); return # add

raise RuntimeError(f"unhandled rvc {ins:#x}")

def step(cpu, limit):
m,x=cpu['m'],cpu['x']
if cpu['pc']>=limit: return False
b0=m.u8(cpu['pc'])
if (b0&3)!=3:
ins=m.u16(cpu['pc']); cpu['pc']=u64(cpu['pc']+2); rvc(cpu,ins); return True
ins=m.u32(cpu['pc']); pc0=cpu['pc']; cpu['pc']=u64(cpu['pc']+4)
op=ins&0x7F; rd=(ins>>7)&0x1F; f3=(ins>>12)&7; rs1=(ins>>15)&0x1F; rs2=(ins>>20)&0x1F; f7=(ins>>25)&0x7F
I=sext(ins>>20,12)
if op==0x37: x[rd]=u64(ins&0xFFFFF000); return True # LUI
if op==0x13: # OP-IMM
a=x[rs1]
if f3==0: x[rd]=u64(a+I) # ADDI
elif f3==4: x[rd]=u64(a ^ u64(I)) # XORI
elif f3==5: # SRLI/SRAI
sh=(ins>>20)&0x3F
x[rd]=u64((a>>sh) if f7==0 else (sext(a,64)>>sh))
elif f3==3: x[rd]=1 if (a&0xFFFFFFFFFFFFFFFF) < (u64(I)&0xFFFFFFFFFFFFFFFF) else 0 # SLTIU (seqz path)
else: raise RuntimeError("op-imm")
return True
if op==0x03: # LOAD
a=u64(x[rs1]+I)
if f3==4: x[rd]=m.u8(a) # LBU
elif f3==2: x[rd]=u64(sext(m.u32(a),32)) # LW
elif f3==6: x[rd]=m.u32(a) # LWU
elif f3==3: x[rd]=m.u64(a) # LD
else: raise RuntimeError("load")
return True
if op==0x23: # STORE
S=sext(((ins>>7)&0x1F)|(((ins>>25)&0x7F)<<5),12); a=u64(x[rs1]+S)
if f3==2: m.w32(a, x[rs2]) # SW
elif f3==3: m.w64(a, x[rs2]) # SD
else: raise RuntimeError("store")
return True
if op==0x63: # BRANCH
B=sext((((ins>>8)&0xF)<<1)|(((ins>>25)&0x3F)<<5)|(((ins>>7)&1)<<11)|(((ins>>31)&1)<<12),13)
take = (x[rs1]==x[rs2]) if f3==0 else (x[rs1]!=x[rs2]) if f3==1 else None
if take is None: raise RuntimeError("branch")
if take: cpu['pc']=u64(pc0+B)
return True
if op==0x33: # OP / M
a,b=x[rs1],x[rs2]
if f7==0 and f3==0: x[rd]=u64(a+b) # ADD
elif f7==0 and f3==4: x[rd]=u64(a^b) # XOR
elif f7==0x20 and f3==0: x[rd]=u64(a-b) # SUB
elif f7==1:
if f3==0: x[rd]=u64((a*b)&0xFFFFFFFFFFFFFFFF) # MUL
elif f3==3: x[rd]=mulhu(a,b) # MULHU
elif f3==7: x[rd]=u64(remu(a,b)) # REMU
else: raise RuntimeError("M")
else: raise RuntimeError("op")
return True
if op==0x3B: # OP-32
a,b=x[rs1]&0xFFFFFFFF, x[rs2]&0xFFFFFFFF
if f7==1 and f3==7: x[rd]=u64(sext(remuw(a,b),32)) # REMUW
else: raise RuntimeError("op-32")
return True
raise RuntimeError(f"opcode {op:#x} at {pc0:#x}")

def run_blob(blob, const, inp48):
m=Mem()
m.map(0,0x1000); m.wb(0,blob)
m.map(INP,0x1000); m.wb(INP, (inp48+b"\0"*48)[:48])
m.map(CST,0x1000); m.wb(CST, const+b"\0")
m.map(STK-0x4000,0x4000)
cpu={'m':m,'x':[0]*32,'pc':0}
cpu['x'][2]=STK; cpu['x'][10]=INP; cpu['x'][11]=CST # sp,a0,a1
for _ in range(2_000_000):
if not step(cpu,len(blob)): break
base=STK-0x60 # prologue: sp -= 0x60
return [m.u32(base+o) for o in range(4,0x34,4)] # 12 u32

def extract_expected(blob):
pend={}; out=[]
pc=0
while pc<len(blob):
if (blob[pc]&3)!=3: pc+=2; continue
ins=int.from_bytes(blob[pc:pc+4],"little")
op=ins&0x7F; rd=(ins>>7)&0x1F; f3=(ins>>12)&7; rs1=(ins>>15)&0x1F
if op==0x37: pend[rd]=ins&0xFFFFF000
if op==0x13 and f3==0 and pc>=0x19E:
imm=sext(ins>>20,12)
if rs1 in pend: out.append((pend[rs1]+imm)&0xFFFFFFFF)
pc+=4
if len(out)<12: raise RuntimeError("extract expected failed")
return out[-12:]

def extract_runtime(binpath):
master, slave = pty.openpty()
p = subprocess.Popen([binpath], stdin=slave, stdout=slave, stderr=slave, close_fds=True)
os.close(slave)
buf=b""; t0=time.time()
while time.time()-t0<5 and b"%48c" not in buf:
r,_,_=select.select([master],[],[],0.2)
if r:
try: buf+=os.read(master,4096)
except OSError: break
os.kill(p.pid, signal.SIGSTOP)
maps=open(f"/proc/{p.pid}/maps","r",errors="ignore").read().splitlines()
rws=[]
for ln in maps:
m=re.match(r"^([0-9a-f]+)-([0-9a-f]+)\s+rw..",ln)
if m: rws.append((int(m.group(1),16), int(m.group(2),16)))
mem=open(f"/proc/{p.pid}/mem","rb",0)
blob=const=None
for a0,a1 in rws:
mem.seek(a0); b=mem.read(a1-a0)
if const is None:
i=b.find(CONST_PAT)
if i!=-1: const=b[i:i+32]
if blob is None:
j=b.find(BLOB_SIG)
if j!=-1: blob=b[j:j+BLOB_LEN]
if blob and const: break
os.kill(p.pid, signal.SIGKILL)
if not (blob and const):
raise RuntimeError("cannot find blob/const (maybe /proc/pid/mem blocked?)")
return blob,const

def main():
blob,const = extract_runtime(BIN)
exp = extract_expected(blob)
mask = run_blob(blob,const,b"\0"*48)
inp = b"".join(struct.pack("<I",(mask[i]^exp[i])&0xFFFFFFFF) for i in range(12))
try: inner = inp.decode("ascii")
except: inner = inp.decode("latin1")
print(f"flag{{{inner}}}")

if __name__=="__main__":
main()

CISCN
https://boke-git-main-huang-chaos-projects.vercel.app/2025/12/28/CISCN/
作者
[object Object]
发布于
2025年12月28日
许可协议