无名 发表于 2026-3-4 07:26

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

本帖最后由 无名 于 2026-3-4 10:02 编辑

# 前言

我比较菜,很多都是靠ai分析才完成的,毕竟都6202年了。

windows高级题和所有的安卓题我都不会,所以文章中不包含这些题的解法(附件里有cc的解法)。

有条件的可以上cc,今年这些题目都可以**全自动**解出来。

我朋友用cc解没有给任何工具,都是cc自己想办法解决的,额度至少花了几百刀吧。

附件中是cc最后生成的工具和文档。


# 安卓初级题

安卓初级题我不会,那就玩嘛,两分钟就出了,参考[文章](https://zhuanlan.zhihu.com/p/1926010242463949873),最后点击宝箱,得到:`flag{Wu41_P0j13_2026_Spr1ng_F3st1v4l}`

ps:

其实也可以上cc,直接秒杀。

这里上张图:





# 第一篇windows初级题

首先查壳,无壳(其实初级题一般也不会加壳,主要是为了看编译信息),这里可以断定为c/c++编写的,拖入ida中,f5,复制代码,接下来就交给gemini了。

很快就发现关键函数:

```
_BYTE *__cdecl sub_401620(int a1)
{
_BYTE *result; // eax

*(_DWORD *)a1 = 758280311;
*(_DWORD *)(a1 + 4) = 1663511336;
*(_DWORD *)(a1 + 8) = 1880974179;
*(_DWORD *)(a1 + 12) = 494170226;
*(_DWORD *)(a1 + 16) = 842146570;
*(_DWORD *)(a1 + 20) = 657202491;
*(_DWORD *)(a1 + 24) = 658185525;
*(_BYTE *)(a1 + 30) = 99;
*(_WORD *)(a1 + 28) = 12323;
result = (_BYTE *)a1;
do
    *result++ ^= 0x42u;
while ( result != (_BYTE *)(a1 + 31) );
*(_BYTE *)(a1 + 31) = 0;
return result;
}
```

这里喂给ds ,注意开深度思考(如果用gemini就得开pro)

提示词:上面的代码加上`仔细分析,这个 Flag是什么`



很快就得到flag:`52pojie!!!_2026_Happy_new_year!`



# 第二篇windows初级题

首先查壳,发现是pyinstaller编译的(其实看图标也能猜个七七八八)。

那么就上(https://github.com/extremecoders-re/pyinstxtractor),可以得到一个crackme_easy.pyc。

我尝试所有pycdc,结果失败了。

## 法一:

既然是初级题,那就应该很简单,于是我使用notepad4打开,文件编码选择utf-8,这样可以正确看到中文字符串和中文注释,发现了一个敏感数据:

```
flagz(e3w+fiRvfW18fnx4ZAZ6Pj43YwB9OWMXfXo8Dg4O)�base64� b64decode)
```

于是我把贴近这的上下文都复制粘贴给gemini,经过它的一通分析,最终得到flag:`52p0j!3#2026*H4ppy-N3w-Y34r@@@`

ps:

这里的算法是:对`e3w+fiRvfW18fnx4ZAZ6Pj43YwB9OWMXfXo8Dg4O`base64解码,然后异或解密,gemini通过推测(理)猜测的Key: 0x4E 了

## 法二:

可以看到python314.dll,右键查看详细信息发现是python 3.14.2版本,去官网下载(https://www.python.org/ftp/pytho ... 4.2-embed-amd64.zip)。

然后利用该版本python,运行下面的代码:

```
import dis
import marshal

with open('crackme_easy.pyc', 'rb') as f:
    f.read(16)# 跳过文件头
    code = marshal.load(f)
    dis.dis(code)
```

得到:

```
   0         RESUME                   0

   4         LOAD_SMALL_INT         0
               LOAD_CONST               1 (None)
               IMPORT_NAME            0 (hashlib)
               STORE_NAME               0 (hashlib)

   5         LOAD_SMALL_INT         0
               LOAD_CONST               1 (None)
               IMPORT_NAME            1 (base64)
               STORE_NAME               1 (base64)

   6         LOAD_SMALL_INT         0
               LOAD_CONST               1 (None)
               IMPORT_NAME            2 (sys)
               STORE_NAME               2 (sys)

   8         LOAD_CONST               2 (<code object xor_decrypt at 0x000001C336305830, file "crackme_easy.py", line 8>)
               MAKE_FUNCTION
               STORE_NAME               3 (xor_decrypt)

15         LOAD_CONST               3 (<code object get_encrypted_flag at 0x000001C3362D7AA0, file "crackme_easy.py", line 15>)
               MAKE_FUNCTION
               STORE_NAME               4 (get_encrypted_flag)

21         LOAD_CONST               4 (<code object generate_flag at 0x000001C336305E30, file "crackme_easy.py", line 21>)
               MAKE_FUNCTION
               STORE_NAME               5 (generate_flag)

32         LOAD_CONST               5 (<code object calculate_checksum at 0x000001C3362B63A0, file "crackme_easy.py", line 32>)
               MAKE_FUNCTION
               STORE_NAME               6 (calculate_checksum)

39         LOAD_CONST               6 (<code object hash_string at 0x000001C33633BEB0, file "crackme_easy.py", line 39>)
               MAKE_FUNCTION
               STORE_NAME               7 (hash_string)

43         LOAD_CONST               7 (<code object verify_flag at 0x000001C3366444E0, file "crackme_easy.py", line 43>)
               MAKE_FUNCTION
               STORE_NAME               8 (verify_flag)

57         LOAD_CONST               8 (<code object fake_check_1 at 0x000001C3362B3A30, file "crackme_easy.py", line 57>)
               MAKE_FUNCTION
               STORE_NAME               9 (fake_check_1)

62         LOAD_CONST               9 (<code object fake_check_2 at 0x000001C3364B0430, file "crackme_easy.py", line 62>)
               MAKE_FUNCTION
               STORE_NAME            10 (fake_check_2)

67         LOAD_CONST            10 (<code object main at 0x000001C336539400, file "crackme_easy.py", line 67>)
               MAKE_FUNCTION
               STORE_NAME            11 (main)

108         LOAD_NAME               12 (__name__)
               LOAD_CONST            11 ('__main__')
               COMPARE_OP            88 (bool(==))
               POP_JUMP_IF_FALSE       11 (to L3)
               NOT_TAKEN

109         NOP

110   L1:   LOAD_NAME               11 (main)
               PUSH_NULL
               CALL                     0
               POP_TOP
       L2:   LOAD_CONST               1 (None)
               RETURN_VALUE

108   L3:   LOAD_CONST               1 (None)
               RETURN_VALUE

--   L4:   PUSH_EXC_INFO

111         LOAD_NAME               13 (KeyboardInterrupt)
               CHECK_EXC_MATCH
               POP_JUMP_IF_FALSE       31 (to L6)
               NOT_TAKEN
               POP_TOP

112         LOAD_NAME               14 (print)
               PUSH_NULL
               LOAD_CONST            12 ('\n\n[!] Interrupted by user')
               CALL                     1
               POP_TOP

113         LOAD_NAME                2 (sys)
               LOAD_ATTR               30 (exit)
               PUSH_NULL
               LOAD_SMALL_INT         0
               CALL                     1
               POP_TOP
       L5:   POP_EXCEPT
               LOAD_CONST               1 (None)
               RETURN_VALUE

111   L6:   RERAISE                  0

--   L7:   COPY                     3
               POP_EXCEPT
               RERAISE                  1
ExceptionTable:
L1 to L2 -> L4
L4 to L5 -> L7 lasti
L6 to L7 -> L7 lasti

Disassembly of <code object xor_decrypt at 0x000001C336305830, file "crackme_easy.py", line 8>:
8         RESUME                   0

10         LOAD_GLOBAL            1 (bytearray + NULL)
            CALL                     0
            STORE_FAST               2 (result)

11         LOAD_GLOBAL            3 (enumerate + NULL)
            LOAD_FAST_BORROW         0 (data)
            CALL                     1
            GET_ITER
      L1:   FOR_ITER                42 (to L2)
            UNPACK_SEQUENCE          2
            STORE_FAST_STORE_FAST   52 (i, byte)

12         LOAD_FAST_BORROW         2 (result)
            LOAD_ATTR                5 (append + NULL|self)
            LOAD_FAST_BORROW_LOAD_FAST_BORROW 65 (byte, key)
            BINARY_OP               12 (^)
            LOAD_FAST_BORROW         3 (i)
            LOAD_SMALL_INT         255
            BINARY_OP                1 (&)
            BINARY_OP               12 (^)
            CALL                     1
            POP_TOP
            JUMP_BACKWARD         44 (to L1)

11   L2:   END_FOR
            POP_ITER

13         LOAD_FAST_BORROW         2 (result)
            LOAD_ATTR                7 (decode + NULL|self)
            LOAD_CONST               1 ('utf-8')
            LOAD_CONST               2 ('ignore')
            LOAD_CONST               3 (('errors',))
            CALL_KW                  2
            RETURN_VALUE

Disassembly of <code object get_encrypted_flag at 0x000001C3362D7AA0, file "crackme_easy.py", line 15>:
15         RESUME                   0

18         LOAD_CONST               1 ('e3w+fiRvfW18fnx4ZAZ6Pj43YwB9OWMXfXo8Dg4O')
            STORE_FAST               0 (enc_data)

19         LOAD_GLOBAL            0 (base64)
            LOAD_ATTR                2 (b64decode)
            PUSH_NULL
            LOAD_FAST_BORROW         0 (enc_data)
            CALL                     1
            RETURN_VALUE

Disassembly of <code object generate_flag at 0x000001C336305E30, file "crackme_easy.py", line 21>:
21         RESUME                   0

23         LOAD_GLOBAL            1 (get_encrypted_flag + NULL)
            CALL                     0
            STORE_FAST               0 (encrypted)

24         LOAD_SMALL_INT          78
            STORE_FAST               1 (key)

26         LOAD_GLOBAL            3 (bytearray + NULL)
            CALL                     0
            STORE_FAST               2 (result)

27         LOAD_GLOBAL            5 (enumerate + NULL)
            LOAD_FAST_BORROW         0 (encrypted)
            CALL                     1
            GET_ITER
      L1:   FOR_ITER                28 (to L2)
            UNPACK_SEQUENCE          2
            STORE_FAST_STORE_FAST   52 (i, byte)

28         LOAD_FAST_BORROW         2 (result)
            LOAD_ATTR                7 (append + NULL|self)
            LOAD_FAST_BORROW_LOAD_FAST_BORROW 65 (byte, key)
            BINARY_OP               12 (^)
            CALL                     1
            POP_TOP
            JUMP_BACKWARD         30 (to L1)

27   L2:   END_FOR
            POP_ITER

30         LOAD_FAST_BORROW         2 (result)
            LOAD_ATTR                9 (decode + NULL|self)
            LOAD_CONST               1 ('utf-8')
            CALL                     1
            RETURN_VALUE

Disassembly of <code object calculate_checksum at 0x000001C3362B63A0, file "crackme_easy.py", line 32>:
32         RESUME                   0

34         LOAD_SMALL_INT         0
            STORE_FAST               1 (total)

35         LOAD_GLOBAL            1 (enumerate + NULL)
            LOAD_FAST_BORROW         0 (s)
            CALL                     1
            GET_ITER
      L1:   FOR_ITER                37 (to L2)
            UNPACK_SEQUENCE          2
            STORE_FAST_STORE_FAST   35 (i, c)

36         LOAD_FAST_BORROW         1 (total)
            LOAD_GLOBAL            3 (ord + NULL)
            LOAD_FAST_BORROW         3 (c)
            CALL                     1
            LOAD_FAST_BORROW         2 (i)
            LOAD_SMALL_INT         1
            BINARY_OP                0 (+)
            BINARY_OP                5 (*)
            BINARY_OP               13 (+=)
            STORE_FAST               1 (total)
            JUMP_BACKWARD         39 (to L1)

35   L2:   END_FOR
            POP_ITER

37         LOAD_FAST_BORROW         1 (total)
            RETURN_VALUE

Disassembly of <code object hash_string at 0x000001C33633BEB0, file "crackme_easy.py", line 39>:
39         RESUME                   0

41         LOAD_GLOBAL            0 (hashlib)
            LOAD_ATTR                2 (sha256)
            PUSH_NULL
            LOAD_FAST_BORROW         0 (s)
            LOAD_ATTR                5 (encode + NULL|self)
            CALL                     0
            CALL                     1
            LOAD_ATTR                7 (hexdigest + NULL|self)
            CALL                     0
            RETURN_VALUE

Disassembly of <code object verify_flag at 0x000001C3366444E0, file "crackme_easy.py", line 43>:
43         RESUME                   0

45         LOAD_GLOBAL            1 (generate_flag + NULL)
            CALL                     0
            STORE_FAST               1 (correct_flag)

47         LOAD_GLOBAL            3 (len + NULL)
            LOAD_FAST_BORROW         0 (user_input)
            CALL                     1
            LOAD_GLOBAL            3 (len + NULL)
            LOAD_FAST_BORROW         1 (correct_flag)
            CALL                     1
            COMPARE_OP             119 (bool(!=))
            POP_JUMP_IF_FALSE      3 (to L1)
            NOT_TAKEN

48         LOAD_CONST               1 (False)
            RETURN_VALUE

51   L1:   LOAD_GLOBAL            5 (range + NULL)
            LOAD_GLOBAL            3 (len + NULL)
            LOAD_FAST_BORROW         1 (correct_flag)
            CALL                     1
            CALL                     1
            GET_ITER
      L2:   FOR_ITER                25 (to L4)
            STORE_FAST               2 (i)

52         LOAD_FAST_BORROW_LOAD_FAST_BORROW 2 (user_input, i)
            BINARY_OP               26 ([])
            LOAD_FAST_BORROW_LOAD_FAST_BORROW 18 (correct_flag, i)
            BINARY_OP               26 ([])
            COMPARE_OP             119 (bool(!=))
            POP_JUMP_IF_TRUE         3 (to L3)
            NOT_TAKEN
            JUMP_BACKWARD         24 (to L2)

53   L3:   POP_TOP
            LOAD_CONST               1 (False)
            RETURN_VALUE

51   L4:   END_FOR
            POP_ITER

55         LOAD_CONST               2 (True)
            RETURN_VALUE

Disassembly of <code object fake_check_1 at 0x000001C3362B3A30, file "crackme_easy.py", line 57>:
57         RESUME                   0

59         LOAD_CONST               1 ('a1b2c3d4e5f67890abcdef1234567890abcdef1234567890abcdef1234567890')
            STORE_FAST               1 (fake_hash)

60         LOAD_GLOBAL            1 (hash_string + NULL)
            LOAD_FAST_BORROW         0 (user_input)
            CALL                     1
            LOAD_FAST_BORROW         1 (fake_hash)
            COMPARE_OP            72 (==)
            RETURN_VALUE

Disassembly of <code object fake_check_2 at 0x000001C3364B0430, file "crackme_easy.py", line 62>:
62         RESUME                   0

64         LOAD_CONST               1 ('1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef')
            STORE_FAST               1 (fake_hash)

65         LOAD_GLOBAL            1 (hash_string + NULL)
            LOAD_FAST_BORROW         0 (user_input)
            CALL                     1
            LOAD_FAST_BORROW         1 (fake_hash)
            COMPARE_OP            72 (==)
            RETURN_VALUE

Disassembly of <code object main at 0x000001C336539400, file "crackme_easy.py", line 67>:
67         RESUME                   0

69         LOAD_GLOBAL            1 (print + NULL)
            LOAD_CONST            15 ('==================================================')
            CALL                     1
            POP_TOP

70         LOAD_GLOBAL            1 (print + NULL)
            LOAD_CONST               1 ('   CrackMe Challenge - Python Edition')
            CALL                     1
            POP_TOP

71         LOAD_GLOBAL            1 (print + NULL)
            LOAD_CONST            15 ('==================================================')
            CALL                     1
            POP_TOP

72         LOAD_GLOBAL            1 (print + NULL)
            LOAD_CONST               2 ('Keywords: 52pojie, 2026, Happy New Year')
            CALL                     1
            POP_TOP

73         LOAD_GLOBAL            1 (print + NULL)
            LOAD_CONST               3 ('Hint: Decompile me if you can!')
            CALL                     1
            POP_TOP

74         LOAD_GLOBAL            1 (print + NULL)
            LOAD_CONST            16 ('--------------------------------------------------')
            CALL                     1
            POP_TOP

76         LOAD_GLOBAL            3 (input + NULL)
            LOAD_CONST               4 ('\n[?] Enter the password: ')
            CALL                     1
            LOAD_ATTR                5 (strip + NULL|self)
            CALL                     0
            STORE_FAST               0 (user_input)

79         LOAD_GLOBAL            7 (fake_check_1 + NULL)
            LOAD_FAST_BORROW         0 (user_input)
            CALL                     1
            TO_BOOL
            POP_JUMP_IF_FALSE       25 (to L1)
            NOT_TAKEN

80         LOAD_GLOBAL            1 (print + NULL)
            LOAD_CONST               5 ('\n[!] Nice try, but not quite right...')
            CALL                     1
            POP_TOP

81         LOAD_GLOBAL            3 (input + NULL)
            LOAD_CONST               6 ('\nPress Enter to exit...')
            CALL                     1
            POP_TOP

82         LOAD_CONST               7 (None)
            RETURN_VALUE

84   L1:   LOAD_GLOBAL            9 (fake_check_2 + NULL)
            LOAD_FAST_BORROW         0 (user_input)
            CALL                     1
            TO_BOOL
            POP_JUMP_IF_FALSE       25 (to L2)
            NOT_TAKEN

85         LOAD_GLOBAL            1 (print + NULL)
            LOAD_CONST               8 ("\n[!] You're getting closer...")
            CALL                     1
            POP_TOP

86         LOAD_GLOBAL            3 (input + NULL)
            LOAD_CONST               6 ('\nPress Enter to exit...')
            CALL                     1
            POP_TOP

87         LOAD_CONST               7 (None)
            RETURN_VALUE

90   L2:   LOAD_GLOBAL             11 (verify_flag + NULL)
            LOAD_FAST_BORROW         0 (user_input)
            CALL                     1
            TO_BOOL
            POP_JUMP_IF_FALSE      108 (to L4)
            NOT_TAKEN

91         LOAD_GLOBAL             13 (calculate_checksum + NULL)
            LOAD_FAST_BORROW         0 (user_input)
            CALL                     1
            STORE_FAST               1 (checksum)

92         LOAD_GLOBAL             13 (calculate_checksum + NULL)
            LOAD_GLOBAL             15 (generate_flag + NULL)
            CALL                     0
            CALL                     1
            STORE_FAST               2 (expected_checksum)

94         LOAD_FAST_BORROW_LOAD_FAST_BORROW 18 (checksum, expected_checksum)
            COMPARE_OP            88 (bool(==))
            POP_JUMP_IF_FALSE       60 (to L3)
            NOT_TAKEN

95         LOAD_GLOBAL            1 (print + NULL)
            LOAD_CONST            17 ('\n==================================================')
            CALL                     1
            POP_TOP

96         LOAD_GLOBAL            1 (print + NULL)
            LOAD_CONST               9 ('      *** SUCCESS! ***')
            CALL                     1
            POP_TOP

97         LOAD_GLOBAL            1 (print + NULL)
            LOAD_CONST            15 ('==================================================')
            CALL                     1
            POP_TOP

98         LOAD_GLOBAL            1 (print + NULL)
            LOAD_CONST            10 ('[+] Congratulations! You cracked it!')
            CALL                     1
            POP_TOP

99         LOAD_GLOBAL            1 (print + NULL)
            LOAD_CONST            11 ('[+] Correct flag: ')
            LOAD_FAST_BORROW         0 (user_input)
            FORMAT_SIMPLE
            BUILD_STRING             2
            CALL                     1
            POP_TOP
            JUMP_FORWARD            34 (to L5)

101   L3:   LOAD_GLOBAL            1 (print + NULL)
            LOAD_CONST            12 ('\n[!] Checksum failed!')
            CALL                     1
            POP_TOP
            JUMP_FORWARD            22 (to L5)

103   L4:   LOAD_GLOBAL            1 (print + NULL)
            LOAD_CONST            13 ('\n Access Denied!')
            CALL                     1
            POP_TOP

104         LOAD_GLOBAL            1 (print + NULL)
            LOAD_CONST            14 (' Wrong password. Keep trying!')
            CALL                     1
            POP_TOP

106   L5:   LOAD_GLOBAL            3 (input + NULL)
            LOAD_CONST               6 ('\nPress Enter to exit...')
            CALL                     1
            POP_TOP
            LOAD_CONST               7 (None)
            RETURN_VALUE
```

通过阅读代码,main->verify_flag->generate_flag->get_encrypted_flag

generate_flag中:

可得知是xor加密,key=78(0x4E),

get_encrypted_flag中:

可发现'e3w+fiRvfW18fnx4ZAZ6Pj43YwB9OWMXfXo8Dg4O',并且后面还有b64decode

那就很明了了。



## 题外话

这题也可上cc,





# 第一篇windows中级题

可参考:(https://j1nxem-o.github.io/2026/ ... %91%E4%B9%8BNuitka/),其实坛里也有一篇,但感觉写的不如这篇清晰,流程都是这个流程,记住了就好。

使用到的工具:(https://github.com/extremecoders-re/nuitka-extractor)



还是一样,查壳,发现是Nuitka python compiler

那么解包:

```
nuitka-extractor <file name>
```

ps:其实也可以运行后去temp目录找onefile开头的文件夹

得到的文件中一眼就看到:`crackme_hard.dll`

然后先上ResourceHacker提取python的字节码(nuitka会将python的字节码写入在资源段中)

我们先来枚举bin的模块:

```
import io
import struct


def read_uint32(bio):
    return struct.unpack("<I", bio.read(4))


def read_uint16(bio):
    return struct.unpack("<H", bio.read(2))


def read_utf8(bio):
    bs = b""

    while True:
      bs += bio.read(1)
      if b"\x00" in bs:
            break
    return bs[:-1].decode("utf-8")


def main():
    with open("main.bin", "rb") as f_in:
      bs = f_in.read()

    bio = io.BytesIO(bs)
    hash_ = read_uint32(bio)
    size = read_uint32(bio)
    print(f"hash: {hex(hash_)}")
    print(f"size: {hex(size)}")

    while bio.tell() < size:
      blob_name = read_utf8(bio)
      blob_size = read_uint32(bio)
      blob_count = read_uint16(bio)
      print(f"name: {blob_name}, size: {hex(blob_size)}, count: {hex(blob_count)}")
      bio.seek(bio.tell() + (blob_size - 2))


if __name__ == "__main__":
    main()
```

运行后可以得到:

```
hash: 0xdcaa9d4e
size: 0x55fdaa
name: .bytecode, size: 0x55f3f1, count: 0x143
name: , size: 0x36e, count: 0x6b
name: __main__, size: 0x62b, count: 0x53
```

那么可得知`__main__`是我要解析的了。

接下来就得解析python的字节码了,由于不同版本的nuitka的解析方式也不同,

于是将crackme_hard.dll拖入ida中,可以通过搜索字符串blob->找交叉引用,确定解析函数的位置(别问我怎么知道的,我是看上面的那篇文章学的)。

然后就将那个函数全部复制下来,喂给ds,让它帮你写一个解析代码:(得到的代码第一次可能会出现错误,让ds修正就好了,要告诉ds我们要得到的输出的格式才行)

```
import io
import struct
from typing import Any, Dict, List, Optional, Union


def read_u8(b) -> int:
    return struct.unpack("<B", b.read(1))


def read_u32(b) -> int:
    return struct.unpack("<I", b.read(4))


def read_u16(b) -> int:
    return struct.unpack("<H", b.read(2))


def read_varint(b) -> int:
    """读取变长整数 (对应C代码中的v73 += v74 * (v75 & 0x7F)逻辑)"""
    shift = 0
    result = 0
    while True:
      byte = read_u8(b)
      result |= (byte & 0x7F) << shift
      if byte < 0x80:
            return result
      shift += 7


def read_cstring(b) -> bytes:
    """读取C风格字符串 (以\0结尾)"""
    s = b""
    while True:
      c = b.read(1)
      if not c or c == b"\x00":
            break
      s += c
    return s


def decode_value(b, depth=0) -> Any:
    """解析一个值 (对应C代码中的sub_31340ECE0函数)"""
    try:
      tag_byte = read_u8(b)
    except:
      return None
   
    # 处理非ASCII字符作为原始数据
    if tag_byte < 32 or tag_byte >= 127:
      # 可能是原始数据,回退一个字节并作为bytes返回
      b.seek(b.tell() - 1)
      data = b.read(1)
      return f"RAW_{data.hex()}"
   
    tag = chr(tag_byte)
   
    # --- 忽略填充符 (对应case '.') ---
    if tag == ".":
      return None
   
    # --- 字符串类型 ---
    # case 'a', 'u': UTF-8字符串,a会被intern
    if tag in ("a", "u"):
      s = read_cstring(b).decode("utf-8", errors="surrogatepass")
      return s
   
    # case 'w': 单字符字符串
    if tag == "w":
      return b.read(1).decode("utf-8", errors="surrogatepass")
   
    # case 's': interned字符串
    if tag == "s":
      return read_cstring(b).decode("utf-8", errors="surrogatepass")
   
    # case 'v': 带长度的UTF-8字符串
    if tag == "v":
      n = read_varint(b)
      return b.read(n).decode("utf-8", errors="surrogatepass")
   
    # --- 字节类型 ---
    # case 'b': 字节串
    if tag == "b":
      n = read_varint(b)
      data = b.read(n)
      # 尝试解码为字符串,失败则返回字节列表
      try:
            return data.decode("utf-8")
      except:
            return list(data)
   
    # case 'c': C风格字节串
    if tag == "c":
      data = read_cstring(b)
      try:
            return data.decode("utf-8")
      except:
            return list(data)
   
    # case 'B': bytearray (带长度)
    if tag == "B":
      n = read_varint(b)
      data = b.read(n)
      return list(data)
   
    # --- 数字类型 ---
    # case 'l', 'q': 整数 (l为正数,q为负数)
    if tag in ("l", "q"):
      v = read_varint(b)
      return v if tag == "l" else -v
   
    # case 'f': 浮点数 (8字节)
    if tag == "f":
      return struct.unpack("<d", b.read(8))
   
    # case 'j': 复数 (16字节)
    if tag == "j":
      real = struct.unpack("<d", b.read(8))
      imag = struct.unpack("<d", b.read(8))
      return complex(real, imag)
   
    # case 'Z': 特殊浮点数常量
    if tag == "Z":
      subcode = read_u8(b)
      # 对应C代码中的switch
      constants =
      if subcode < len(constants):
            return constants
      return 0.0
   
    # --- 布尔值和None ---
    # case 't', 'F', 'n'
    if tag in ("t", "F", "n"):
      return {
            "t": True,
            "F": False,
            "n": None
      }
   
    # --- 容器类型 ---
    # case 'T': 元组
    if tag == "T":
      n = read_varint(b)
      return tuple(decode_value(b, depth+1) for _ in range(n))
   
    # case 'L': 列表
    if tag == "L":
      n = read_varint(b)
      return
   
    # case 'D': 字典
    if tag == "D":
      n = read_varint(b)
      d = {}
      for _ in range(n):
            key = decode_value(b, depth+1)
            value = decode_value(b, depth+1)
            d = value
      return d
   
    # case 'P', 'S': 集合/冻结集合
    if tag in ("P", "S"):
      n = read_varint(b)
      items =
      if tag == "P":
            return frozenset(items)
      else:
            return set(items)
   
    # --- 特殊类型 ---
    # case ':': 切片
    if tag == ":":
      start = decode_value(b, depth+1)
      stop = decode_value(b, depth+1)
      step = decode_value(b, depth+1)
      return slice(start, stop, step)
   
    # case ';': range
    if tag == ";":
      start = decode_value(b, depth+1)
      stop = decode_value(b, depth+1)
      step = decode_value(b, depth+1)
      return range(start, stop, step)
   
    # case 'M': 标记值
    if tag == "M":
      subcode = read_u8(b)
      markers = {
            0: None,      # Py_None
            1: ...,       # Ellipsis
            2: NotImplemented,
            3: "Function",
            4: "Generator",
            5: "CFunction",
            6: "Code",
            7: "Module",
            10: "Type"
      }
      return markers.get(subcode, f"Marker_{subcode}")
   
    # case 'Q': 特殊值
    if tag == "Q":
      subcode = read_u8(b)
      if subcode == 0:
            return ...
      elif subcode == 1:
            return NotImplemented
      elif subcode == 2:
            return ...
      else:
            return f"Special_{subcode}"
   
    # case 'p': 引用前一个值
    if tag == "p":
      # 简单实现,实际需要维护引用列表
      return "<reference>"
   
    # case 'X': 原始数据块
    if tag == "X":
      n = read_varint(b)
      return b.read(n)
   
    # case 'O': 属性访问
    if tag == "O":
      name = read_cstring(b).decode()
      return f"Attr({name})"
   
    # case 'A': 泛型别名
    if tag == "A":
      origin = decode_value(b, depth+1)
      args = decode_value(b, depth+1)
      return f"GenericAlias({origin}, {args})"
   
    # case 'H': 按位OR
    if tag == "H":
      # 读取第一个对象
      first = decode_value(b, depth+1)
      if first is None:
            return 0
      result = first
      # 继续读取直到结束
      while True:
            try:
                pos = b.tell()
                next_val = decode_value(b, depth+1)
                if next_val is None:
                  break
                if isinstance(result, int) and isinstance(next_val, int):
                  result |= next_val
            except:
                b.seek(pos)
                break
      return result
   
    # case 'J': 复数
    if tag == "J":
      real = decode_value(b, depth+1)
      imag = decode_value(b, depth+1)
      real_val = real if isinstance(real, (int, float)) else 0
      imag_val = imag if isinstance(imag, (int, float)) else 0
      return complex(real_val, imag_val)
   
    # case 'g', 'G': 全局对象
    if tag in ("g", "G"):
      idx = read_varint(b)
      return f"Global_{idx}"
   
    # case 'd': 运行时常量
    if tag == "d":
      idx = read_u8(b)
      return f"RuntimeConst_{idx}"
   
    # case 'E': 空
    if tag == "E":
      # 跳过直到\0
      while True:
            if b.read(1) == b"\x00":
                break
      return None
   
    # case 'C': 复杂结构 (从输出中看到)
    if tag == "C":
      # 复杂结构,可能是code对象
      flags = read_u8(b)
      # 读取更多数据...
      return f"ComplexObject(flags={flags})"
   
    # 处理可能的原始字节
    # 回退一个字节,然后作为原始数据读取
    b.seek(b.tell() - 1)
   
    # 尝试读取一个整数
    try:
      val = read_varint(b)
      return val
    except:
      pass
   
    # 作为原始字节返回
    data = b.read(1)
    return f"RAW_{data.hex()}"


def decode_blob(b, count):
    """解析blob中的对象"""
    results = []
    for i in range(count):
      try:
            obj = decode_value(b)
            results.append(obj)
      except Exception as e:
            # 出错时尝试恢复
            pos = b.tell()
            # 尝试跳过一些字节
            b.seek(pos + 1)
            results.append(f"<ERROR: {e} at {pos}>")
    return results


def main():
    import sys
   
    if len(sys.argv) < 2:
      print("用法: python script.py <二进制文件>")
      return
   
    filename = sys.argv
   
    with open(filename, "rb") as f:
      data = f.read()
   
    b = io.BytesIO(data)
   
    # 读取文件头
    magic = read_u32(b)
    size = read_u32(b)
   
    print(f"magic={hex(magic)} size={size}")
   
    blob_index = 0
   
    while b.tell() < size and b.tell() < len(data):
      try:
            # 读取blob名称
            name_bytes = read_cstring(b)
            try:
                name = name_bytes.decode('utf-8', errors='ignore')
                if not name:
                  name = "(empty)"
            except:
                name = name_bytes.hex()
            
            # 读取blob大小和数量
            blob_size = read_u32(b)
            count = read_u16(b)
            
            print(f"[+] blob {name!r} count={count}")
            
            # 记录当前位置
            start_pos = b.tell()
            
            # 如果是__main__ blob,解析里面的对象
            if name == "__main__":
                objects = decode_blob(b, count)
                for i, obj in enumerate(objects):
                  print(f"{i}: {obj}")
                break
            else:
                # 跳过这个blob
                b.seek(start_pos + blob_size - 2)
               
      except Exception as e:
            print(f"解析blob时出错: {e}")
            break


if __name__ == "__main__":
    main()
```

运行该代码,可得到:

```
magic=0xdcaa9d4e size=5635498
[+] blob '.bytecode' count=323
[+] blob '(empty)' count=107
[+] blob '__main__' count=83
0: ['dc!a;`b', 'RuntimeConst_17', 'cacg', 'RuntimeConst_47', '\x19e!!(', 'RuntimeConst_14', '\x1fb&', 'RuntimeConst_14', '\x08be#', 'ppp']
1: _parts
2: 81
3: _key
4: 30
5: _total_len
6: 解密单个字符
7: current
8: _decrypt_char
9: 获取指定位置的字符
10: self
11: _get_char_at_position
12: 验证用户输入
13: total
14: 计算校验和
15: aflag
16: checksum
17: 获取目标校验和
18: hashlib
19: sha256
20: encode
21: hexdigest
22: slice(None, 8, None)
23: 16
24: 哈希函数
25: 305419896
26: Global_2
27: RAW_01
28: RAW_81
29: RAW_de
30: RAW_b7
31: RAW_de
32: RAW_02
33: 1380994890
34: hash_input
35: 假检查
36: print
37: ('==================================================',)
38: ('   CrackMe Challenge - Binary Edition',)
39: ('Keywords: 52pojie, 2026, Happy New Year',)
40: ('Hint: 1337 5p34k & 5ymb0l5!',)
41: ('      Try to decompile this in IDA!',)
42: ('--------------------------------------------------',)
43: CrackMeCore
44:
[?] Enter the password:
45: fake_check
46: ('\n[!] Close, but not quite there...',)
47:
Press Enter to exit...
48: verify
49: get_target_checksum
50: ('\n==================================================',)
51: ('      *** SUCCESS! ***',)
52: ('[+] L33T H4X0R!',)
53: [+] Your answer:
54:
[!] Checksum mismatch:
55:!=
56: ('\n Access Denied!',)
57: (' Wrong password!',)
58: 主函数
59: __doc__
60: __file__
61: __cached__
62: __annotations__
63: sys
64: __main__
65: __module__
66: 核心验证类 - 将被编译成二进制
67: __qualname__
68: __init__
69: CrackMeCore.__init__
70: CrackMeCore._decrypt_char
71: CrackMeCore._get_char_at_position
72: CrackMeCore.verify
73: CrackMeCore.checksum
74: CrackMeCore.get_target_checksum
75: main
76: ('\n\n[!] Interrupted',)
77: crackme_hard.py
78: <module>
79: ('self',)
80: ('self', 'part_idx', 'char_idx', 'encrypted_byte')
81: ('self', 'pos', 'current', 'part_idx', 'part')
82: ('self', 's', 'total', 'i', 'c')
```

然后,将这些输出丢给ds,开深度思考,最后得到密码为 `52p0j13@2026~H4ppy_N3w_Y34r!!!`

*这里我还试了刚出的gemini pro3.1,居然还不如ds深度思考。*



cc仍然可解,





# 番外篇

这不就是熟悉的抓小猫吗,直接玩就行了,得到`flag{52pojie_2026_Happy_New_Year!_>w<}`。

我一开始还没注意到easy模式没有flag,还傻乎乎的玩easy模式。

后来,我分析了一下,查CatchTheCat.exe壳发现: `x64 - Zip Sfx Archive`

于是使用(https://wwavz.lanzout.com/iZDgN3iyow7e),右键CatchTheCat.exe打开

发现里面有main.lua,flag.dat,conf.lua(其实根据lua51.dll也可以判断了是lua写的了)

那这很明显了,flag.dat存的应该就是flag。

打开一看,乱码,说明加密了,那就看lua解密。

观察lua文件大小和命名可知,主要逻辑在main.lua。

打开main.lua,因为win了才显示flag,于是我搜索win,得到以下代码片段:

```
local function getWinMessage()
    local content = nil
   
    if love.filesystem.getInfo("assets/flag.dat") then
      content = love.filesystem.read("assets/flag.dat")
    end

    if not content or currentDifficulty ~= "hard" then
      return "You WIN!"
    end

    local key = "52pojie"
    local keyLen = #key
    local result = {}
    local bit = require("bit")

    for i = 1, #content do
      local b = string.byte(content, i)
      local k = string.byte(key, ((i - 1) % keyLen) + 1)
      table.insert(result, string.char(bit.bxor(b, k)))
    end

    return table.concat(result)
end
```

不难得知是对flag.dat进行xor,key=`52pojie`。

不想搜索也可将main.lua喂给ds,提示词写:`分析一下win之后的表现`,也是可以得到结果的。



# 第二篇windows中级题

首先查壳,发现是标准upx,用upx -d先脱一下

可先打开cm1.exe看一下,发现直接解密会弹窗。

然后拖入ida中,ctrl+f搜索messagebox,发现MessageBoxW,右键__imp_MessageBoxW查找交叉引用,这样就可以快速定位了。

将那段伪代码喂给ai,找到关键一行:`v7 = sub_140008720(Str, v5, v6);`

查看sub_140008720:

```
__int64 __fastcall sub_140008720(char *Str, FILE *Stream, FILE *a3)
{
size_t v6; // rax
__int64 v7; // rdi
char v8; // dl
__int64 result; // rax
__int64 v10; // rdi
unsigned __int64 v11; // r13
char *v12; // rax
char *v13; // r12
unsigned __int64 v14; // rax
_BYTE Buffer; // BYREF
_BYTE v16; // BYREF

sub_140008640(v16);
sub_140008500(v16, "52pojie_2026_", 14);
v6 = strlen(Str);
sub_140008500(v16, Str, v6);
v7 = sub_140008580(v16);
fread(Buffer, 0x10u, 1u, Stream);
v8 = sub_140008310(v16, v7, Buffer);
result = 1;
if ( v8 )
{
    fseek(Stream, 0, 2);
    v10 = ftell(Stream);
    result = 2;
    v11 = v10 - 16;
    if ( (v10 & 7) == 0 )
    {
      fseek(Stream, 16, 0);
      v12 = (char *)malloc(v10 - 16);
      v13 = v12;
      if ( v12 )
      {
      fread(v12, 1u, v10 - 16, Stream);
      sub_1400081E0(v16, v13, v10 - 16);
      if ( (unsigned __int8)sub_1400082E0(v16) )
      {
          v14 = (unsigned __int8)v13;
          if ( v11 < v14 )
          {
            free(v13);
            return 5;
          }
          else
          {
            fwrite(v13, 1u, v11 - v14, a3);
            free(v13);
            return 0;
          }
      }
      else
      {
          free(v13);
          return 4;
      }
      }
      else
      {
      return 3;
      }
    }
}
return result;
}
```

一样的喂给ai(这里得把里面的函数伪代码一块喂),得知

```
sub_140008640(v16);
sub_140008500(v16, "52pojie_2026_", 14);
v6 = strlen(Str);
sub_140008500(v16, Str, v6);
v7 = sub_140008580(v16);
```

这是对 `"52pojie_2026_" + 目标flag` 计算 CRC-64,得到64位初始密钥。



## 下面的大致算法是(这是我二次使用ai概括的结果):

从文件偏移 `0x08` 处读取 16字节作为初始化向量(IV),使用 AES标准S-Box,提供非线性替换。

**解密核心变换(每8字节执行一次)**

1. 位旋转(循环左移)

- 将当前64位状态 循环左移3位
- 高位移出的部分自动补到低位,保持64位长度

2. S-Box 字节替换(8轮迭代)

- 循环执行 8次:
- 取当前值的最高8位作为索引
- 查 AES标准S-Box 进行字节替换
- 将替换结果通过位运算拼回原值
- 作用:在64位维度上实现非线性扩散,打乱数据相关性

3. 异或反馈(密钥混合)

- 计算公式:密文数据 ⊕ S-Box变换后的密钥数据 ⊕ 额外密钥数据
- 三部分异或后的结果即为解密后的明文
- 将结果写回内存,完成当前8字节解密

---

**校验与去填充(Verification & Unpadding)**

| 步骤      | 说明                                                         |
| --------- | ------------------------------------------------------------ |
| CRC32校验 | 计算解密后全文的 CRC32,与文件头 `0x04` 处存储的CRC值对比。|
| 去除填充| 取解密后数据的最后一个字节作为长度 L,删除末尾 L 个字节,得到最终明文 |

---

整体流程图

```
用户输入字符串 ──┐
                ▼
      CRC-64("52pojie_2026_" + 输入) ──→ 初始密钥
                │
                ▼
      读取文件IV (16字节 @ 0x08)
                │
                ▼
      ┌─────────────────┐
      │循环处理每8字节│
      │1. 循环左移3位   │
      │2. S-Box查表×8│
      │3. 异或密钥还原│
      └─────────────────┘
                │
                ▼
      全文CRC32校验 ──→ 失败则退出
                │
                ▼
      去填充(删末尾L字节)
                │
                ▼
            最终明文
```

---

一句话总结

> 用CRC-64派生密钥,以"旋转→S-Box混淆→异或还原"为核心轮函数逐8字节解密,最后校验CRC并去填充 —— 这是一个结合哈希派生、类流密码变换和标准校验的自定义加密方案。

## 文件格式结构

加密文件采用以下格式:

- **0x00-0x03**: 魔数 `b"CM26"`(4字节)
- **0x04-0x07**: CRC32校验值(小端序,4字节)
- **0x08-0x0F**: 初始64位反馈值(小端序,8字节)
- **0x10-末尾**: 密文块(每块8字节)

## 解密文件得到flag

好,现在知道算法了,那么就可以着手得到flag了。

得到这个flag,肯定不能暴力破解,因为crc64的可能有太多太多了。

因为知道最终得到的是png,而png的文件头已知,所以可用已知明文攻击,直接得到crc64的那个目标哈希值,借助ai得到如下代码(别用网页版,得用cli才行,我使用的是gpt-5.3-codex):

```
import struct

MASK64 = 0xFFFFFFFFFFFFFFFF

def ror64(value: int, shift: int = 3) -> int:
    return ((value >> shift) | ((value & ((1 << shift) - 1)) << (64 - shift))) & MASK64

# AES逆S盒
AES_SBOX_INV = * 256
for i, val in enumerate([
    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,
]):
    AES_SBOX_INV = i

def sbox_mix_64_inv(value: int) -> int:
    """一轮逆运算"""
    for _ in range(8):
      bottom = value & 0xFF
      value = (value >> 8) | (AES_SBOX_INV << 56)
    return value

def get_crc64_hash(encrypted_data: bytes) -> int:
    feedback0 = struct.unpack_from("<Q", encrypted_data, 8)
    block1 = struct.unpack_from("<Q", encrypted_data, 0x10)
    png_header = 0x0A1A0A0D474E5089# PNG文件头
   
    # state1 = block1 ^ png_header ^ feedback0
    state1 = block1 ^ png_header ^ feedback0
   
    # 逆运算得到rol_state0,再右移3位得到state0
    state0 = ror64(sbox_mix_64_inv(state1), 3)
   
    return state0# 这就是INITIAL_KEY
   
c=get_crc64_hash(open("flag.png.encrypted", "rb").read())

print(hex(c))
```

利用该哈希值,可以直接跳过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,
]


def rol64(value: int, shift: int = 3) -> int:
    return ((value << shift) & MASK64) | (value >> (64 - shift))


def sbox_mix_64(value: int) -> int:
    """Apply AES S-Box over 8 bytes from high byte to low byte."""
    for _ in range(8):
      top = (value >> 56) & 0xFF
      value = ((value << 8) & MASK64) | AES_SBOX
    return value


def pkcs7_unpad(data: bytes, block_size: int = 8) -> bytes:
    if not data:
      raise ValueError("empty plaintext")

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

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

    return data[:-pad_len]


def decrypt_cm26(data: bytes, key: int = INITIAL_KEY, strict_magic: bool = True) -> bytes:
    if len(data) < 0x10:
      raise ValueError("input too short")

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

    expected_crc = struct.unpack_from("<I", data, 4)
    feedback = struct.unpack_from("<Q", data, 8)
    ciphertext = data

    if len(ciphertext) % 8 != 0:
      raise ValueError("ciphertext length is not a multiple of 8")

    state = key & MASK64
    padded_plain = bytearray()

    for i in range(0, len(ciphertext), 8):
      block = struct.unpack_from("<Q", ciphertext, i)
      state = sbox_mix_64(rol64(state, 3))
      plain_qword = block ^ state ^ feedback
      padded_plain += struct.pack("<Q", plain_qword)
      feedback = block

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

    return pkcs7_unpad(bytes(padded_plain), block_size=8)


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

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

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

    args.output.write_bytes(plain)
    print(f"[+] Decrypted OK: {args.output} ({len(plain)} bytes)")
    return 0


if __name__ == "__main__":
    raise 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将值固定

```
(function() {
    const rawCrypto = window.crypto;

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

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

                  const hexStr = Array.from(typedArray)
                      .map(b => b.toString(16).padStart(2, '0'))
                      .join('');
                  
                  console.log(hexStr);
                  
                  return typedArray;
                };
            }

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

            return value;
      }
    });

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

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

## wasm简析

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

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

> wasm转o,要将下载的wabt中的头文件和.c文件放在同一文件夹下:
>
> ```
> wasm2c.exe wasm.wasm -o wasm.c
> gcc -c wasm.c -o wasm.o
> ```

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

```
MAX_FUNCSIZE            = 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);// 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,推理开超高,说要得到验证码文本,不一会儿就出了。

```
from __future__ import annotations

import argparse
import hashlib
import hmac
import os
from dataclasses import dataclass

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

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

@dataclass(frozen=True)
class CodeMaterial:
    uid: int
    random17: bytes
    payload21: bytes
    signature16: bytes
    payload37: bytes
    code: str


def custom_b64_encode_no_padding(data: bytes, alphabet: str = CUSTOM_B64_ALPHABET) -> str:
    if len(alphabet) != 64:
      raise ValueError("alphabet must contain exactly 64 characters")

    out: list = []
    acc = 0
    bits = 0

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

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

    return "".join(out)


def build_payload21(uid: int, random17: bytes) -> bytes:
    if len(random17) != 17:
      raise ValueError("random17 must be exactly 17 bytes")

    uid_le = (uid & 0xFFFFFFFF).to_bytes(4, "little")
    head4 = bytes(uid_le ^ random17 for i in range(4))
    return head4 + random17


def generate_code_material(uid: int, random17: bytes | None = None) -> CodeMaterial:
    if random17 is None:
      random17 = os.urandom(17)
    payload21 = build_payload21(uid, random17)

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

    payload37 = payload21 + signature16
    code = custom_b64_encode_no_padding(payload37)

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


def check_hash(code: str, rounds: int = 0x2026) -> str:
    current = code.encode("utf-8")
    for _ in range(rounds):
      current = hashlib.sha256(current).digest()
    return current.hex()


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


def main() -> None:
    args = parse_args()

    random17 = None
    if args.rand17_hex is not None:
      raw = bytes.fromhex(args.rand17_hex)
      if len(raw) != 17:
            raise SystemExit("--rand17-hex must be exactly 34 hex chars (17 bytes)")
      random17 = raw

    material = generate_code_material(args.uid, random17)

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

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


if __name__ == "__main__":
    main()

```

使用示例:

```bash
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给解出来了。

提示词如下:

```
这是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`
> 3. 得到口令:`玄霄密令`。
> 4. 访问 `secret:/board/resolutions` 触发拒绝,拿到最新 `audit_log_id`。
> 5. 计算:`seal = sha256(audit_log_id + passphrase)[:8]`(小写十六进制)。
> 6. 调用 `review_document`:
>    - `doc_id = confidential:/audit/logs`
>    - `review_notes` 格式:
>      - `ctf_request=玄霄密令`
>      - `audit_log_id=<最新拒绝编号>`
>      - `seal=<上一步计算值>`
> 7. 返回 `tool_access_token` 后,调用 `update_tool_chain`,并将 `chain_config` 设置为仅 `["get_audit_log"]`。
> 8. 用 `tool_access_token` 调用 `get_audit_log(log_id=<audit_log_id>)`,拿到 `_audit_token`。
> 9. 用 `_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)

无名 发表于 2026-3-4 15:23

kc666 发表于 2026-3-4 14:43
cc是啥,我用的都是假ai吗。。
claude code啊,我展示的部分图片都有透露
不过最近出大事了,cc应该很难找到便宜的渠道了

DingDangMao001 发表于 2026-3-4 22:22

都是厉害的大佬,我python的初级题还是用的动态分析找了大半天,发现大佬们都是用逆向,我看3.14没办法按ai的进行反编译就去动态分析去了

无名 发表于 2026-3-4 07:34

我先占个楼啊,有没有大佬知道web题那里的原因
奇怪的是编译成.o在ida中这个数据就不对了,有没有大佬知道原因解释一下。

cattie 发表于 2026-3-4 08:05

ds系统提示词都给设置成啥了,居然给我又作诗又鼓励的
哈哈哈,看看js文件有惊喜,搜Agents.md{:301_976:}

(有prompt injection

无名 发表于 2026-3-4 08:08

cattie 发表于 2026-3-4 08:05
哈哈哈,看看js文件有惊喜,搜Agents.md

(有prompt injection

看了您的文章,我确实是没注意有提示词注入

TryShallow 发表于 2026-3-4 09:51

真强大,再不多搞搞AI都要被抛弃了

89507982 发表于 2026-3-4 11:30

努力学习中

kc666 发表于 2026-3-4 14:43

cc是啥{:1_904:},我用的都是假ai吗。。

jingtai123 发表于 2026-3-4 21:45

大家都写的好复杂啊
页: [1] 2
查看完整版本: 【2026春节】解题领红包大部分题题解