第一届 Polaris CTF 招新赛 Reverse wp
字数 8164
更新时间 2026-04-02 14:18:22

Polaris CTF 招新赛逆向题(Reverse)Writeup 教学文档

题目1: Illusion [Hook]

1. 核心要点

题目表面是RC4加密,实则通过Hook机制(MessageBoxA)转向AES解密流程以获取真实flag。

2. 程序逻辑分析

  1. 主程序(伪RC4)

    • 程序入口接收长度为25的输入,格式为xmctf{...}
    • sub_140001440函数模拟了标准RC4算法:使用密钥nev_gona_give_up初始化S盒,与用户输入的中间18字节进行异或,并与一段硬编码的18字节常量比较。
    • 若匹配,可逆推出一个结果nev_gona_letydown\x07,但这是一个假flag
  2. Hook机制与真实校验

    • 程序初始化时调用sub_140001000,动态获取user32.dllMessageBoxA函数的地址。
    • 通过VirtualProtect修改内存页权限,将MessageBoxA函数的前几个字节替换为跳转指令,将其Hook到自定义函数sub_1400010F0
    • 因此,任何触发MessageBoxA弹窗的操作都会实际执行sub_1400010F0,此函数内包含真正的flag校验逻辑。
  3. 真实校验(AES解密)

    • sub_1400010F0中,存在第二层校验。
    • 密钥生成:由4个DWORD值构成:[873608210, 873608210, 873608210, 559105345]。按小端序(<IIII)打包后得到16字节密钥:b'\x12\x34\x12\x34\x12\x34\x12\x34AES!'
    • 密文:一段固定的32字节十六进制字符串:f27b7e75b45c08fa193c8a4a04f81f671b059ce72740786d28f6a8b806c6c551
    • 解密算法:使用AES-128-ECB模式,并去除PKCS#7填充。

3. 解题脚本(Exp)

from struct import pack

# AES算法相关函数定义(此处省略,同文档中给出的完整实现)
# 包括:SBOX, INV_SBOX, RCON, bytes2matrix, matrix2bytes, add_round_key,
# inv_sub_bytes, inv_shift_rows, xtime, mul, inv_mix_columns,
# expand_key, decrypt_block, ecb_decrypt

# 核心解密部分
key = pack("<IIII", 873608210, 873608210, 873608210, 559105345)
ct = bytes.fromhex("f27b7e75b45c08fa193c8a4a04f81f671b059ce72740786d28f6a8b806c6c551")
pt = ecb_decrypt(ct, key)
pad = pt[-1]
pt = pt[:-pad]
flag = b"xmctf{" + pt + b"}"
print(flag.decode())

Flag: xmctf{R3a1_w0rld_M47ters}


题目2: ez_uds

1. 核心要点

考察汽车统一诊断服务(UDS)中的0x27 SecurityAccess服务。

2. 协议流程

  1. 客户端发送27 01请求种子(Seed)。
  2. 服务端回复67 01 <4字节种子>
  3. 客户端需根据算法计算出4字节密钥(Key)。
  4. 客户端发送27 02 <4字节密钥>
  5. 服务端验证密钥,通过则认证成功。

3. 算法与逆向

  • 文档中以示例说明:服务端返回67 01 1A 2E 0E 7F,则种子为0x1A2E0E7F
  • 计算出的密钥为0x0E91B54D
  • 关键细节:种子和密钥都需要按大端序(Big-Endian)解析和发送。若按小端序处理,会收到否定响应7F 27 35
  • 解题关键在于逆向出从种子计算密钥的具体算法函数。

题目3: ezFinger [IOT]

1. 核心要点

考察STM32等嵌入式系统固件逆向,识别HAL库函数。

2. 函数分析

  • sub_8003498:通过直接访问内存地址0x40023808(RCC->CFGR寄存器),检查系统时钟源(SWS位)。逻辑与HAL_RCC_GetSysClockFreq函数一致,用于获取系统时钟频率。
  • sub_8000EC0:通过引脚映射表操作GPIO的BSRR寄存器,实现设置引脚高低电平,对应digitalWrite函数。

3. Flag

xmctf{HAL_RCC_GetSysClockFreq_digitalWrite}


题目4: Hulua [Lua 5.3 字节码]

1. 核心要点

逆向被异或加密的Lua字节码,分析其自定义的校验逻辑。

2. 初步分析

  • main函数加载用户输入到Lua虚拟机的全局变量user_input中。
  • sub_1400014A4对一段内存数据(起始偏移0x31a00,长度0x3dc)用密钥"hulua"进行循环异或解密,得到Lua字节码。

3. Lua字节码分析

  • 使用Luatool.cnunluac等工具对解密后的.luac文件进行反汇编。
  • 分析发现两个关键常量字符串:
    1. "78 6D 63 74 66 32 30 32 36" -> ASCII转换后为"xmctf2026",作为RC4密钥。
    2. 一段32字节的十六进制字符串,作为目标密文。
  • 校验逻辑:对用户输入进行RC4加密(密钥为"xmctf2026"),然后将结果每个字节与0x66异或,最后与目标密文比较。

4. 解题脚本思路

  1. 用密钥"xmctf2026"对目标密文进行RC4解密。
  2. 将解密结果的每个字节与0x66异或,得到最终flag。

题目5: hajimi [模型逆向]

1. 核心要点

逆向一个由Tracr编译生成的Transformer模型,理解其内部逻辑以构造正确输入。

2. 文件分析

  • main.py:加载challenge.pkl.zst中的模型,检查输入(16位,每位为1/2/3/4),将输入送入模型前向推理,并解码输出。
  • challenge.pkl.zst:Zstandard压缩的Pickle序列化文件,内含模型权重、配置和关键的residual_labels

3. 模型逆向关键

  • residual_labels 是核心,它将Transformer残差流(residual stream)中的维度映射到有意义的变量名(如sequence_map_*, map_*)。
  • 通过运行模型并dump特定变量(如map_53, map_52, map_50, map_41)的值,可以发现模型将输入的16个位置分成了4组:
    • map_53 = 1 出现在位置:1, 7, 10, 16
    • map_52 = 1 出现在位置:2, 8, 9, 15
    • map_50 = 1 出现在位置:3, 5, 12, 14
    • map_41 = 1 出现在位置:4, 6, 11, 13
  • 这四组分别对应数字1, 2, 3, 4

4. 构造输入

根据分组规则,确定16个位置分别对应的数字。将得到的数字串进行SHA-256哈希,最终结果即为flag。


题目6: FunPyVM [自定义虚拟机]

1. 核心要点

逆向一个自定义字节码虚拟机(VM),理解其指令集,对两段字节码程序进行反汇编和逆向。

2. 虚拟机架构

基于kernelVM.pyc还原出的虚拟机核心组件:

  • 寄存器R0(堆块指针),R1(通用),PC(程序计数器)。
  • 内存heap虚拟堆。
  • 关键指令
    • 0x10 imm: ALLOC 分配内存
    • 0x11 imm: LOAD 加载值到R1
    • 0x12 imm: STORE 存储R1到内存
    • 0x52 0x01: 读取输入
    • 0x35 imm: 对内存单元异或 heap[R0][imm] ^= R1
    • 0x40 imm: CMPEQ 比较
    • 0x42/0x43 imm: 条件跳转

3. 第一阶段:opcode.bin

  • 读取用户输入的前27字节,进行多轮变换(包括与常量异或、循环移位、与索引运算等)。
  • 最终与一个27字节的常量表比较。
  • 注意:此阶段只检查前27字节,可通过逆变换得到一段假flag。

4. 第二阶段:ntbase.pyd

  • 该文件并非真正的.pyd,而是另一段VM字节码。
  • 同样校验输入的前27字节,但使用不同的变换算法:
    1. 每字节加10。
    2. 从下标1开始,每个字节与前一个已处理的字节异或。
    3. 根据奇偶位,分别加上不同的常数。
  • 最终结果与另一组27字节常量表比较。

5. 真实Flag获取

对第二阶段(ntbase.pyd)的算法进行逆变换,得到正确输入:
xmctf{F0n_And_3asyViMGa1v1eF9rY@u}


题目7: 移动的秘密

1. 核心要点

输入经过移位和MD5双重校验,需通过约束求解或爆破得到唯一解。

2. 校验逻辑

  1. 移位校验:对输入的29个字符,每个字符右移1位(c >> 1),结果与一个29字节的目标数组target逐字节比较。
  2. MD5校验:对原始输入(移位前)计算MD5,与固定值3a22c098710019b31c328a861429d3ad比较。

3. 求解思路

  • c >> 1 == target[i]可知,每个输入字符c只有两种可能:2 * target[i]2 * target[i] + 1
  • 结合flag常用字符集[a-z0-9_{}],可以大幅缩小每个位置的候选字符范围(通常只剩1-2个)。
  • 前几位可确定为xmctf{
  • 对剩下的可能组合,计算MD5并与目标碰撞,即可得到唯一正确的flag。

题目8: BankGuardian [.NET 混淆与加载器]

1. 核心要点

主程序是一个加载器(Loader),解密并执行隐藏的.NET程序,后者包含真正的flag校验逻辑。

2. 第一阶段:加载器分析

  • main函数动态解析kernel32.dll
  • 从程序.data段复制一段加密数据(0xE400字节)到新内存。
  • 调用解密函数sub_140003860(实质是ChaCha20流密码)。
  • Key/Nonce生成:通过固定种子2356100023,使用LCG(线性同余生成器)x = (1103515245 * x + 12345) & 0xffffffff生成44字节,前32字节为Key,后12字节为Nonce。
  • 解密后得到一个PE文件(.NET程序),写入临时目录并用dotnet运行。

3. 第二阶段:.NET程序分析

  • 程序高度混淆,但核心是几个返回flag片段的方法。
  • 关键类_oGzm5MCMGO4Vr3Mm5lKqB6mpDnh中包含:
    • RealSecret2(Byte ssnKey):从资源MalwareStage2.Resources.Config.bin中读取字节,与ssnKey(值为24)异或,得到"D0tN3t_"
    • RealSecret3(Byte ssnKey):类似,得到"1nj3ct10n_"
    • _VsBYyyEZbFsjoKNf29WydChhMSx():返回"Pwn3r}"
  • 程序开头还硬编码了第一部分"xmctf{R3fl3ct1v3_"

4. 最终Flag

拼接所有部分:xmctf{R3fl3ct1v3_D0tN3t_1nj3ct10n_Pwn3r}


题目9: ezLanguage [易语言/花指令]

1. 核心要点

程序由易语言编写,并被大量花指令(Junk Code)保护,需要清理花指令后分析。

2. 花指令特征

存在大量形如call $+5; add [esp], xx; retn的短跳转花指令,干扰反汇编器的线性分析。需用IDAPython脚本或手动将特定地址范围(如0x4012DB-0x4012DF)nop掉。

3. 核心逻辑

清理花指令后,可分析出程序的核心是查表替换

  • 程序内置一个字母表(Alphabet,可能为35个字符)。
  • 对用户输入的每个字符,在表中查找其位置,经过特定计算(如(index + 5) % 35)得到新索引,再用新索引从表中取出字符,构成输出字符串。
  • 将输出与一个固定的密文字符串比较。

4. 解题方法

  1. 逆向出完整的字母表和映射关系。
  2. 对目标密文进行逆映射,即可得到原始输入(flag)。

题目10: MixTielele [Android 多层封装与协议逆向]

1. 核心要点

Android应用多层混淆,Java层为壳,核心逻辑在Native层和隐藏的Dex中,涉及Protobuf序列化和自定义加密。

2. 逆向流程

  1. Java层:仅作包装,调用native方法EncTitlele
  2. Native层 (libmixtitlele.so):负责将内层登录串封装成外层JSON。
  3. 隐藏逻辑libflutter.so是伪装文件,内含第二个Dex,包含真正的登录构造逻辑。

3. 登录协议分析

  • 核心数据:一个Protobuf消息,包含两个字段:user (字符串) 和 isHacker (布尔值)。
  • 目标:以admin身份且isHacker=true登录。
  • 示例
    • user="admin", isHacker=true 对应的Protobuf编码为:0A 05 61 64 6D 69 6E 10 01

4. 加密流程

  1. 第一层加密(Java):对Protobuf明文进行自定义异或流加密(固定种子),然后Base64编码,得到inner_login
  2. 第二层加密(Native)
    • 随机生成16字节AES Key。
    • 用RSA公钥加密该AES Key,得到a1(Base64)。
    • 用该AES Key以AES-CBC模式(IV为16个\x00,PKCS7填充)加密inner_login,得到b2(Base64)。
    • 构造外层JSON:{"a1": a1, "b2": b2},POST到/login

5. 解题步骤

  1. 构造正确的Protobuf数据(admin, true)。
  2. 实现第一层自定义异或流加密,得到inner_login
  3. 模拟第二层Native加密流程,生成最终的POST数据。
  4. 向服务器发送请求,获取flag。

题目11: Disguise [VM、SM4、文件隐藏]

1. 核心要点

两阶段程序:第一层给出假flag并隐藏第二层PE;第二层是一个实现魔改SM4算法的小型VM。

2. 第一层程序

  • main函数:校验输入input[i] - i与常量比较,可得假flag。
  • sub_412C10函数:从.data:0x41C000开始,读取DWORD表,将每个DWORD的低字节与0x07异或,提取出0x15000字节的第二层PE文件。

3. 第二层程序与VM

  • VM结构:典型的取指-译码-执行循环,有私有内存空间、指令分派(Switch-Case)。
  • 算法识别:通过分析常量(FK, CK, S-Box)和控制流,确定VM实现了魔改的SM4分组密码算法
    • 轮密钥扩展:使用种子密钥"We1c0me_t0_xmctf"与FK异或,并通过32轮扩展生成轮密钥。
    • 分组加密:将48字节输入分成3块,每块进行32轮SM4加密。
  • 最终比较:加密结果与.data:0x421018处的12个DWORD(48字节)比较。

4. 解密获取Flag

  1. 提取密文(0x421018处,48字节)。
  2. 使用密钥"We1c0me_t0_xmctf",按照SM4算法进行解密(注意轮密钥逆序使用)。
  3. 连接3个解密后的明文块,得到最终flag。

题目12: Oracle Eye [模型逆向、频域隐写]

1. 核心要点

程序使用一个ONNX模型进行“人脸识别”,但模型实际在频域(DCT)埋设了后门触发器(Trigger),正确触发才能获得flag。

2. 输入输出

  • 输入:64x64的PGM(P5)格式灰度图像。
  • 输出:分类ID、指纹(fingerprint)、触发分数(trigger_score)。
  • Flag条件:分类为“神谕”(ID=4)触发分数接近1,才能通过深层验证。

3. 模型逆向分析

  • 模型结构
    1. 对输入图像做2D-DCT(离散余弦变换),转换到频域。
    2. 仅检查频域矩阵中四个固定坐标点的值:(5,5), (10,10), (15,15), (20,20)
    3. 计算这四个点的值与目标值的接近程度,通过公式生成trigger_score。当四点都命中时,trigger_score ≈ 1
    4. 模型后半部分的卷积网络(Conv)等结构在触发后不起决定作用。
  • 后门机制:当trigger_score高时,模型输出的fingerprint会逼近一个固定的secret_vector

4. 构造攻击图像

  1. 创建一个64x64的全零频域矩阵C
  2. 在四个坐标点(5,5), (10,10), (15,15), (20,20)上填入模型期望的目标浮点数值
  3. 对矩阵C进行逆DCT变换,得到空间域图像I
  4. 对图像I整体加0.5偏移并裁剪到[0,1]范围,保存为PGM格式。
  5. 将此图像输入程序,即可获得高触发分数和正确分类,从而得到flag。

Flag: xmctf{Y0u_H4v3_Tru1y_S33n_Th3_0r4c13_1n_Th3_N0is3}


题目13: easyre [控制流平坦化、网络协议]

1. 核心要点

分为客户端(client.exe)和服务端(server.exe)。服务端被控制流平坦化混淆,校验逻辑通过网络通信进行。

2. 程序角色

  • client.exe:交互前端,接收用户输入,打包发送给127.0.0.1:5566,显示结果。
  • server.exe:真正的校验器,监听5566端口。

3. 通信协议

  • 通用格式[14字节大端长度] + [RC4加密的数据]
  • 第一阶段 (Username)
    1. 客户端发送加密后的用户名。
    2. 服务端RC4解密后,计算其MD5(32位小写hex)。
    3. 与目标MD5值比较。目标值为:4772e2e6b3f5c2a1f2b2f3c4d5e6f7a8
    4. 原像:该MD5对应的用户名为polaris
  • 第二阶段 (Serial/Flag)
    1. 用户名正确后,客户端发送加密后的serial。
    2. 服务端RC4解密后,与内部硬编码字符串直接比较。
    3. 目标字符串c90a6b4f3e2d1a0b9c8d7e6f5a4b3c2d

4. 加密算法识别

通过API断点(BCryptOpenAlgorithmProvider)分析,确认使用RC4和MD5算法。

5. 解题步骤

  1. 运行server.exe
  2. 运行client.exe,第一轮输入用户名:polaris
  3. 第二轮输入serial/flag:c90a6b4f3e2d1a0b9c8d7e6f5a4b3c2d

题目14: ez_uds_plus

本题是ez_uds的升级版。在已掌握UDS 0x27服务流程和第一层算法的基础上,需要进一步分析流量包或程序逻辑,找出第二层(level2)所使用的密钥生成算法。密钥可能为Polaris_ctf_2026,但需验证其具体使用方式。


题目15: ShakeLife

题目要求输入长度为43的可打印ASCII字符串。程序的核心是一个自定义的变换函数sub_41D9,它使用一个由32字节ASCII常量3ff653606890a0591e204093a0d59202初始化生成的复杂状态上下文(涉及sub_3BF3),对输入进行多轮混合、查表、移位、异或等操作。最终将43字节输出与目标密文8da4efed737854927e5ff15ac2c310e6c27b3012d2a78cc0cccb04c2d766b6437f17b25b85400070899b8f比较。解题关键在于逆向分析sub_41D9sub_694Bsub_64B8等函数的详细操作,实现逆变换。

相似文章
相似文章
 全屏