平台:ctf.bugku.com|范围:逆向分类(Android、native、托管、IPA) 主张:逆向题的胜负手是定位校验函数,不是把反汇编读完。
0. 分类与首步动作
| 目标 | 首步 | 关键工具 |
|---|---|---|
| APK(Java 层) | jadx 打开,先看 AndroidManifest 与入口 Activity |
jadx / apktool |
| APK(native 层) | 判断是否 native-activity(jadx 里只有 BuildConfig/R)→ 解包取 .so |
r2 / objdump / Ghidra |
| PE(C/C++) | checksec 与入口,找 scanf/strcmp |
IDA / x64dbg |
| PE(.NET) | 先判 CLR 头,再选 dnSpy | dnSpy / dnlib |
| JAR | 解压看 META-INF 与类名,JD-GUI/jadx |
jadx |
| IPA | Mach-O 头、.app 目录、Swift mangling |
otool / Hopper / r2 |

图 1:Android / native / 托管 / IPA 的分层与工具(Archify architecture)
1. Android:java 层与 native 层要分开
1.1 java 层常规路线
jadx -d out app.apk;- 搜
flag、check、verify、decrypt,或从MainActivity.onCreate往下跟; - 常见校验:字符串比较、
MessageDigest摘要、Base64/异或。
1.2 native 层(132 LittleRotatorGame 的完整路径)
- 先判断「Java 层是不是空的」 :jadx 里只有
BuildConfig/R→ 说明是 NDK native-activity ,逻辑全在.so; - 解包取
.so:
bash
unzip app.apk 'lib/*' -d libs
file libs/arm64-v8a/libnative.so
- 先搜格式串 :
.rodata里搜flag{/%s,本例找到"The flag is:njctf{%s}"; - 从其 xref 反查生成函数:
flg(int,char*)@0x2f98,函数体恰好 12 个strb(12 个字符一次写入); - 翻译表达式 :取 7 个十进制位
d[i]=(N/10^i)%10,12 个字符表达式含20*d3-d4的 2³² 抵消、d2*d2*d1*d0-4; - 反解 :由校验式反推
arg1 = 1635274(0 ms),flg_py(1635274)=='PvrNa7iv3Al1',枚举0..2e62.6 s 唯一命中。
踩坑清单:
| 坑 | 表现 | 处理 |
|---|---|---|
| 误读 helper | 把除法/取模识别错 | 查 __aeabi_idiv、__aeabi_idivmod 调用约定 |
| 寄存器归属错 | r3 实际是 d2,按 d3 算卡 20 min |
逐指令标注 def-use,别凭印象 |
| unicorn 模拟失败 | helper 未打桩 | 直接静态翻译,或用 QEMU 跑整段 |
| 2.6 MB dex 误导 | 以为是 Java 逻辑 | 看 dex 里只有 support 框架 → 回 native |
1.3 动态辅助
- frida:
Interceptor.attach(Module.findExportByName(null,'strcmp'), ...)看比较参数; - 或 hook 目标函数打印入参/返回值,比盲猜校验式快得多;
- 注意反调试(
ptrace、/proc/self/status检查),必要时改 smali 或 patch so。
2. 托管层(.NET):先确认是 IL 还是 native
- 看 PE 可选头 Data Directory 第 14 项(CLR)非 0 → 托管;
dnSpy/dnlib打开,字符串常被混淆 → 找strings解混淆函数,或直接动态调用;- IL 阅读要点:
ldstr(字符串)、call(调用)、switch(状态机)、stloc/ldloc(局部变量); - 若被 ConfuserEx 等加壳:先
de4dot脱壳,再按上面流程。
3. PE 原生
checksec:看 ASLR/NX/Canary(同时决定是否有 pwn 空间);- 入口 →
main→ 找输入函数(scanf/ReadFile/getchar)与比较函数(strcmp/memcmp); - 常数与 S 盒:看到 256 字节表 → AES/S 盒;看到 4 个 32 位常数(
67452301/efcdab89...)→ MD5/SHA; - 注意 反调试 (
IsDebuggerPresent、ptrace、时间差检测)与 SMC(自修改代码,需在运行时 dump 解密后段)。
4. IPA / Mach-O 要点
file判断是否 FAT(多架构),lipo -thin arm64拆;- Swift 程序符号被 mangle,用
swift demangle还原可读名; Info.plist看 Bundle ID 与 URL Scheme,Mach-O 看LC_LOAD_DYLIB判断依赖;- 逻辑常在
__TEXT.__text的自定义类里,按字符串 xref 定位更快。
5. 逆向通用方法论
- 先找「输出点」:格式串、打印、文件写入 ------ 顺藤摸瓜找到校验;
- 先找「输入点」 :
scanf/getline、EditText、argv; - 两点之间才是要翻的范围,其余函数一律跳过;
- 能反解就不爆破:由校验式反推输入(132 是反解 + 枚举验证,不是暴力试);
- 动态优先于静态:frida/x64dbg 看一眼入参,胜过读 100 行反汇编;
- 写脚本固化:把定位→翻译→反解→验证串成脚本,下次同型题 1 分钟跑完。
6. 一页检查表
[ ] 先 file / unzip 判格式,别拿 APK 当普通 zip
[ ] jadx 里只有 BuildConfig/R?→ 去 native
[ ] 搜格式串(flag{ / %s)再 xref
[ ] 判断 helper 调用约定(除法/取模/字符串)
[ ] .NET 先查 CLR 目录项,再决定 dnSpy
[ ] PE 看反调试/SMC,动态配合
[ ] 能反解就反解,别写爆破
7. 图形附件(Archify)
| 图 | 文件 | 说明 |
|---|---|---|
| 逆向题型分层与工具链 | reverse-arch.html(交互图,本地交付包) |
APK / native / 托管 / 动态 |
| 逆向作战流程 | reverse-timeline.html(交互图,本地交付包) |
定位→翻译→反解→动态复核→归档 |
流程视图

从判型到归档的主线流程:每步都要求留下原始输出,结论必须能被独立脚本重跑。
两图 archify check 均 issues: [];archify finalize 均 ok:true + browser-check:pass;正文内嵌同源 PNG 截图。