LilCTF2025 逆向WP-AI版
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.exe,redeme.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
结论:
- 在
0x40ee30构造阶段,把 7 段字符串拼接到对象成员this+0x38。
拼接源:[1c,20,24,28,2c,30,34]。 - 在
0x40fff0,真实变换是按下标交替处理:- 偶数位:
char + 1 - 奇数位:
char - 1
关键证据: 0x410021: je 0x410090(偶数位分支)0x410063: sub esi, 1(奇数位)0x410092: add esi, 1(偶数位)
- 偶数位:
- 在
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]命中:0x1400247c70x140026082
- 目标同为:
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 -> 00x14000153B -> 10x140001546 -> 20x140001551 -> 30x14000155C -> 40x140001567 -> 50x140001572 -> 60x14000157D -> 70x140001588 -> 80x140001593 -> 9
5. 当前静态阶段结论
- 已静态确定:
- 输入缓冲区地址:
0x14003A040 - 题目核心是“运行时改写分发表 + 大量伪分支/错位指令”的控制流迷宫
- 用数据流追踪可收敛到
[rbp+0x228]/[rbp+0x1c8]写回以及对0x14003A040的最终分发调用
- 输入缓冲区地址:
- 尚未完成:
- 纯静态完全还原最终 flag(当前样本把大量路径塞进了基于运行时状态改写后的间接调用,且存在重叠/错位指令干扰)
6. 需要动态调试时的建议(按你的要求明确列出)
如果你允许进入下一步动态验证,我需要至少一个可用 CLI 调试/跟踪工具:
x64dbg(首选,Windows PE)- 或
windbg/cdb - 或
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+0x247dbbu 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 = 0x00007ff7caa40000r8 = 0x00007ff7caa734a0-> 真实调用目标re_obfusheader+0x334a0rdx = 0x00007ff7caa7a040->base+0x3a040(输入缓冲区)rcx = 0x00007ff7caa76158(%100s)
- 结论:此前静态推断完全正确,
0x247db为第一层间接调度点,rdx指向输入缓冲区。
BP2: base+0x2608c
- 命中指令:
call rdx - 关键寄存器:
rdx = 0x00007ff7caa480a9-> 真实调用目标re_obfusheader+0x80a9rcx = 0x00007ff7caa7a040(同一输入缓冲区)
- 结论:最终校验函数入口已动态锁定为
0x80a9。
8.3 动态与静态证据闭环
动态结果与前述静态分析一致:
- 输入缓冲区固定为
base+0x3A040 - 控制流确实通过两层间接调用:
- 第一层:
+0x247db -> call r8 -> +0x334a0 - 第二层:
+0x2608c -> call rdx -> +0x80a9
- 第一层:
+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 { ... }
结论:
- 长度
<40:Your flag is too short! - 长度
>40:Your 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 结果可还原关键变换:
- 单字节高低半字节交换(nibble swap)
- 再与位置相关 key 做异或
并通过差分验证:
- 仅改第
i个输入字符,只会改第i个输出字节。
11.2 辅助静态确认 import thunk
python - << 'PY'
# pefile 解析 IAT
PY
确认:
0x333d0 -> msvcrt!rand0x333a8 -> memcpy0x333b8 -> putchar0x333a0 -> 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 的主流程(静态恢复):
- 输入长度必须为
0x30(48)。 - 分 3 块(每块 16 字节)处理:
tbl:按 key 向量做字节重排。eor:与 key 异或。- 每块结束后
key ^= block_index(广播到 16 字节)。
- 全 48 字节再按 3 字节组做位旋转:
- 第 1 字节
ROL 3 - 第 2 字节
ROR 1 - 第 3 字节不变
- 第 1 字节
- 用自定义 alphabet 做 Base64 编码,得到字符串
enc。 - 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.mdC:/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.exe、python。 - 无
readelf/objdump/r2。 - 有
capstone、pyelftools、angr。
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
发现可疑项:
hello2bootstopkeysha256- 神秘串:
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 结构读取、字符串提取、字节/地址交叉引用、静态反汇编、算法逆向与本地脚本计算。
