吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 584|回复: 7
上一主题 下一主题
收起左侧

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

[复制链接]
跳转到指定楼层
楼主
无名 发表于 2026-3-4 07:26 回帖奖励
本帖最后由 无名 于 2026-3-4 10:02 编辑

前言

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

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

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

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

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

安卓初级题

安卓初级题我不会,那就玩嘛,两分钟就出了,参考文章,最后点击宝箱,得到: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编译的(其实看图标也能猜个七七八八)。

那么就上pyinstxtractor,可以得到一个crackme_easy.pyc。

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

法一:

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

flagz(e3w+fiRvfW18fnx4ZAZ6Pj43YwB9OWMXfXo8Dg4O)�base64� b64decode)

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

ps:

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

法二:

可以看到python314.dll,右键查看详细信息发现是python 3.14.2版本,去官网下载python-3.14.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 [0]
  L4 to L5 -> L7 [1] lasti
  L6 to L7 -> L7 [1] 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[X] Access Denied!')
              CALL                     1
              POP_TOP

104           LOAD_GLOBAL              1 (print + NULL)
              LOAD_CONST              14 ('[X] 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中级题

可参考:python逆向之Nuitka,其实坛里也有一篇,但感觉写的不如这篇清晰,流程都是这个流程,记住了就好。

使用到的工具: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))[0]

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

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))[0]

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

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

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))[0]

    # case 'j': 复数 (16字节)
    if tag == "j":
        real = struct.unpack("<d", b.read(8))[0]
        imag = struct.unpack("<d", b.read(8))[0]
        return complex(real, imag)

    # case 'Z': 特殊浮点数常量
    if tag == "Z":
        subcode = read_u8(b)
        # 对应C代码中的switch
        constants = [0.0, 1.0, -1.0, 2.0, -2.0, 0.5]
        if subcode < len(constants):
            return constants[subcode]
        return 0.0

    # --- 布尔值和None ---
    # case 't', 'F', 'n'
    if tag in ("t", "F", "n"):
        return {
            "t": True,
            "F": False,
            "n": None
        }[tag]

    # --- 容器类型 ---
    # 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 [decode_value(b, depth+1) for _ in range(n)]

    # 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[key] = value
        return d

    # case 'P', 'S': 集合/冻结集合
    if tag in ("P", "S"):
        n = read_varint(b)
        items = [decode_value(b, depth+1) for _ in range(n)]
        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[1]

    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[X] Access Denied!',)
57: ('[X] 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

于是使用360zip国际版,右键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[16]; // [rsp+20h] [rbp-858h] BYREF
  _BYTE v16[2120]; // [rsp+30h] [rbp-848h] 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[v10 - 17];
          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位长度
  1. S-Box 字节替换(8轮迭代)
  • 循环执行 8次:
    • 取当前值的最高8位作为索引
    • 查 AES标准S-Box 进行字节替换
    • 将替换结果通过位运算拼回原值
  • 作用:在64位维度上实现非线性扩散,打乱数据相关性
  1. 异或反馈(密钥混合)
  • 计算公式:密文数据 ⊕ 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 = [0] * 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[val] = i

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

def get_crc64_hash(encrypted_data: bytes) -> int:
    feedback0 = struct.unpack_from("<Q", encrypted_data, 8)[0]
    block1 = struct.unpack_from("<Q", encrypted_data, 0x10)[0]
    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[top]
    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]) * 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)[0]
    feedback = struct.unpack_from("<Q", data, 8)[0]
    ciphertext = data[0x10:]

    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)[0]
        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[Hook] 拦截成功! 长度: ${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[str] = []
    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[i] ^ random17[i] 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()

使用示例:

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会发展成什么样子了。

附件

蓝奏云链接

52pojie春节2026解题得红包题目详解.7z

2.81 MB, 下载次数: 3, 下载积分: 吾爱币 -1 CB

免费评分

参与人数 1热心值 +1 收起 理由
Coxxs + 1 用心讨论,共获提升!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

沙发
 楼主| 无名 发表于 2026-3-4 07:34 |楼主
我先占个楼啊,有没有大佬知道web题那里的原因
奇怪的是编译成.o在ida中这个数据就不对了,有没有大佬知道原因解释一下。

3#
cattie 发表于 2026-3-4 08:05
ds系统提示词都给设置成啥了,居然给我又作诗又鼓励的

哈哈哈,看看js文件有惊喜,搜Agents.md

(有prompt injection
4#
 楼主| 无名 发表于 2026-3-4 08:08 |楼主
cattie 发表于 2026-3-4 08:05
哈哈哈,看看js文件有惊喜,搜Agents.md

(有prompt injection

看了您的文章,我确实是没注意有提示词注入
5#
TryShallow 发表于 2026-3-4 09:51
真强大,再不多搞搞AI都要被抛弃了
6#
89507982 发表于 2026-3-4 11:30
努力学习中
7#
kc666 发表于 2026-3-4 14:43
cc是啥,我用的都是假ai吗。。
8#
 楼主| 无名 发表于 2026-3-4 15:23 |楼主
kc666 发表于 2026-3-4 14:43
cc是啥,我用的都是假ai吗。。

claude code啊,我展示的部分图片都有透露
不过最近出大事了,cc应该很难找到便宜的渠道了
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - 52pojie.cn ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2026-3-4 16:14

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表