LilCTF 2025 逆向WP-AI版

用于展示skill(雾

本篇WP模型:gpt-5.3-codex-medium

说实话还是很强的,已经薄纱我的逆向水平了

AK LilCTF 2025逆向

1’M no7 A rO6oT

AI伟大,无需多言

无需skill(

LILCTF{83_Vl6l1AnT_@g4insT_phl5hING}

Qt_Creator

1. 约束与目标

  • 题目:立志成为软件开发糕手!诶?我注册码是多少来着
  • 要求:纯静态分析,不运行目标程序。
  • 目标:恢复可通过注册校验的注册码。

2. Skill 记录

略(毕竟要保密的

3. 关键 CLI 调用与结果(节选)

3.1 目录与题目文件确认

Get-ChildItem -Force
Get-Content -Raw .\redeme.txt

结果:目标主程序为 demo_code_editor.exeredeme.txt 仅有测试文本。

3.2 快速 PE 路由(按 pe-fast.md

python ".../pe_fast.py" ".\demo_code_editor.exe"

高价值输出:

  • machine=0x14c(x86)
  • 发现 Register/login 等注册相关字符串
  • 识别出关键业务候选点在 0x40ee30/0x410100 附近

3.3 字符串定位

python ".../pe_strings.py" ".\demo_code_editor.exe"
python ".../pe_xref.py" ".\demo_code_editor.exe" "KJKDS"
python ".../pe_xref.py" ".\demo_code_editor.exe" "Ciallo"
python ".../pe_xref.py" ".\demo_code_editor.exe" --raw-window 256 0x41bed0

定位到注册相关常量:

KJKDS
GzR6`
bsd5s
1q`0t
^wdsx
`b1mw
2oh4mu|

同时定位到成功提示串 Ciallo... 所在校验函数窗口(0x410100)。

3.4 关键语义恢复(静态)

python ".../pe_cg.py" ".\demo_code_editor.exe" 0xee30 1 --force-rva --window-size 0x520 --full-ret --caller-prep
python ".../pe_cg.py" ".\demo_code_editor.exe" 0xfff0 1 --force-rva --window-size 0x240 --full-ret --caller-prep
python ".../pe_xref.py" ".\demo_code_editor.exe" --imm 0x41beed

结论:

  1. 0x40ee30 构造阶段,把 7 段字符串拼接到对象成员 this+0x38
    拼接源:[1c,20,24,28,2c,30,34]
  2. 0x40fff0,真实变换是按下标交替处理:
    • 偶数位:char + 1
    • 奇数位:char - 1
      关键证据:
    • 0x410021: je 0x410090(偶数位分支)
    • 0x410063: sub esi, 1(奇数位)
    • 0x410092: add esi, 1(偶数位)
  3. 0x410100,将变换结果与输入框内容比较;相等时走成功分支(弹 Ciallo...)。

4. 恢复注册码(静态计算)

4.1 被拼接的原串

KJKDSGzR6`bsd5s1q`0t^wdsx`b1mw2oh4mu|

4.2 计算代码(本地一次性静态脚本)

@'
s='KJKDSGzR6`bsd5s1q`0t^wdsx`b1mw2oh4mu|'
print(len(s))
print(''.join(chr(ord(c)+1) if i%2==0 else chr(ord(c)-1) for i,c in enumerate(s)))
'@ | python -

输出:

37
LILCTF{Q7_cre4t0r_1s_very_c0nv3ni3nt}

5. 最终答案

按题目描述恢复出的“可通过注册校验的注册码”为:

LILCTF{Q7_cre4t0r_1s_very_c0nv3ni3nt}

若平台提交格式要求 lilctf{xxx},则应提交:

lilctf{Q7_cre4t0r_1s_very_c0nv3ni3nt}

LILCTF{Q7_cre4t0r_1s_very_c0nv3ni3nt}

obfusheader.h

0. 任务约束

  • 按用户要求:优先使用 Skill 内方法与脚本。
  • 仅进行静态分析;若必须动态调试,需单独说明缺失工具。

1. Skill 路由与检索记录

不给你看(

2. 环境与样本

  • 目录:D:\CTF\skill_improve\rev\lilctf\re-obfusheader
  • 样本:re-obfusheader.exe(64-bit PE)

3. 主要 CLI 调用过程(按阶段)

3.1 首轮 Skill fast-path

python ...\pe_fast.py re-obfusheader.exe

关键结论:

  • 64-bit PE,OEP 0x1400014d0
  • 高信号导入:fputc/fwrite/memcmp/puts/strlen/strncmp
  • 可见字符串有 Please enter the flag:
  • analysis_state 未直接锁定校验函数

3.2 继续按 Skill 分析

被吃掉了(x

4. 关键静态数据流结论

4.1 输入缓冲区

  • 由两处 lea ... [rip+disp] 命中:
    • 0x1400247c7
    • 0x140026082
  • 目标同为:0x14003A040

4.2 两级调度+状态变量

0x140022FC0 附近可见:

  • call 0x140021780 结果存到 [rbp+0x160]
  • [rbp+0x230] = &([rbp+0x160])
  • 若条件不走捷径,则:
    • [rbp+0x228] = [rbp+0x230] + 4
    • [rbp+0xC4] = 0
    • 跳入大段算术/浮点混合块(0x23010 ~ 0x26042

0x140024889 附近另一支:

  • call 0x1400217B4 结果存 [rbp+0x16C]
  • [rbp+0x1D0] = &([rbp+0x16C])
  • [rbp+0x1C8] = [rbp+0x1D0] + 4
  • 进入 0x248BF ~ 0x26042 的一轮写回逻辑后,最终再通过分发表对 0x14003A040 进行调用。

4.3 Opaque helpers(常量函数)

以下 helper 全是常量返回(用于控制流与表达式迷惑):

  • 0x140001530 -> 0
  • 0x14000153B -> 1
  • 0x140001546 -> 2
  • 0x140001551 -> 3
  • 0x14000155C -> 4
  • 0x140001567 -> 5
  • 0x140001572 -> 6
  • 0x14000157D -> 7
  • 0x140001588 -> 8
  • 0x140001593 -> 9

5. 当前静态阶段结论

  • 已静态确定:
    • 输入缓冲区地址:0x14003A040
    • 题目核心是“运行时改写分发表 + 大量伪分支/错位指令”的控制流迷宫
    • 用数据流追踪可收敛到 [rbp+0x228]/[rbp+0x1c8] 写回以及对 0x14003A040 的最终分发调用
  • 尚未完成:
    • 纯静态完全还原最终 flag(当前样本把大量路径塞进了基于运行时状态改写后的间接调用,且存在重叠/错位指令干扰)

6. 需要动态调试时的建议(按你的要求明确列出)

如果你允许进入下一步动态验证,我需要至少一个可用 CLI 调试/跟踪工具:

  1. x64dbg(首选,Windows PE)
  2. windbg/cdb
  3. rizin/radare2(当前环境未安装)

动态最小断点方案(仅供下一步):

  • 断在 0x1400247DB(第一次间接 call r8)和 0x14002608C(最终 call rdx
  • 观察当时 r8/rdx 真实目标、[rbp+0x230]/[rbp+0x1D0]0x14003A040 缓冲区内容
  • 可直接确认真实校验函数入口并回填静态公式

7. 本次新增自写代码记录(均为一次性静态分析脚本)

以下代码均通过 @' ... '@ | python - 方式执行,未落盘,作用分别为:

  • 扫描 thunk 与调用点
  • 扫描 RIP 相对引用命中 .rdata/.data
  • 扫描对分发表区域的写入
  • 解析分发表附近内存
  • 粗解析 0x26140~0x26480 初始化调用链

(已在终端执行,命令与关键输出已在上文逐段记录)

8. 动态调试补充(cdb)

用户提供 cdb 路径:

  • C:\Program Files (x86)\Windows Kits\10\Debuggers\x64\cdb.exe

8.1 CLI 调试命令

版本确认:

& 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x64\cdb.exe' -version

首次命令文件(模块名断点)失败:

  • bu re-obfusheader+0x247db
  • bu re-obfusheader+0x2608c
  • 失败原因:模块符号名未限定解析。

改进后使用 PEB.ImageBase 相对断点(成功):

bp @@masm(poi(@$peb+0x10)+0x247db) ...
bp @@masm(poi(@$peb+0x10)+0x2608c) ...

执行:

& 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x64\cdb.exe' -o -cf cdb_cmds3.txt -logo cdb_run3.log re-obfusheader.exe

8.2 命中结果

BP1: base+0x247db
  • 命中指令:call r8
  • 关键寄存器:
    • base = 0x00007ff7caa40000
    • r8 = 0x00007ff7caa734a0 -> 真实调用目标 re_obfusheader+0x334a0
    • rdx = 0x00007ff7caa7a040 -> base+0x3a040(输入缓冲区)
    • rcx = 0x00007ff7caa76158%100s
  • 结论:此前静态推断完全正确,0x247db 为第一层间接调度点,rdx 指向输入缓冲区。
BP2: base+0x2608c
  • 命中指令:call rdx
  • 关键寄存器:
    • rdx = 0x00007ff7caa480a9 -> 真实调用目标 re_obfusheader+0x80a9
    • rcx = 0x00007ff7caa7a040(同一输入缓冲区)
  • 结论:最终校验函数入口已动态锁定为 0x80a9

8.3 动态与静态证据闭环

动态结果与前述静态分析一致:

  1. 输入缓冲区固定为 base+0x3A040
  2. 控制流确实通过两层间接调用:
    • 第一层:+0x247db -> call r8 -> +0x334a0
    • 第二层:+0x2608c -> call rdx -> +0x80a9
  3. +0x80a9 是后续应深挖的核心校验函数(可继续做混合静/动还原 flag)。

8.4 当前状态

  • 动调成功命中关键断点。
  • 已把真实运行时调用目标从混淆控制流中剥离出来。
  • 下一步应以 0x80a9 为核心做定点逆向(可以继续在 cdb 对比输入字节与中间数组写回过程)。

9. 继续按 Skill 脚本做定点语义(+0x80a9

被吃了(

10. cdb 关键突破:直接命中 memcmp

10.1 先确认长度门槛(CLI 直跑)

$exe = Join-Path (Get-Location) 're-obfusheader.exe'; 1..45 | ForEach-Object { ... }

结论:

  • 长度 <40Your flag is too short!
  • 长度 >40Your flag is too long!
  • 必须 ==40 才进入真正校验。

10.2 用 cdb 在 msvcrt!memcmp 下断

命令文件 cdb_bp_memcmp.txt

bp msvcrt!memcmp ".echo ==== HIT_MEMCMP ====; r; .printf \"rcx=%p rdx=%p r8=%p\n\",@rcx,@rdx,@r8; db @rcx L40; db @rdx L40; kv; q"
g

执行:

@('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA') | & 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x64\cdb.exe' -o -cf cdb_bp_memcmp.txt re-obfusheader.exe

命中结果(核心):

  • r8 = 0x28,即比较长度 40
  • rcx = base+0x3a040,即“输入经变换后的缓冲区”。
  • rdx = 0x...f331,即“目标密文缓冲区”。
  • db @rdx L40 读取到真实目标 40 字节:
5c af b0 1c fc ef c7 8d 01 88 34 39 94 bc 47 2d
0e 7c ef fa 7d 0f d0 fa f8 68 83 fd 73 a8 06 1e
ab 7b 40 bc 67 bb dd 1b

11. 数据流还原变换公式

11.1 写监视命中实际变换子函数

命令文件 cdb_watch_after_entry.txt

bp @@masm(poi(@$peb+0x10)+0x2608c) ".echo ==== ENTER_VALIDATOR ====; r; .printf \"rcx=%p rdx=%p\n\",@rcx,@rdx; ba w1 @rcx \".echo ==== HIT_WRITE ====; r; u @rip-20 L40; kv; q\"; g"
g

命中到 re_obfusheader+0x785b ~ +0x7930 区域,结合 u 结果可还原关键变换:

  1. 单字节高低半字节交换(nibble swap)
  2. 再与位置相关 key 做异或

并通过差分验证:

  • 仅改第 i 个输入字符,只会改第 i 个输出字节。

11.2 辅助静态确认 import thunk

python - << 'PY'
# pefile 解析 IAT
PY

确认:

  • 0x333d0 -> msvcrt!rand
  • 0x333a8 -> memcpy
  • 0x333b8 -> putchar
  • 0x333a0 -> memcmp

与运行时行为一致。

12. 反解脚本与 flag

12.1 反解脚本(CLI 一次性)

base = bytes.fromhex('8c2f603cad9f64ee91df67d8939da67d0c2ffffdadefb11bda3f711c74f9e72e8c9a02bc34fb2fd8')
target = bytes.fromhex('5cafb01cfcefc78d0188343994bc472d0e7ceffa7d0fd0faf86883fd73a8061eab7b40bc67bbdd1b')

def swap_nib(x):
    return ((x & 0xF) << 4) | (x >> 4)

k = [b ^ swap_nib(ord('A')) for b in base]
plain = bytes(swap_nib(target[i] ^ k[i]) for i in range(40))
print(plain.decode('ascii'))

输出:

LILCTF{wH4t_1S_Dat@1LOW_c4n_1T_B3_eAtEn}

12.2 最终验证

'LILCTF{wH4t_1S_Dat@1LOW_c4n_1T_B3_eAtEn}' | .\re-obfusheader.exe

程序输出:Correct flag!

13. 最终答案

LILCTF{wH4t_1S_Dat@1LOW_c4n_1T_B3_eAtEn}

ARM ASM

0. 约束与结论

  • 按要求仅做纯静态分析,未执行 APK、未运行动态调试/Hook。
  • Flag:
LILCTF{ez_arm_asm_meow_meow_meow_meow_meow_meow}

1. Skill 调用与检索记录

保密(


2. CLI 操作过程(含关键输出)

工作目录:D:\CTF\skill_improve\rev\lilctf\re-arm-asm

2.1 初始探测

Get-ChildItem -Force

发现题目文件:

re-arm-asm.apk

2.2 Skill 路线确认

NULL

2.3 工具可用性探测(Skill 脚本)

python -X utf8 "C:\Users\jzwbe\.agents\skills\ctf-reverse\scripts\tool_probe.py" --preset android-jni

关键输出(节选):

[probe]
readelf: missing
objdump: missing
r2: missing
jadx: missing
apktool: missing
strings: available

[route]
Use built-in Python fallback scripts first: jni_symbol_disasm.py, elf_plt_resolve.py, elf_func_dump.py.

2.4 APK 结构分流(Skill 脚本)

python -X utf8 "C:\Users\jzwbe\.agents\skills\ctf-reverse\scripts\apk_triage.py" re-arm-asm.apk

关键输出(节选):

[dex]
classes3.dex size=4060 priority=high
classes2.dex size=482760
classes.dex size=10148172

[libs]
lib/arm64-v8a/libez_asm_hahaha.so

[jni]
Java_work_pangbai_ez_1asm_1hahaha_MainActivity_check
alphabet-candidates:
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ3456780129+/

2.5 DEX 关键词收敛(Skill 脚本)

python -X utf8 "C:\Users\jzwbe\.agents\skills\ctf-reverse\scripts\dex_strings.py" re-arm-asm.apk --query ez_asm --max-hits 50
python -X utf8 "C:\Users\jzwbe\.agents\skills\ctf-reverse\scripts\dex_strings.py" re-arm-asm.apk --dex classes3.dex --query MainActivity --max-hits 50
python -X utf8 "C:\Users\jzwbe\.agents\skills\ctf-reverse\scripts\dex_strings.py" re-arm-asm.apk --dex classes3.dex --query check --max-hits 50
python -X utf8 "C:\Users\jzwbe\.agents\skills\ctf-reverse\scripts\dex_strings.py" re-arm-asm.apk --dex classes3.dex --query loadLibrary --max-hits 50
python -X utf8 "C:\Users\jzwbe\.agents\skills\ctf-reverse\scripts\dex_strings.py" re-arm-asm.apk --dex classes3.dex --query KRD --max-hits 10

确认了关键常量(64 字符):

KRD2c1XRSJL9e0fqCIbiyJrHW1bu0ZnTYJvYw1DM2RzPK1XIQJnN2ZfRMY4So09S

2.6 解包并抽取 so

Expand-Archive -LiteralPath re-arm-asm.apk -DestinationPath .\apk_unpacked -Force
Get-ChildItem .\apk_unpacked\lib\arm64-v8a
Get-ChildItem .\apk_unpacked -Filter classes*.dex | Sort-Object Length

2.7 JNI/ELF 静态反汇编(Skill 脚本)

python -X utf8 "C:\Users\jzwbe\.agents\skills\ctf-reverse\scripts\jni_symbol_disasm.py" .\apk_unpacked\lib\arm64-v8a\libez_asm_hahaha.so --symbol Java_work_pangbai_ez_1asm_1hahaha_MainActivity_check --full-func
python -X utf8 "C:\Users\jzwbe\.agents\skills\ctf-reverse\scripts\elf_plt_resolve.py" .\apk_unpacked\lib\arm64-v8a\libez_asm_hahaha.so
python -X utf8 "C:\Users\jzwbe\.agents\skills\ctf-reverse\scripts\elf_func_dump.py" .\apk_unpacked\lib\arm64-v8a\libez_asm_hahaha.so --symbol Java_work_pangbai_ez_1asm_1hahaha_MainActivity_check --full-func --show-data --xref-imports
python -X utf8 "C:\Users\jzwbe\.agents\skills\ctf-reverse\scripts\jni_symbol_disasm.py" .\apk_unpacked\lib\arm64-v8a\libez_asm_hahaha.so --symbol _Z12decodeBase64PciPS_ --full-func
python -X utf8 "C:\Users\jzwbe\.agents\skills\ctf-reverse\scripts\jni_symbol_disasm.py" .\apk_unpacked\lib\arm64-v8a\libez_asm_hahaha.so --symbol _Z12encodeBase64PciPS_ --full-func

关键数据证据(Skill 输出):

  • 自定义 Base64 alphabet:
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ3456780129+/
  • 数据符号 t(前 16 字节为置换/异或初值):
0d 0e 0f 0c 0b 0a 09 08 06 07 05 04 02 03 01 00

2.8 辅助只读脚本(读取 Skill 内模块信息)

python -X utf8 - << 'PY'
import importlib.util
from pathlib import Path
mod_path = Path(r"C:\Users\jzwbe\.agents\skills\ctf-reverse\scripts\_android_elf.py")
spec = importlib.util.spec_from_file_location("android_elf", mod_path)
mod = importlib.util.module_from_spec(spec)
spec.loader.exec_module(mod)
syms = mod.collect_symbol_records(Path(r"D:\CTF\skill_improve\rev\lilctf\re-arm-asm\apk_unpacked\lib\arm64-v8a\libez_asm_hahaha.so"))
for s in sorted([x for x in syms if x.type=="STT_FUNC" and not x.undefined], key=lambda x:x.value):
    if 0xa00 <= s.value <= 0x1700:
        print(hex(s.value), s.size, s.name)
PY

定位关键函数:

0xae8 _Z12encodeBase64PciPS_
0xe98 _Z12decodeBase64PciPS_
0x10d8 Java_work_pangbai_ez_1asm_1hahaha_MainActivity_check

3. 逆向逻辑还原(静态)

check 的主流程(静态恢复):

  1. 输入长度必须为 0x30(48)。
  2. 分 3 块(每块 16 字节)处理:
    1. tbl:按 key 向量做字节重排。
    2. eor:与 key 异或。
    3. 每块结束后 key ^= block_index(广播到 16 字节)。
  3. 全 48 字节再按 3 字节组做位旋转:
    1. 第 1 字节 ROL 3
    2. 第 2 字节 ROR 1
    3. 第 3 字节不变
  4. 用自定义 alphabet 做 Base64 编码,得到字符串 enc
  5. Java 层拿 enc 与常量
    KRD2c1XRSJL9e0fqCIbiyJrHW1bu0ZnTYJvYw1DM2RzPK1XIQJnN2ZfRMY4So09S
    equals

所以只要对该常量做逆向流程即可得到原始输入(flag)。


4. 编写代码(求解脚本)

新建文件:solve_static.py

#!/usr/bin/env python3
from __future__ import annotations

ALPHABET = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ3456780129+/"
TARGET = "KRD2c1XRSJL9e0fqCIbiyJrHW1bu0ZnTYJvYw1DM2RzPK1XIQJnN2ZfRMY4So09S"

# t[0:16] from libez_asm_hahaha.so (.data, symbol "t")
KEY0 = bytes([0x0D, 0x0E, 0x0F, 0x0C, 0x0B, 0x0A, 0x09, 0x08, 0x06, 0x07, 0x05, 0x04, 0x02, 0x03, 0x01, 0x00])


def custom_b64_decode(s: str) -> bytes:
    idx = {c: i for i, c in enumerate(ALPHABET)}
    out = bytearray()
    for i in range(0, len(s), 4):
        a, b, c, d = s[i : i + 4]
        v0 = idx[a]
        v1 = idx[b]
        v2 = idx[c]
        v3 = idx[d]
        out.append((v0 << 2) | (v1 >> 4))
        out.append(((v1 & 0x0F) << 4) | (v2 >> 2))
        out.append(((v2 & 0x03) << 6) | v3)
    return bytes(out)


def rol8(x: int, n: int) -> int:
    return ((x << n) | (x >> (8 - n))) & 0xFF


def ror8(x: int, n: int) -> int:
    return ((x >> n) | (x << (8 - n))) & 0xFF


def invert_rotate_stage(buf: bytes) -> bytes:
    b = bytearray(buf)
    for i in range(0, 48, 3):
        b[i] = ror8(b[i], 3)       # invert rol3
        b[i + 1] = rol8(b[i + 1], 1)  # invert ror1
        # b[i + 2] unchanged
    return bytes(b)


def invert_block_transform(buf: bytes) -> bytes:
    out = bytearray(48)
    key = bytearray(KEY0)
    for block_idx in range(3):
        block = buf[block_idx * 16 : (block_idx + 1) * 16]
        # forward: y[j] = x[key[j]] ^ key[j]
        # inverse: x[key[j]] = y[j] ^ key[j]
        x = [0] * 16
        for j in range(16):
            x[key[j]] = block[j] ^ key[j]
        out[block_idx * 16 : (block_idx + 1) * 16] = bytes(x)
        for j in range(16):
            key[j] ^= block_idx
    return bytes(out)


def custom_b64_encode(raw: bytes) -> str:
    out = []
    for i in range(0, len(raw), 3):
        a, b, c = raw[i], raw[i + 1], raw[i + 2]
        out.append(ALPHABET[(a >> 2) & 0x3F])
        out.append(ALPHABET[((a & 0x03) << 4) | (b >> 4)])
        out.append(ALPHABET[((b & 0x0F) << 2) | (c >> 6)])
        out.append(ALPHABET[c & 0x3F])
    return "".join(out)


def forward_check(candidate: bytes) -> str:
    key = bytearray(KEY0)
    b = bytearray(candidate)
    for block_idx in range(3):
        src = b[block_idx * 16 : (block_idx + 1) * 16]
        y = [0] * 16
        for j in range(16):
            y[j] = src[key[j]] ^ key[j]
        b[block_idx * 16 : (block_idx + 1) * 16] = bytes(y)
        for j in range(16):
            key[j] ^= block_idx
    for i in range(0, 48, 3):
        b[i] = rol8(b[i], 3)
        b[i + 1] = ror8(b[i + 1], 1)
    return custom_b64_encode(bytes(b))


def main() -> None:
    stage = custom_b64_decode(TARGET)
    stage = invert_rotate_stage(stage)
    plain = invert_block_transform(stage)
    text = plain.decode("utf-8")
    print(text)
    print("verify:", forward_check(plain) == TARGET)


if __name__ == "__main__":
    main()

执行:

python -X utf8 .\solve_static.py

输出:

LILCTF{ez_arm_asm_meow_meow_meow_meow_meow_meow}
verify: True

5. 最终答案

LILCTF{ez_arm_asm_meow_meow_meow_meow_meow_meow}

Oh_My_Uboot

1. 任务约束

  • 题目:Do you like uboot?
  • 文件:re-u-boot
  • 约束:只做纯静态分析,不运行目标二进制。

2. Skills 调用与检索记录

本题分类明确为逆向,因此按要求优先使用 ctf-reverse skill,而不是 solve-challenge 分发器。

2.1 读取的 Skill 文档
  • C:/Users/jzwbe/.agents/skills/ctf-reverse/SKILL.md
  • C:/Users/jzwbe/.agents/skills/solve-challenge/SKILL.md(仅用于确认分发器角色)
2.2 检索的 Skill 内文件(含脚本)

通过递归列出 ctf-reverse 目录确认可用脚本,重点关注:

  • C:/Users/jzwbe/.agents/skills/ctf-reverse/scripts/elf_plt_resolve.py
  • 其余 scripts/*.py 也已检索到(PE/JNI/PowerShell 等)。
2.3 优先调用的 Skill 脚本

先调用 skill 内现成 ELF 脚本:

python -X utf8 C:/Users/jzwbe/.agents/skills/ctf-reverse/scripts/elf_plt_resolve.py .\re-u-boot

输出:

RuntimeError: need objdump or llvm-objdump in PATH

结论:当前环境缺少 objdump/llvm-objdump,因此在满足“优先 skill 脚本”前提后,进入自编静态脚本补充分析。

3. CLI 静态分析过程

3.1 基础识别
Get-Item .\re-u-boot | Select-Object Name,Length,LastWriteTime
Format-Hex -Path .\re-u-boot -Count 128

关键结果:

  • 文件魔数 7F 45 4C 46,为 ELF。
  • EM_ARM(后续 pyelftools 确认),32 位 ARM。
3.2 环境工具可用性检查
where.exe strings; where.exe readelf; where.exe objdump; where.exe r2; where.exe python
python -X utf8 -c "import importlib.util as u;print('capstone',bool(u.find_spec('capstone')));print('elftools',bool(u.find_spec('elftools')));print('angr',bool(u.find_spec('angr')));"

关键结果:

  • strings.exepython
  • readelf/objdump/r2
  • capstonepyelftoolsangr
3.3 编写临时脚本查看 ELF 布局

编写:tmp_elf_layout.py

from elftools.elf.elffile import ELFFile

with open('re-u-boot','rb') as f:
    e=ELFFile(f)
    print('Sections:')
    for i,s in enumerate(e.iter_sections()):
        print(f"{i:2d} {s.name:18s} type={s['sh_type']} addr={s['sh_addr']:##010x} off={s['sh_offset']:##08x} size={s['sh_size']:##08x} flags={s['sh_flags']:##x}")
    print('\nSegments:')
    for i,p in enumerate(e.iter_segments()):
        print(f"{i:2d} type={p['p_type']} off={p['p_offset']:##08x} vaddr={p['p_vaddr']:##010x} filesz={p['p_filesz']:##08x} memsz={p['p_memsz']:##08x} flags={p['p_flags']:##x}")

执行:

python -X utf8 .\tmp_elf_layout.py

关键结果:

  • .text 很小,但 .text_rest 很大(0x69128),主要逻辑在 .text_rest
  • .rodata 地址范围覆盖大量字符串。
3.4 关键字符串筛选
D:\CTF\software\misc\strings\strings.exe -n 3 .\re-u-boot | Select-String -Pattern "Do you like|uboot|bootstopkeysha256|hello2|flag" -CaseSensitive:$false

发现可疑项:

  • hello2
  • bootstopkeysha256
  • 神秘串:5W2b9PbLE6SIc3WP=X6VbPI0?X@HMEWH;
3.5 指针交叉引用(纯静态)

用 pyelftools + 字节检索定位常量地址在代码中的引用:

python -X utf8 -c "...targets=[0x60875f25,0x60875cb3]..."

关键命中:

  • 0x60875cb3(神秘串)在 .text_rest 里有唯一引用点:0x60814080 附近字面量池。
3.6 Capstone 反汇编关键函数

反汇编 0x60813f74 一段后得到核心逻辑:

60813f98: ldr  r1, [pc, ##0xdc]    ; 取加密提示串
60813fa0: bl   0x60801cfc         ; 拷贝 0x26 字节
...
60813fc4: eor  r1, r1, ##0x72      ; 按字节 XOR 0x72 解密提示串
...
60814010: bl   0x60813e3c         ; 对用户输入做变换
60814014: ldr  r1, [pc, ##0x64]    ; r1 = 0x60875cb3 (目标串)
6081401c: bl   0x60864138         ; 比较变换结果与目标串

补充验证提示串:

python -X utf8 -c "...src=get(0x6086d357,0x26); print(bytes([x^0x72 for x in src]))"

得到:

###### Please input uboot password: ######

4. 算法还原(纯静态)

sub_60813e3c 可还原为:

  • 先把输入每个字节 ^ 0x72
  • 再做一个“自定义 base58 编码”,字母表是 ASCII 连续区间 0x30..0x69(共 58 字符)。
  • 将编码结果与常量 5W2b9PbLE6SIc3WP=X6VbPI0?X@HMEWH; 比较。

因此逆向时只需对该目标串做对应解码,再 ^ 0x72

5. 还原脚本(我编写)

target='5W2b9PbLE6SIc3WP=X6VbPI0?X@HMEWH;'
alphabet=''.join(chr(i) for i in range(0x30,0x6a))
val={c:i for i,c in enumerate(alphabet)}

num=0
for c in target:
    num=num*58+val[c]

buf=[]
while num>0:
    buf.append(num & 0xff)
    num//=256
enc=bytes(reversed(buf))
plain=bytes([x^0x72 for x in enc])
print(plain.decode('latin1'))

运行输出:

LILCTF{Ub007_1s_v3ry_ez}

6. 最终答案

  • LILCTF{Ub007_1s_v3ry_ez}

7. 纯静态性说明

  • 未运行 re-u-boot 本体。
  • 所有结论来自:ELF 结构读取、字符串提取、字节/地址交叉引用、静态反汇编、算法逆向与本地脚本计算。