【2026春节】解题领红包大部分题题解

admin 2026-03-27 01:29:53 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

文章总结: 本文分享了2026年春节活动中大部分题目的解题方法,主要包括安卓初级题和两篇Windows初级题。其中,Windows初级题的解法涵盖了使用IDAPro分析C/C++代码、借助AI工具(如Gemini)进行逆向分析,以及通过pyinstxtractor工具处理PyInstaller编译的Python程序等多种技巧。 综合评分: 85 文章分类: CTF,逆向分析,WEB安全,安全工具,实战经验


利用该哈希值,可以直接跳过flag的获取,直接按正常流程解密(就把上面得到的crc64哈希值和算法给它):

 复制代码 隐藏代码
"""
Decrypt a CM26-encrypted PNG file.

Format:
- 0x00..0x03: magic (b"CM26")
- 0x04..0x07: CRC32 (little-endian) of padded plaintext
- 0x08..0x0F: initial 64-bit feedback value (little-endian)
- 0x10..end : ciphertext blocks (8 bytes each)
"""

from __future__ import annotations

import argparse
import struct
import sys
import zlib
from pathlib import Path

MASK64 = 0xFFFFFFFFFFFFFFFF
INITIAL_KEY = 0x55A4F867BA4475DD

AES_SBOX = [
    0x63, 0x7C, 0x77, 0x7B, 0xF2, 0x6B, 0x6F, 0xC5, 0x30, 0x01, 0x67, 0x2B, 0xFE, 0xD7, 0xAB, 0x76,
    0xCA, 0x82, 0xC9, 0x7D, 0xFA, 0x59, 0x47, 0xF0, 0xAD, 0xD4, 0xA2, 0xAF, 0x9C, 0xA4, 0x72, 0xC0,
    0xB7, 0xFD, 0x93, 0x26, 0x36, 0x3F, 0xF7, 0xCC, 0x34, 0xA5, 0xE5, 0xF1, 0x71, 0xD8, 0x31, 0x15,
    0x04, 0xC7, 0x23, 0xC3, 0x18, 0x96, 0x05, 0x9A, 0x07, 0x12, 0x80, 0xE2, 0xEB, 0x27, 0xB2, 0x75,
    0x09, 0x83, 0x2C, 0x1A, 0x1B, 0x6E, 0x5A, 0xA0, 0x52, 0x3B, 0xD6, 0xB3, 0x29, 0xE3, 0x2F, 0x84,
    0x53, 0xD1, 0x00, 0xED, 0x20, 0xFC, 0xB1, 0x5B, 0x6A, 0xCB, 0xBE, 0x39, 0x4A, 0x4C, 0x58, 0xCF,
    0xD0, 0xEF, 0xAA, 0xFB, 0x43, 0x4D, 0x33, 0x85, 0x45, 0xF9, 0x02, 0x7F, 0x50, 0x3C, 0x9F, 0xA8,
    0x51, 0xA3, 0x40, 0x8F, 0x92, 0x9D, 0x38, 0xF5, 0xBC, 0xB6, 0xDA, 0x21, 0x10, 0xFF, 0xF3, 0xD2,
    0xCD, 0x0C, 0x13, 0xEC, 0x5F, 0x97, 0x44, 0x17, 0xC4, 0xA7, 0x7E, 0x3D, 0x64, 0x5D, 0x19, 0x73,
    0x60, 0x81, 0x4F, 0xDC, 0x22, 0x2A, 0x90, 0x88, 0x46, 0xEE, 0xB8, 0x14, 0xDE, 0x5E, 0x0B, 0xDB,
    0xE0, 0x32, 0x3A, 0x0A, 0x49, 0x06, 0x24, 0x5C, 0xC2, 0xD3, 0xAC, 0x62, 0x91, 0x95, 0xE4, 0x79,
    0xE7, 0xC8, 0x37, 0x6D, 0x8D, 0xD5, 0x4E, 0xA9, 0x6C, 0x56, 0xF4, 0xEA, 0x65, 0x7A, 0xAE, 0x08,
    0xBA, 0x78, 0x25, 0x2E, 0x1C, 0xA6, 0xB4, 0xC6, 0xE8, 0xDD, 0x74, 0x1F, 0x4B, 0xBD, 0x8B, 0x8A,
    0x70, 0x3E, 0xB5, 0x66, 0x48, 0x03, 0xF6, 0x0E, 0x61, 0x35, 0x57, 0xB9, 0x86, 0xC1, 0x1D, 0x9E,
    0xE1, 0xF8, 0x98, 0x11, 0x69, 0xD9, 0x8E, 0x94, 0x9B, 0x1E, 0x87, 0xE9, 0xCE, 0x55, 0x28, 0xDF,
    0x8C, 0xA1, 0x89, 0x0D, 0xBF, 0xE6, 0x42, 0x68, 0x41, 0x99, 0x2D, 0x0F, 0xB0, 0x54, 0xBB, 0x16,
]

defrol64(value: int, shift: int = 3) -> int:
&nbsp; &nbsp;&nbsp;return&nbsp;((value << shift) & MASK64) | (value >> (64&nbsp;- shift))

defsbox_mix_64(value:&nbsp;int) ->&nbsp;int:
&nbsp; &nbsp;&nbsp;"""Apply AES S-Box over 8 bytes from high byte to low byte."""
&nbsp; &nbsp;&nbsp;for&nbsp;_&nbsp;inrange(8):
&nbsp; &nbsp; &nbsp; &nbsp; top = (value >>&nbsp;56) &&nbsp;0xFF
&nbsp; &nbsp; &nbsp; &nbsp; value = ((value <<&nbsp;8) & MASK64) | AES_SBOX[top]
&nbsp; &nbsp;&nbsp;return&nbsp;value

defpkcs7_unpad(data:&nbsp;bytes, block_size:&nbsp;int&nbsp;=&nbsp;8) ->&nbsp;bytes:
&nbsp; &nbsp;&nbsp;ifnot&nbsp;data:
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;raise&nbsp;ValueError("empty plaintext")

&nbsp; &nbsp; pad_len = data[-1]
&nbsp; &nbsp;&nbsp;if&nbsp;pad_len <&nbsp;1or&nbsp;pad_len > block_size:
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;raise&nbsp;ValueError(f"invalid padding length:&nbsp;{pad_len}")

&nbsp; &nbsp;&nbsp;if&nbsp;data[-pad_len:] !=&nbsp;bytes([pad_len]) * pad_len:
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;raise&nbsp;ValueError("invalid PKCS#7&nbsp;padding bytes")

&nbsp; &nbsp;&nbsp;return&nbsp;data[:-pad_len]

defdecrypt_cm26(data:&nbsp;bytes, key:&nbsp;int&nbsp;= INITIAL_KEY, strict_magic:&nbsp;bool&nbsp;=&nbsp;True) ->&nbsp;bytes:
&nbsp; &nbsp;&nbsp;iflen(data) <&nbsp;0x10:
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;raise&nbsp;ValueError("input too short")

&nbsp; &nbsp; magic = data[:4]
&nbsp; &nbsp;&nbsp;if&nbsp;strict_magic&nbsp;and&nbsp;magic !=&nbsp;b"CM26":
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;raise&nbsp;ValueError(f"unexpected magic:&nbsp;{magic!r}")

&nbsp; &nbsp; expected_crc = struct.unpack_from("<I", data,&nbsp;4)[0]
&nbsp; &nbsp; feedback = struct.unpack_from("<Q", data,&nbsp;8)[0]
&nbsp; &nbsp; ciphertext = data[0x10:]

&nbsp; &nbsp;&nbsp;iflen(ciphertext) %&nbsp;8&nbsp;!=&nbsp;0:
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;raise&nbsp;ValueError("ciphertext length is not a multiple of 8")

&nbsp; &nbsp; state = key & MASK64
&nbsp; &nbsp; padded_plain =&nbsp;bytearray()

&nbsp; &nbsp;&nbsp;for&nbsp;i&nbsp;inrange(0,&nbsp;len(ciphertext),&nbsp;8):
&nbsp; &nbsp; &nbsp; &nbsp; block = struct.unpack_from("<Q", ciphertext, i)[0]
&nbsp; &nbsp; &nbsp; &nbsp; state = sbox_mix_64(rol64(state,&nbsp;3))
&nbsp; &nbsp; &nbsp; &nbsp; plain_qword = block ^ state ^ feedback
&nbsp; &nbsp; &nbsp; &nbsp; padded_plain += struct.pack("<Q", plain_qword)
&nbsp; &nbsp; &nbsp; &nbsp; feedback = block

&nbsp; &nbsp; calc_crc = zlib.crc32(padded_plain) &&nbsp;0xFFFFFFFF
&nbsp; &nbsp;&nbsp;if&nbsp;calc_crc != expected_crc:
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;raise&nbsp;ValueError(
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;f"CRC32 mismatch: expected 0x{expected_crc:08x}, got 0x{calc_crc:08x}"
&nbsp; &nbsp; &nbsp; &nbsp; )

&nbsp; &nbsp;&nbsp;return&nbsp;pkcs7_unpad(bytes(padded_plain), block_size=8)

defmain() ->&nbsp;int:
&nbsp; &nbsp; parser = argparse.ArgumentParser(description="Decrypt CM26-encrypted PNG")
&nbsp; &nbsp; parser.add_argument("input",&nbsp;type=Path,&nbsp;help="encrypted file path")
&nbsp; &nbsp; parser.add_argument("output",&nbsp;type=Path, nargs="?", default=Path("flag.png"),&nbsp;help="output PNG path")
&nbsp; &nbsp; parser.add_argument(
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"--key",
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;type=lambda&nbsp;x:&nbsp;int(x,&nbsp;0),
&nbsp; &nbsp; &nbsp; &nbsp; default=INITIAL_KEY,
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;help="64-bit initial key (default: 0x55A4F867BA4475DD)",
&nbsp; &nbsp; )
&nbsp; &nbsp; parser.add_argument("--no-magic-check", action="store_true",&nbsp;help="skip CM26 magic validation")
&nbsp; &nbsp; args = parser.parse_args()

&nbsp; &nbsp; encrypted = args.input.read_bytes()
&nbsp; &nbsp; plain = decrypt_cm26(encrypted, key=args.key, strict_magic=not&nbsp;args.no_magic_check)

&nbsp; &nbsp;&nbsp;ifnot&nbsp;plain.startswith(b"\x89PNG\r\n\x1a\n"):
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;print("[!] Decrypted data does not start with PNG signature", file=sys.stderr)

&nbsp; &nbsp; args.output.write_bytes(plain)
&nbsp; &nbsp;&nbsp;print(f"[+] Decrypted OK:&nbsp;{args.output}&nbsp;({len(plain)}&nbsp;bytes)")
&nbsp; &nbsp;&nbsp;return0

if&nbsp;__name__ ==&nbsp;"__main__":
&nbsp; &nbsp;&nbsp;raise&nbsp;SystemExit(main())

这样就得到了一张解密好的图片:

可用notepad4打开看到明文flag:flag{EncrypTIoN_Is_haRd_52p0jIE_2o26_m62Tc4uj78maAq1C}

安卓中级题

我不会,但朋友用cc跑了7小时硬是将算法静态分析出来了。

但奇怪的是正确的flag投喂仍然不对,我用frida调用了setDebugBypass也不行。

web题

js代码简析

先去看一眼js层语音是怎么合成的,发现

  1. 函数(uid, voice),返回值:{a:音频数据,h:一串hash值}
  2. 函数checkCode是将输入的不含flag{}的{}中的数据进行0x2026次SHA-256,并与h进行比较。

这就不难猜测,h值是通过验证码文本进行0x2026次SHA-256得到的,a也与验证码文本有关。

还发现了一个关键导入函数wbg.__wbg_getRandomValues_1c61fac11405ffdc,使用了crypto.getRandomValues

于是hook crypto.getRandomValues将值固定

&nbsp;复制代码&nbsp;隐藏代码
(function() {
&nbsp; &nbsp;&nbsp;const&nbsp;rawCrypto =&nbsp;window.crypto;

&nbsp; &nbsp;&nbsp;const&nbsp;cryptoProxy =&nbsp;newProxy(rawCrypto, {
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;get(target, prop) {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// FIX 1: Only pass target and prop.
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// Do NOT pass 'receiver' (the 3rd argument).
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;const&nbsp;value =&nbsp;Reflect.get(target, prop);

&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// Hook logic for getRandomValues
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(prop ===&nbsp;'getRandomValues') {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;returnfunction(typedArray) {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;console.log(`%c[Hook] 拦截成功! 长度:&nbsp;${typedArray.length}`,&nbsp;'color:&nbsp;#2ed573; font-weight: bold;');
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; typedArray.fill(1);&nbsp;// Your custom logic

&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;const&nbsp;hexStr =&nbsp;Array.from(typedArray)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .map(b&nbsp;=>&nbsp;b.toString(16).padStart(2,&nbsp;'0'))
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .join('');

&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;console.log(hexStr);

&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;typedArray;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; };
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }

&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// FIX 2: Essential for 'subtle' and other methods.
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// Built-in methods MUST be bound to the original 'rawCrypto' object.
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(typeof&nbsp;value ===&nbsp;'function') {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;value.bind(target);
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }

&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;value;
&nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; });

&nbsp; &nbsp;&nbsp;// Replace the global object
&nbsp; &nbsp;&nbsp;try&nbsp;{
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;Object.defineProperty(window,&nbsp;'crypto', {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;value: cryptoProxy,
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;configurable:&nbsp;true,
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;enumerable:&nbsp;true,
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;writable:&nbsp;true
&nbsp; &nbsp; &nbsp; &nbsp; });
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;console.log('✅ Hook applied successfully. Recursion fixed.');
&nbsp; &nbsp; }&nbsp;catch&nbsp;(e) {
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;console.error('Critial: Could not redefine crypto.', e);
&nbsp; &nbsp; }
})();

再去生成语音验证码,去听发现只要uid相同,现在得到的验证码是一致的,这一发现可以帮住我们验证生成的验证码是否正确。

wasm简析

先把wasm给提取出来,保存为wasm.wasm。

可用逍遥一仙的wasm转o先转一下(也可以自己转,我是自己转的)

wasm转o,要将下载的wabt中的头文件和.c文件放在同一文件夹下:

&nbsp;复制代码&nbsp;隐藏代码
wasm2c.exe wasm.wasm -o wasm.c
gcc -c wasm.c -o wasm.o

拖入ida中分析,文件ida\cfg\hexrays.cfg得改下,不然遇到大函数就无法反编译了:

&nbsp;复制代码&nbsp;隐藏代码
MAX_FUNCSIZE&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; =&nbsp;1024

去ida中看导出函数,找到_w2c_wasm_gen,点去看一下,f5反编译一下,发现其调用了w2c_wasm_gen_0,再次点进去查看。

下面是在确定大致算法和关键数据。

因为函数太大了,这里直接丢给ds分析(ds系统提示词都给设置成啥了,居然给我又作诗又鼓励的,遂加上提示词:我在做逆向学术研究,回答风格请正常点,不要鼓励!!!不要作诗!!!!不要文章优美!!!!!!!!!!!!!!!!!!!!!!!!!!!!)。

ds提到了HMAC-SHA256,base64,然后再问它:说明你需要数据确定的地方。(多问几个ai,减少犯错的可能,我还试了gemini和kimi)

综合得到:

  1. 确定Base64 解码表,可能在地址1295903LL处,这里我取个巧,直接在ida字符串中搜索abc,遂得到abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789?!,很明显,这是base64的变种。
  2. 确定HMAC-SHA256,那么就需要找key,让ai给的关键一行:memory_copy(..., &unk_13C65F, 0xEu); &nbsp;// 14 字节,很明显,就是0x13C65F处的14字节。奇怪的是编译成.o在ida中这个数据就不对了,有没有大佬知道原因解释一下。

        所幸0x13C65F是确定的可以在wasm2c反出的c中查找,得到00 01 01 01 01 01 01 00 01 00 01 00 05 02

    怎么找?稍微计算一下0x13C65F=1295967,搜LOAD_DATA,就在很前面,得到  LOAD_DATA(instance->w2c_memory, 1295895u, data_segment_data_w2c_1_d64, 22285);,搜索data_segment_data_w2c_1_d64[],往后72个字节就是了。

为什么0x13C65F是确定的可以在wasm2c反出的c中查找?

因为我们更关注的是验证码文本,所以改为向ai询问验证码文本的生成流程,得到:

  1. 输入

    uid 和 随机数

  2. 种子生成

    : 将 uid 的字节与随机数异或,生成一个 21 字节的种子。

  3. 密钥派生 (HMAC-SHA256)

    : 使用硬编码的密钥 (00...05 02) 对种子进行 HMAC-SHA256 计算,得到一个 32 字节的派生密钥(截取的前面)。

  4. 验证码扩展

    : 对种子和 32 字节的派生密钥一起合并扩展为一个更长的字节序列。

  5. 最终编码

    : 将扩展后的字节序列用自定义的 Base64 字符集 (abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789?!) 编码成 50 个字符的字符串。这就是最终的 code

得到代码

其实得到关键数据后就可以偷波懒了,直接将gen的伪代码和关键数据和题目信息一起给gpt-5.3 codex,推理开超高,说要得到验证码文本,不一会儿就出了。

&nbsp;复制代码&nbsp;隐藏代码
from&nbsp;__future__&nbsp;import&nbsp;annotations

import&nbsp;argparse
import&nbsp;hashlib
import&nbsp;hmac
import&nbsp;os
from&nbsp;dataclasses&nbsp;import&nbsp;dataclass

# Extracted from gen.c:4318-4321
CUSTOM_B64_ALPHABET =&nbsp;"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789?!"

# Extracted from gen.c:338 + gen.c:4323-4336 (14 bytes copied from unk_13C65F)
HMAC_KEY =&nbsp;bytes([
&nbsp; &nbsp;&nbsp;0x00,&nbsp;0x01,&nbsp;0x01,&nbsp;0x01,&nbsp;0x01,&nbsp;0x01,&nbsp;0x01,
&nbsp; &nbsp;&nbsp;0x00,&nbsp;0x01,&nbsp;0x00,&nbsp;0x01,&nbsp;0x00,&nbsp;0x05,&nbsp;0x02,
])

@dataclass(frozen=True)
classCodeMaterial:
&nbsp; &nbsp; uid:&nbsp;int
&nbsp; &nbsp; random17:&nbsp;bytes
&nbsp; &nbsp; payload21:&nbsp;bytes
&nbsp; &nbsp; signature16:&nbsp;bytes
&nbsp; &nbsp; payload37:&nbsp;bytes
&nbsp; &nbsp; code:&nbsp;str

defcustom_b64_encode_no_padding(data:&nbsp;bytes, alphabet:&nbsp;str&nbsp;= CUSTOM_B64_ALPHABET) ->&nbsp;str:
&nbsp; &nbsp;&nbsp;iflen(alphabet) !=&nbsp;64:
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;raise&nbsp;ValueError("alphabet must contain exactly 64 characters")

&nbsp; &nbsp; out:&nbsp;list[str] = []
&nbsp; &nbsp; acc =&nbsp;0
&nbsp; &nbsp; bits =&nbsp;0

&nbsp; &nbsp;&nbsp;for&nbsp;b&nbsp;in&nbsp;data:
&nbsp; &nbsp; &nbsp; &nbsp; acc = ((acc <<&nbsp;8) | b) &&nbsp;0xFFFFFFFFFFFFFFFF
&nbsp; &nbsp; &nbsp; &nbsp; bits +=&nbsp;8
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;while&nbsp;bits >=&nbsp;6:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; bits -=&nbsp;6
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; out.append(alphabet[(acc >> bits) &&nbsp;0x3F])

&nbsp; &nbsp;&nbsp;if&nbsp;bits:
&nbsp; &nbsp; &nbsp; &nbsp; out.append(alphabet[(acc << (6&nbsp;- bits)) &&nbsp;0x3F])

&nbsp; &nbsp;&nbsp;return"".join(out)

defbuild_payload21(uid:&nbsp;int, random17:&nbsp;bytes) ->&nbsp;bytes:
&nbsp; &nbsp;&nbsp;iflen(random17) !=&nbsp;17:
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;raise&nbsp;ValueError("random17 must be exactly 17 bytes")

&nbsp; &nbsp; uid_le = (uid &&nbsp;0xFFFFFFFF).to_bytes(4,&nbsp;"little")
&nbsp; &nbsp; head4 =&nbsp;bytes(uid_le[i] ^ random17[i]&nbsp;for&nbsp;i&nbsp;inrange(4))
&nbsp; &nbsp;&nbsp;return&nbsp;head4 + random17

defgenerate_code_material(uid:&nbsp;int, random17:&nbsp;bytes&nbsp;|&nbsp;None&nbsp;=&nbsp;None) -> CodeMaterial:
&nbsp; &nbsp;&nbsp;if&nbsp;random17&nbsp;isNone:
&nbsp; &nbsp; &nbsp; &nbsp; random17 = os.urandom(17)
&nbsp; &nbsp; payload21 = build_payload21(uid, random17)

&nbsp; &nbsp;&nbsp;# Inferred from gen.c:359-415 (ipad/opad) + gen.c:2997-3014 (SHA-256 rounds).
&nbsp; &nbsp; signature16 = hmac.new(HMAC_KEY, payload21, hashlib.sha256).digest()[:16]

&nbsp; &nbsp; payload37 = payload21 + signature16
&nbsp; &nbsp; code = custom_b64_encode_no_padding(payload37)

&nbsp; &nbsp;&nbsp;return&nbsp;CodeMaterial(
&nbsp; &nbsp; &nbsp; &nbsp; uid=uid &&nbsp;0xFFFFFFFF,
&nbsp; &nbsp; &nbsp; &nbsp; random17=random17,
&nbsp; &nbsp; &nbsp; &nbsp; payload21=payload21,
&nbsp; &nbsp; &nbsp; &nbsp; signature16=signature16,
&nbsp; &nbsp; &nbsp; &nbsp; payload37=payload37,
&nbsp; &nbsp; &nbsp; &nbsp; code=code,
&nbsp; &nbsp; )

defcheck_hash(code:&nbsp;str, rounds:&nbsp;int&nbsp;=&nbsp;0x2026) ->&nbsp;str:
&nbsp; &nbsp; current = code.encode("utf-8")
&nbsp; &nbsp;&nbsp;for&nbsp;_&nbsp;inrange(rounds):
&nbsp; &nbsp; &nbsp; &nbsp; current = hashlib.sha256(current).digest()
&nbsp; &nbsp;&nbsp;return&nbsp;current.hex()

defparse_args() -> argparse.Namespace:
&nbsp; &nbsp; parser = argparse.ArgumentParser(description="Generate captcha code text from uid + random17")
&nbsp; &nbsp; parser.add_argument("uid",&nbsp;type=lambda&nbsp;x:&nbsp;int(x,&nbsp;0),&nbsp;help="User ID (supports decimal/hex, e.g. 123 or 0x7b)")
&nbsp; &nbsp; parser.add_argument(
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"--rand17-hex",
&nbsp; &nbsp; &nbsp; &nbsp; dest="rand17_hex",
&nbsp; &nbsp; &nbsp; &nbsp; default=None,
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;help="17-byte random source as hex (34 hex chars). Omit to use os.urandom(17)",
&nbsp; &nbsp; )
&nbsp; &nbsp; parser.add_argument(
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"--with-hash",
&nbsp; &nbsp; &nbsp; &nbsp; action="store_true",
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;help="Also print the frontend verification hash (SHA-256 repeated 0x2026 rounds)",
&nbsp; &nbsp; )
&nbsp; &nbsp;&nbsp;return&nbsp;parser.parse_args()

defmain() ->&nbsp;None:
&nbsp; &nbsp; args = parse_args()

&nbsp; &nbsp; random17 =&nbsp;None
&nbsp; &nbsp;&nbsp;if&nbsp;args.rand17_hex&nbsp;isnotNone:
&nbsp; &nbsp; &nbsp; &nbsp; raw =&nbsp;bytes.fromhex(args.rand17_hex)
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;iflen(raw) !=&nbsp;17:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;raise&nbsp;SystemExit("--rand17-hex must be exactly 34 hex chars (17 bytes)")
&nbsp; &nbsp; &nbsp; &nbsp; random17 = raw

&nbsp; &nbsp; material = generate_code_material(args.uid, random17)

&nbsp; &nbsp;&nbsp;print(f"uid &nbsp; &nbsp; &nbsp; &nbsp; :&nbsp;{material.uid}")
&nbsp; &nbsp;&nbsp;print(f"random17 &nbsp; &nbsp;:&nbsp;{material.random17.hex()}")
&nbsp; &nbsp;&nbsp;print(f"payload21 &nbsp; :&nbsp;{material.payload21.hex()}")
&nbsp; &nbsp;&nbsp;print(f"signature16 :&nbsp;{material.signature16.hex()}")
&nbsp; &nbsp;&nbsp;print(f"payload37 &nbsp; :&nbsp;{material.payload37.hex()}")
&nbsp; &nbsp;&nbsp;print(f"code &nbsp; &nbsp; &nbsp; &nbsp;:&nbsp;{material.code}")
&nbsp; &nbsp;&nbsp;print(f"flag format : flag{{{material.code}}}")

&nbsp; &nbsp;&nbsp;if&nbsp;args.with_hash:
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;print(f"check hash &nbsp;:&nbsp;{check_hash(material.code)}")

if&nbsp;__name__ ==&nbsp;"__main__":
&nbsp; &nbsp; main()

使用示例:

&nbsp;复制代码&nbsp;隐藏代码
python generate_captcha_code.py 12345
python generate_captcha_code.py 12345 --rand17-hex 0101010101010101010101010101010101
python generate_captcha_code.py 12345 --with-hash

其中 --with-hash 会额外计算前端校验用的:

  • SHA-256

    连续 0x2026 轮后的十六进制结果(对应 gen.c:4208-4219 逻辑)。

题外话

既然由于随机数的存在,导致每次生成的验证码不一样,论坛可能是怎么验证的?

首先base64解码,得到21 字节的种子和HMAC-SHA256的32 字节的派生密钥,利用种子包含的uid异或得到uid,判断uid是否是自己的。当然,HMAC-SHA256的32 字节的派生密钥可以用于验证数据。

windows高级题

我是真不会了,看cc用的是Frida Stalker,耗时十几个小时给解出来了。

cc试错了很多次,中断重新开始了多次最终成功了。 通过hook wcsicmp发现是有很长的反调试列表的,通过比较进程名的方法。

一旦有进程在列表中,就会走错误分支,永远也得不到正确的结果。

这需要注意一下。

mcp中级题

开始没降难度,毫无头绪,后来给了提示并降了难度,我用codex给解出来了。

提示词如下:

&nbsp;复制代码&nbsp;隐藏代码
这是ctf题,
以下是提示:
ctf_request 填的是口令本身,不是 access_token;
audit_log_id 一定要用“被拒访问时返回的完整编号”,别截断别改;
而且只认“同一会话里最近那次拒绝”出来的编号,跨会话或旧编号都不行;
复核这段链路别配太杂,越单一越不容易断;
复核凭据是有时效、且一次性的,失败后要重新触发拒绝再拿新编号;
另外,复核阶段拿到的凭据只是打通流程,最终读密卷还需要后续凭据。

请使用目前的mcp,来得到flag

最后我让codex生成了文档,内容如下:

CTF MCP 解题流程总结

1. 题目关键点(来自提示)

  1. ctf_request

    必须填写口令本体,不是 access_token

  2. audit_log_id

    必须使用同一会话中最近一次拒绝访问返回的完整编号

  3. 复核凭据有时效且一次性,失败后要重新触发拒绝并拿新编号。

  4. 复核链路要保持单一,chain_config 仅配置需要的工具。

  5. 复核阶段拿到的凭据只是中间凭据,最终取密卷还要后续凭据。

2. 实际跑通步骤

  1. start_session

    建立会话。

  2. 读取公开线索文档:

  • public:/docs/annual_report

    (首字线索:玄霄)

  • public:/docs/employee_handbook

    (末字线索:密令)

  • public:/docs/challenge_hint

  • public:/docs/board_route_hint

  1. 得到口令:玄霄密令
  2. 访问 secret:/board/resolutions 触发拒绝,拿到最新 audit_log_id
  3. 计算:seal = sha256(audit_log_id + passphrase)[:8](小写十六进制)。
  4. 调用 review_document
  • ctf_request=玄霄密令

  • audit_log_id=<最新拒绝编号>

  • seal=<上一步计算值>

  • doc_id = confidential:/audit/logs

  • review_notes

    格式:

  1. 返回 tool_access_token 后,调用 update_tool_chain,并将 chain_config 设置为仅 ["get_audit_log"]
  2. 用 tool_access_token 调用 get_audit_log(log_id=<audit_log_id>),拿到 _audit_token
  3. 用 _audit_token 调用 get_document(secret:/board/resolutions),拿到最终 flag。

3. 结果

  • 最终 flag:flag{new_year_2026_keep_warm}

  • compliance_blob_b64

    中的 FLAG{HAPPYNEWYEAR-WUAIPOJIE} 是干扰信息,不是最终答案。

4. 常见失败原因

  1. 把 ctf_request 误填成 access_token

  2. audit_log_id

    不是“同会话最近一次拒绝”的编号。

  3. audit_log_id

    复制时被截断或改写。

  4. chain_config

    混入了 get_audit_log 以外的工具导致凭据作废。

  5. 失败后继续用旧凭据(应重新触发拒绝并重跑)。

感受

现在ai发展的真迅猛,很多逆向都可以完全利用ai秒杀了,我们也应学会利用ai。

已经不敢想象在过个几年,ai会发展成什么样子了。

附件

https://wwavz.lanzout.com/ivS7W3jidv8d

-官方论坛

www.52pojie.cn

👆👆👆

公众号设置“星标”,不会错过新的消息通知

开放注册、精华文章和周边活动等公告


免责声明:

本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。

任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。

本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我

本文转载自:吾爱破解论坛 吾爱pojie 吾爱pojie《【2026春节】解题领红包大部分题题解》

春分至,龙抬头 网络安全文章

春分至,龙抬头

文章总结: 综合评分: 0 文章分类: 其他春分至,龙抬头 金盾检测股份 2026年3月20日 08:30 江苏免责声明:本文所载程序、技术方法仅面向合法合规的
评论:0   参与:  0