吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 1238|回复: 11
上一主题 下一主题
收起左侧

[CTF] [2026]春节红包题解(大部分)

[复制链接]
跳转到指定楼层
楼主
jasomlyc 发表于 2026-3-4 10:17 回帖奖励
本帖最后由 jasomlyc 于 2026-3-4 22:58 编辑

第一次参加,勉勉强强把题目全解了。(昨晚最后才把 Win 高级整出来)

暂时先发一部分,有的题目本地都删了。。。任务那里也不能再下载。


之二

使用工具:IDA v8.3

没壳,直接 IDA 分析。

浏览下找到 start 函数, F5。

这个函数是 C 运行时的启动函数,不包括实际的运行逻辑,我们要找的程序入口点在 result 处。


(这里直接搜索 String 也能找到下面)

进入下面红框区域(显示 Flag 正确)的条件是蓝色处 if 为 1

下面的 while 循环在将用户输入与 Block 的每个字符一一比较,最后返回相同的字符个数是否等于 31(也即最终 Flag 长度)。明显蓝色处就是在初始化 Block。(上一个语句应该是 new 一块区域)

进去一看很明显的字符串异或解密。

模仿写出以下脚本:

def decrypt_sub_401620():
    dwords = [758280311, 1663511336, 1880974179, 494170226, 842146570, 657202491, 658185525]
    word = 12323
    byte = 99

    xor_key = 0x42
    buffer = bytearray()

    for d in dwords:
        buffer.extend(d.to_bytes(4, 'little'))

    buffer.extend(word.to_bytes(2, 'little'))
    buffer.append(byte)

    decrypted = "".join(chr(b ^ xor_key) for b in buffer)

    return decrypted

print(f"Decrypted: {decrypt_sub_401620()}")

(这题挂个调试器上去看应该会更快)


之三

使用工具:JADX 1.5.4

(这题网上搜个解法器会快点。。下面我这个流程要不是在过年真没耐心做完。)

直接找 MainActivity,很明显用 Compose 写的。

找到 setContent 以及内容的来源。



Switch 分支走的是 default,但是调用的 f6536a 会再调回来走 case 0.

指令太多 JADX 直接罢工。(记住这个地方后面还会再来)

于是搜索 FLAG,找到了一个 UI 组件。可以推测 f7612q 大概率是 Flag。这个参数是在初始化时引入的,所以要找到是哪里创建了 u1.m.


回到刚刚的 AbstractC0726k.d,在 Smali 里查询。

v23 来源于 v9, 与 w1.g 有关。

这三个里明显 l 更可能是 Flag 值。于是接下来找哪个地方会赋值给它。

回到之前的 u1.m,可以找到 w1.g 作为参数调用了 F.C. 在这个类我们找到了创建 w1.d 的出现。


往上翻一翻,下面这部分其实是取密钥和密文的阶段。(这里应该是另外一个 Case)

而密文从 r1.a 中获取

def decrypt_flag():
    # [0x36, 0x01, 0x16, 0x1c]
    key = [54, 1, 22, 28]

    encrypted_data = [
        [80, 109, 119, 123, 77],
        [97, 116, 34, 45, 105],
        [102, 49, 124, 45, 5, 94],
        [4, 49, 36, 42, 105],
        [101, 113, 100, 45, 88, 102, 73],
        [112, 50, 101, 104, 7, 119, 34, 112, 75]
    ]

    flag = ""

    for row in encrypted_data:
        for i, encrypted_char in enumerate(row):
            decrypted_char = encrypted_char ^ key[i % len(key)]
            flag += chr(decrypted_char)

    return flag

if __name__ == "__main__":
    print(f"Result: {decrypt_flag()}")

之四

使用工具:
https://github.com/extremecoders-re/pyinstxtractor
https://github.com/greenozon/pycdc/tree/py314 (原版只支持到 3.12 所以用了别人的 Fork)(需要自行编译)

(看图标就知道用了什么)用 PyInstaller 打了包,用 Extractor 拆出来。
有其他别的依赖之类的东西,但下面这个文件名就很明显。


对上面的 pyc 用 pycdas 解出操作码。


照着以上两个函数写个脚本解密即可

import base64

def get_encrypted_flag():
    enc_data = 'e3w+fiRvfW18fnx4ZAZ6Pj43YwB9OWMXfXo8Dg4O'
    return base64.b64decode(enc_data)

def generate_flag():
    encrypted = get_encrypted_flag()

    key = 78

    result = bytearray()

    for _, byte in enumerate(encrypted):
        result.append(byte ^ key)

    return result

if __name__ == '__main__':
    print(generate_flag())

之五

使用工具:

IDA v8.3
https://github.com/extremecoders-re/nuitka-extractor
https://www.angusj.com/resourcehacker/
参考:
https://www.52pojie.cn/forum.php?mod=viewthread&tid=2063208

用 DIE 可以看到用了 Nuitka 打包,用工具解出来。不像 PyInstaller 可以解出 pyc,这次只能得到 dll.

之后分析过程照抄上面的论坛帖子即可,不过由于版本更替,相应的分析脚本需要更新。

总之最终能得到主函数里下面类似的输出

--- Blob: __main__ | Size: 0x62b | Objects: 83 ---

  [0]: [b'dc!a;`b', '<Runtime_Identifier_17>', b'cacg', '<Runtime_Identifier_47>', b'\x19e!!(', '<Runtime_Identifier_14>', b'\x1fb&', '<Runtime_Identifier_14>', b'\x08be#', b'ppp']
  [1]: '_parts'
  [2]: 81
  [3]: '_key'
  [4]: 30
  [5]: '_total_len'
  ... 以下省略

刚刚好第一部分里有 30 个字符,相对应的 81 就是密钥。猜测是异或解密(也确实是),所以用以下脚本解决。

def decrypt_flag():
    key = 81

    encrypted_parts = [
        b'dc!a;`b',
        bytes([17]),
        b'cacg',
        bytes([47]),
        b'\x19e!!(',
        bytes([14]),
        b'\x1fb&',
        bytes([14]),
        b'\x08be#',
        b'ppp'
    ]

    ciphertext = b"".join(encrypted_parts)

    decrypted_chars = [chr(b ^ key) for b in ciphertext]
    flag = "".join(decrypted_chars)

    print(f"Result: {flag}")

if __name__ == "__main__":
    decrypt_flag()

之六

使用工具:binwalk

Credit 和 License 两个文件把用的什么技术全交代了。
用 binwalk 把 exe 里藏的东西解出来。


main.lua 里给出了资产的解密逻辑。

于是

def extract_flag(file_path):
    key = b"52pojie"

    with open(file_path, "rb") as f:
        encrypted_content = f.read()

    decrypted_bytes = bytearray()

    for i, byte in enumerate(encrypted_content):
        key_byte = key[i % len(key)]
        decrypted_bytes.append(byte ^ key_byte)

    flag = decrypted_bytes.decode('utf-8')
    print(f"Decrypted Flag: {flag}")

extract_flag("assets/flag.dat")

之七

使用工具:IDA v8.3

都到这题就不讲开头怎么找了。核心函数是 sub_140008720
sub_140008640 不需要返回值,大概率是初始化函数。(0xC96C5795D7870F42, ECMA 182 的多项式)


sub_140008500 是 Hash Update.

以下代码将 52pojie_2026_ 与用户输入组合后送入 sub_140008580 (Hash Final)
再从文件流读 0x10 的文件头一起送进 sub_140008310


函数先读取文件的 Magic Number,确认无误后再拿出 IV 送进 sub_140008360


do while 块将文件的再后 16 字节赋值给了 a1 + 16 。最下 return 处也复制了 unk_14000A270 开始的 256 字节给 a1 + 4(注意此处 a1 是 QWORD*)

接下回主循环看是怎么解密文件的。

sub_1400081E0 以 8 个字节为单位循环将文件流交给 sub_140008080 (解密)


这里有点难看,但关注下

v7 = __ROL8__(v5, 3);
do
{
  v7 = (v7 << 8) | *((unsigned __int8 *)a1 + (HIBYTE(v7) | 0x221300) - 2233056);
  --v2;
}
while ( v2 );

其中 (HIBYTE(v7) | 0x221300) - 2233056) 可以简化成 HIBYTE(v7) + 32,等价于在 a1 + 32 后查表(即先前 memcpy 的部分)

再关注下方

result = v6[21568] ^ v6[21584] ^ v9;

事实上在做的是
$Plaintext[i] = Keystream[i] \oplus Previous\_Ciphertext[i] \oplus Ciphertext[i];$
即块密码工作模式中的密文反馈(CFB)。这种工作方式要求 IV 是随机的以抗破解。

整合以上内容,再加上明文内容为 PNG 文件,即文件头(89 50 4E 47 0D 0A 1A 0A)已知。
上方公式可重写为

$$Keystream_0 = Plaintext_0 \oplus IV \oplus Ciphertext_0$$

再模仿上方 PRNG (v7)的运算方法,就能恢复出完整文件。

代码如下:

import struct

# (unk_14000A270)
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 rol8(val, shift):
    return ((val << shift) & 0xFFFFFFFFFFFFFFFF) | (val >> (64 - shift))

def get_next_state(state):
    v7 = rol8(state, 3)
    for _ in range(8):
        idx = (v7 >> 56) & 0xFF
        v7 = ((v7 << 8) & 0xFFFFFFFFFFFFFFFF) | SBOX[idx]
    return v7

def xor_bytes(b1, b2, b3):
    return bytes(x ^ y ^ z for x, y, z in zip(b1, b2, b3))

def decrypt_file(file_path):
    with open(file_path, "rb") as f:
        data = f.read()

    iv = data[8:16]
    ciphertext = data[16:]

    png_magic = b"\x89PNG\r\n\x1A\n"
    c_0 = ciphertext[0:8]

    keystream_0 = xor_bytes(png_magic, iv, c_0)
    state = struct.unpack("<Q", keystream_0)[0]
    print(f"[+] Initial State Recovered: {hex(state)}")

    decrypted_blocks = [png_magic]
    prev_c = c_0

    for i in range(8, len(ciphertext), 8):
        c_i = ciphertext[i:i+8]

        state = get_next_state(state)
        keystream_bytes = struct.pack("<Q", state)

        p_i = xor_bytes(keystream_bytes, prev_c, c_i)

        decrypted_blocks.append(p_i)
        prev_c = c_i 

    plaintext = b"".join(decrypted_blocks)

    pad_len = plaintext[-1]
    if 0 < pad_len <= 8:
        plaintext = plaintext[:-pad_len]

    out_path = file_path.replace(".encrypted", ".decrypted.png")
    with open(out_path, "wb") as f:
        f.write(plaintext)
    print(f"[+] Success! {out_path}")

if __name__ == "__main__":
    target_file = "flag.png.encrypted" 
    decrypt_file(target_file)

打开原图仅是图标并没有 Flag,猜测是某种隐写术:


之九

使用工具:
随便找个浏览器按 F12
https://github.com/WebAssembly/wabt

验证逻辑在 verify.js 里,将我们的输入 SHA256 0x2026 次后跟预期结果比对,因此完全不可能爆破出。
单靠听力也是不可行的,因为区分了大小写。


可以看到 UID 到 Hash 的生成逻辑在 WASM 的 gen 函数里。

使用 wasm-decompile 将 WASM 转为略接近 C 的语句。

听了下生成的语音,感觉上像是拼接合成。因此代码里应该有用明文字符去查表找音频片段的代码。
因此搜索 1179011410(= RIFF .wav 文件的 Magic Number)

下方的 1163280727 (= WAVE)也再次验证我们找到了合成音频的开始,由此往上找。

不远处的大 f 函数是一个解码器( UTF-8 )而 a 是对应的字符。
再加上 f(d + 560, a = (a & 255) ^ 204);,很大概率 a 就是要找的明文字符。


在控制台找到相应位置下断点。

听一下录音核对就知道找对了地方,接下来写个 Logpoint 收集完然后集合即可。


免费评分

参与人数 7威望 +2 吾爱币 +105 热心值 +6 收起 理由
Hmily + 2 + 100 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
cherzsh + 1 + 1 用心讨论,共获提升!
循环水 + 1 + 1 谢谢@Thanks!
gufangkai + 1 + 1 我很赞同!
Coxxs + 1 用心讨论,共获提升!
aniuxyz + 1 我很赞同!
llurry + 1 + 1 我很赞同!

查看全部评分

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

推荐
Hmily 发表于 2026-3-4 11:23
题目还没来得及打包,没考虑到结束后无法查看题目(后台已经打开即使完成题目也可以看到),我暂时把所有题目地址更新到https://www.52pojie.cn/thread-2092302-1-1.html 活动帖中,可以直接下载了,后面我统一打包一个大包。

@jasomlyc 文章补充完请回复我,我再一起加分哈~
推荐
Poner 发表于 2026-3-4 11:36
Command 发表于 2026-3-4 11:26
Windows高级能给个源码吗, 很好奇这个CM是怎么实现的

其实现在的高级题还是个阉割版   之前的完整版因为内部评估难度过高  给他减弱了还换了算法。。

你可以在继续分析看看   后续可能会开源
沙发
LittleK 发表于 2026-3-4 11:13
3#
Poner 发表于 2026-3-4 11:20
稍后题目文件都会打包上传爱盘  可以关注下爱盘下载
5#
Command 发表于 2026-3-4 11:26
Poner 发表于 2026-3-4 11:20
稍后题目文件都会打包上传爱盘  可以关注下爱盘下载

Windows高级能给个源码吗, 很好奇这个CM是怎么实现的

点评

其实现在的高级题还是个阉割版 之前的完整版因为内部评估难度过高 给他减弱了还换了算法。。 你可以在继续分析看看 后续可能会开源  详情 回复 发表于 2026-3-4 11:36
6#
aniuxyz 发表于 2026-3-4 11:31
我只解出来31位这个,密码解不出来,摸到门,还没有进去。。。
8#
wushishen 发表于 2026-3-4 11:42
学习一下了
9#
fdk0305 发表于 2026-3-4 12:17
学习一下下
10#
liulangdeshi 发表于 2026-3-4 12:21
支持下,感谢分享~~
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

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

GMT+8, 2026-4-12 00:23

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

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