逆向 App 与网站
可落地工作流指南
把散落的开源逆向工具,串成一条从侦察到复现、可复核的流程 —— 覆盖原生二进制、Android、iOS、Web/JS 与 Electron。
这是一份按目标类型组织的实操工作流,把散落的开源逆向工具串成"侦察 → 静态 → 动态 → 协议 → 复现"的可重复流程。默认你已经在用 REA 做统一证据与静态/受控动态分析,这里补充的是它不覆盖或覆盖较弱的环节:动态插桩、抓包改包、脱壳、JS 反混淆与补环境、符号执行。
每个场景的验收标准是同一条:能在脱离原环境的情况下,独立复现出目标的某个可观测行为,并留下可复核的证据。
1. 工具矩阵速查#
按"你在干什么"查,不按工具分类查。
| 我需要的动作 | 首选工具 | 仓库 | 备注 |
|---|---|---|---|
| 反编译原生二进制 | Ghidra | NationalSecurityAgency/ghidra |
多架构、免费、可脚本化 |
| 命令行批量逆向 | radare2 / rizin | radareorg/radare2 · rizinorg/rizin |
适合自动化与 CI |
| 图形化 r2 前端 | Cutter | rizinorg/cutter |
rizin 官方 GUI |
| 解析/改写 ELF·PE·Mach-O | LIEF | lief-project/LIEF |
改二进制、加段、重签名 |
| 指令级仿真 | Unicorn | unicorn-engine/unicorn |
算法抽取神器 |
| 反汇编引擎(自研工具用) | Capstone | capstone-engine/capstone |
很多工具的地基 |
| 符号执行 / 约束求解 | angr | angr/angr |
自动找输入、解校验 |
| 动态 Hook(全平台) | Frida | frida/frida |
移动端逆向第一工具 |
| 免越狱/免 Root 利用 | objection | sensepost/objection |
Frida 的上层封装 |
| Android Hook 框架 | LSPosed | LSPosed/LSPosed |
需 Root/Magisk |
| Android Root | Magisk | topjohnwu/Magisk |
现代 Root 标准方案 |
| APK 反编译到 Java | jadx | skylot/jadx |
可读性最好 |
| APK 解包 / 重打包 | Apktool | iBotPeaches/Apktool |
改资源、改 smali |
| 内存脱壳 | frida-dexdump | hluwa/frida-dexdump |
对抗加固 |
| 移动 App 自动化审计 | MobSF | MobSF/Mobile-Security-Framework-MobSF |
静态+动态一体 |
| iOS 砸壳导 IPA | frida-ios-dump | AloneMonkey/frida-ios-dump |
iOS 静态前置步骤 |
| 导 ObjC 头文件 | class-dump | nygard/class-dump |
恢复类与方法签名 |
| HTTPS 抓包 / 改包 | mitmproxy | mitmproxy/mitmproxy |
可编程,脚本化首选 |
| 流量协议分析 | Wireshark | wireshark/wireshark |
非 HTTP、底层协议 |
| Web 安全扫描 | OWASP ZAP | zaproxy/zaproxy |
开源 Burp 替代 |
| 拦截调试 HTTP(S) | HTTP Toolkit | httptoolkit/httptoolkit |
桌面端体验好 |
| 浏览器自动化 | Playwright | microsoft/playwright |
反检测能力优于 Selenium |
| 浏览器自动化(CDP 直连) | Puppeteer | puppeteer/puppeteer |
贴近 Chrome DevTools 协议 |
| Node 端 DOM 补环境 | jsdom | jsdom/jsdom |
JS 逆向"补环境"主力 |
| JS 格式化 / 美化 | js-beautify | beautifier/js-beautify |
第一步永远是格式化 |
| AST 级还原改写 | Babel | babel/babel |
用 @babel/parser+traverse 写还原脚本 |
| webpack 打包还原 | webcrack | j4k0xb/webcrack |
一键拆 bundle |
| 了解混淆手法 | javascript-obfuscator | javascript-obfuscator/javascript-obfuscator |
知道怎么混淆才会反 |
| Electron 资源解包 | @electron/asar | electron/asar |
解 app.asar |
| 自动化沙箱 | CAPEv2 | kevoreilly/CAPEv2 |
Cuckoo 的活跃分支 |
| 让 LLM 直连反编译器 | GhidraMCP | LaurieWired/GhidraMCP |
与 REA 互补 |
| 同上(IDA) | ida-pro-mcp | mrexodia/ida-pro-mcp |
需 IDA Pro |
选用原则:能静态解决就不上动态,能动态观察就不硬猜算法。 动静结合是为了互相验证,不是二选一。
2. 通用工作流骨架:五阶段#
所有场景都套这个骨架,只是每阶段的工具不同。
阶段一 · 侦察(Recon)#
确定"这是什么、跑在哪、有多少东西要看"。产出范围清单,而不是结论。
- 文件类型 / 架构 / 依赖 / 是否加固 / 是否加密
- 入口点、权限、网络域名、第三方 SDK
- 工具:
fileotoolobjdumpaaptLIEF,以及 REA 的inspect_binary_layout、inspect_macho、binary_overview
阶段二 · 静态(Static)#
不运行目标,抽取结构。产出符号、调用关系、字符串、常量。
- 反编译、字符串搜索、交叉引用、导入表
- 工具:Ghidra、jadx、radare2、class-dump
- 对应 REA:
analyze_function、search_strings、xrefs、analyze_javascript_application
阶段三 · 动态(Dynamic)#
在真实运行时观察与干预。产出真实参数与返回值的观测记录。
- Hook 关键函数、打印入参出参、改返回值
- 工具:Frida、objection、LLDB、CDP
- 对应 REA:
capture_process_scenario、observe_native_calls、observe_javascript_runtime
阶段四 · 协议(Protocol)#
把"App 在做什么"翻译成"接口与数据格式"。产出可独立调用的请求模板。
- 抓包、定位签名/加密函数、还原算法
- 工具:mitmproxy、Wireshark、Unicorn(抽算法)
- 对应 REA:
capture_browser_scenario、inspect_web_network_capture
阶段五 · 复现(Verify)#
这是判断"是否真懂"的唯一标准。 产出脱离原环境的可运行脚本。
- 用 Python/Node 独立构造请求,结果与目标一致
- 工具:pwntools、requests、Playwright
- 对应 REA:
verify_reconstruction、compare_process_captures、record_unknown
不要把"能看懂"当成"已完成"。 看懂只是阶段二结束。
3. 场景 A · 原生二进制与桌面 App#
适用:macOS .app、Windows .exe/.dll、Linux ELF、命令行工具。
| 阶段 | 动作 | 工具 | 产出 |
|---|---|---|---|
| 侦察 | 架构/依赖/加固识别 | file otool -L LIEF |
架构、依赖清单、加载路径 |
| 静态 | 导入 Ghidra,跑自动分析 | Ghidra、radare2 | 函数列表、伪代码、xref |
| 静态 | 定位关键字符串与常量 | Ghidra、REA search_strings |
特征字符串 → 函数 |
| 动态 | Hook 关键函数看真实数据 | Frida、LLDB、REA observe_native_calls |
入参/返回值样本 |
| 协议 | 抓网络与 IPC | mitmproxy、Wireshark | 请求模板 |
| 复现 | 抽取纯算法独立运行 | Unicorn、pwntools | 可独立运行的算法实现 |
关键命令
# 侦察:macOS 架构与依赖
file ./Target.app/Contents/MacOS/Target
otool -L ./Target.app/Contents/MacOS/Target
otool -hv ./Target.app/Contents/MacOS/Target # 看 PIE / 加固标志
# 用 LIEF 列依赖与导出(Python)
python3 -c "
import lief
b = lief.MachO.parse('Target').at(0)
print([str(l.name) for l in b.libraries])
print([s.name for s in b.exported_symbols][:20])
"
// Frida:Hook 一个导出函数,打印入参与返回值
const addr = Module.findExportByName(null, 'verify_license');
Interceptor.attach(addr, {
onEnter(args) { console.log('arg0 =', args[0].readUtf8String()); },
onLeave(ret) { console.log('ret =', ret.toInt32()); }
});
# 复现:用 Unicorn 跑一段抽取出来的算法
from unicorn import Uc, UC_ARCH_ARM64, UC_MODE_ARM
from unicorn.arm64_const import UC_ARM64_REG_X0
uc = Uc(UC_ARCH_ARM64, UC_MODE_ARM)
uc.mem_map(0x1000, 0x1000)
uc.mem_write(0x1000, code_bytes)
uc.emu_start(0x1000, 0x1000 + len(code_bytes))
print(hex(uc.reg_read(UC_ARM64_REG_X0)))
常见坑
- macOS 应用有硬化运行时(Hardened Runtime)时,Frida 附加会失败,需要临时重签名或关闭 SIP 相关保护。
- Swift 符号是 mangled 的,先
demangle_swift(REA 内置)再读。 - 不要把
otool -L的路径当成实际加载路径:@rpath需要展开,用 REA 的trace_dylib_resolution。
4. 场景 B · Android App#
适用:APK、AAB、带加固的商业 App。
| 阶段 | 动作 | 工具 | 产出 |
|---|---|---|---|
| 侦察 | 解包看 Manifest/权限/SDK | Apktool、aapt2 |
组件、权限、第三方 SDK 清单 |
| 静态 | DEX → Java 反编译 | jadx | 可读源码树 |
| 静态 | 代码级分析/引用追踪 | androguard、REA | 调用链、字符串 |
| 脱壳 | 内存 dump 真实 DEX | frida-dexdump、BlackDex | 脱壳后的 DEX |
| 动态 | Hook 方法看真实参数 | Frida、LSPosed | 参数样本 |
| 协议 | 抓包 + 绕过证书固定 | mitmproxy + Frida | 请求模板 |
| 复现 | 脚本化调用 | requests、Playwright | 独立可运行客户端 |
关键命令
# 侦察:解包 + 看 Manifest
apktool d target.apk -o target_src
grep -E "package=|android:name=" target_src/AndroidManifest.xml | head -20
# 快速看权限与 SDK
aapt2 dump badging target.apk | head -20
# 静态:jadx 反编译(带资源)
jadx -d out_jadx --show-bad-code target.apk
// Frida:绕过常见证书固定(OkHttp / TrustManager)
Java.perform(function () {
const X509TrustManager = Java.use('javax.net.ssl.X509TrustManager');
const TrustManagerImpl = Java.use('com.android.org.conscrypt.TrustManagerImpl');
TrustManagerImpl.verifyChain.implementation = function (untrusted, trusted, host, clientAuth, ocsp) {
console.log('[+] bypass pinned chain for', host);
return untrusted;
};
});
# 脱壳:附加到进程,dump 内存中的 DEX
frida-dexdump -U -f com.example.app
常见坑
- 有加固(梆梆/爱加密/腾讯乐固)时,jadx 看到的是壳代码,必须先脱壳。
- 网络请求走 Cronet/QUIC 时 mitmproxy 抓不到,需要用 Frida hook 上层 API 或改用 Wireshark。
- Android 7+ 用户证书默认不被信任,要把 mitmproxy CA 装到系统证书区(需 Root)或改
network_security_config。 jadx反编译失败的方法,回退看 smali(--show-bad-code或 apktool 的 smali 输出)。
5. 场景 C · iOS App#
适用:.ipa、越狱设备或已砸壳的 Mach-O。
| 阶段 | 动作 | 工具 | 产出 |
|---|---|---|---|
| 侦察 | 解压 IPA,定位主二进制 | unzip、REA project_apple_application_graph |
可执行文件路径、嵌套 bundle |
| 砸壳 | 从内存导出解密后的 Mach-O | frida-ios-dump | 可静态分析的主二进制 |
| 静态 | 导 ObjC 头文件 | class-dump、Ghidra | 类/方法签名 |
| 静态 | Swift 符号还原 | REA analyze_swift_types |
类型与归属 |
| 动态 | Hook ObjC 方法 / C 函数 | Frida | 参数样本 |
| 协议 | 抓包 | mitmproxy + SSL Kill Switch | 请求模板 |
关键命令
# 侦察:解压 IPA
unzip -o target.ipa -d ipa_out
ls ipa_out/Payload/*.app/
# 砸壳:导出解密后的 IPA
python3 frida-ios-dump/dump.py -u -o decrypted.ipa "TargetApp"
# 静态:导 ObjC 头文件
class-dump -H ipa_out/Payload/Target.app/Target -o headers/
grep -rn "isPremium\|verifyReceipt" headers/ | head
// Frida:Hook ObjC 类方法
if (ObjC.available) {
const cls = ObjC.classes.NSString;
Interceptor.attach(cls['- containsString:'].implementation, {
onEnter(args) {
const self = new ObjC.Object(args[0]);
const other = new ObjC.Object(args[2]);
console.log('[NSString containsString:]', self.toString(), '|', other.toString());
}
});
}
常见坑
- App Store 下载的 IPA 是加密的,不砸壳直接反编译只能看到加密段。砸壳是必须的前置步骤。
- 越狱检测会主动退出或降级功能,Hook
getenv("DYLD_INSERT_LIBRARIES")、sysctl、文件路径检测。 - Swift 的泛型与协议 witness table 静态很难读全,优先靠动态观察真实调用。
6. 场景 D · 网站 / Web / JavaScript#
适用:网页接口逆向、签名算法还原、爬虫与自动化。
| 阶段 | 动作 | 工具 | 产出 |
|---|---|---|---|
| 侦察 | 资源清单、sourcemap、接口枚举 | DevTools、Playwright | 路由表、脚本清单 |
| 抓包 | 记录请求与响应 | mitmproxy、HTTP Toolkit | 原始请求/响应 |
| 定位 | 从请求参数反查生成函数 | DevTools 断点、debugger |
关键函数位置 |
| 反混淆 | 格式化、AST 还原、拆 bundle | js-beautify、Babel、webcrack | 可读代码 |
| 补环境 | 让加密函数脱离浏览器运行 | jsdom、Node vm |
可独立调用的算法 |
| 复现 | 构造完整请求 | requests、Playwright | 可运行客户端 |
关键命令
# 抓包:mitmproxy 录制为可读流
mitmproxy -p 8080 --save-stream-file flows.mitm
# 回放/导出为 HAR 供离线分析
mitmdump -nr flows.mitm --set hardump=out.har
# 反混淆:先拆 webpack bundle,再格式化
npx webcrack bundle.js -o unpacked/
npx js-beautify unpacked/*.js -o beautified/
// 用 Babel 做 AST 级还原:把 a["b"] 形式的成员访问还原为 a.b
const parser = require('@babel/parser');
const traverse = require('@babel/traverse').default;
const generate = require('@babel/generator').default;
const ast = parser.parse(source);
traverse(ast, {
MemberExpression(path) {
const { computed, property } = path.node;
if (computed && property.type === 'StringLiteral' && /^[A-Za-z_$][\w$]*$/.test(property.value)) {
path.node.computed = false;
path.node.property = { type: 'Identifier', name: property.value };
}
}
});
console.log(generate(ast).code);
// 补环境:在 Node 里给出加密函数所需的最小浏览器环境
const { JSDOM } = require('jsdom');
const dom = new JSDOM('<!doctype html><html><body></body></html>', {
url: 'https://target.example.com/',
pretendToBeVisual: true
});
global.window = dom.window;
global.document = dom.window.document;
global.navigator = dom.window.navigator; // 注意:navigator 可能需手动伪造字段
global.location = dom.window.location;
// 此后加载目标脚本,调用其加密函数
require('./beautified/sign.js');
console.log(global.window.sign('payload'));
常见坑
navigator/window的检测字段(webdriver、plugins、languages、chrome)经常是反爬点,补环境时要逐一对齐真实浏览器取值。- 直接
eval加密函数容易踩死循环与反调试,先断点定位再抽取。 - 请求签名往往依赖时间戳 + 随机数 + 固定盐,先固定随机数以便差分测试。
- sourcemap 若存在,优先还原源码,省掉 90% 反混淆工作。
7. 场景 E · Electron 桌面应用#
Electron 应用 = 一个 Node 主进程 + 一个 Chromium 渲染进程,两条路都能逆。
| 阶段 | 动作 | 工具 | 产出 |
|---|---|---|---|
| 侦察 | 解包 app.asar |
@electron/asar |
全部前端源码 |
| 静态 | 读主进程与 preload | REA analyze_javascript_application |
IPC 通道与暴露 API |
| 动态 | 开 DevTools / 连 CDP | REA inspect_electron_page、capture_electron_scenario |
运行时脚本与 DOM |
| 协议 | 抓渲染进程网络 | mitmproxy | 接口 |
关键命令
# 解包 asar
npx @electron/asar extract app.asar ./app_unpacked
ls app_unpacked/
# 找 IPC 通道与主进程逻辑
grep -rn "ipcMain.handle\|ipcRenderer.invoke\|contextBridge" app_unpacked/ | head -20
常见坑
- 生产构建常把主进程代码压缩/打包,但
preload.js通常还在,从 preload 的contextBridge反推能力边界最快。 - 有些应用禁用 DevTools,可用 CDP 端口(
--remote-debugging-port)或直接改 asar 后重打包。 - 重打包后可能触发签名/完整性校验,注意
app.asar的哈希校验点。
8. 证据沉淀:如何与 REA 对齐#
工具跑完会散落一堆日志、截图、脚本。价值不在于跑过,而在于可复核。 建议每个阶段固定产出:
| 阶段 | 必须留下的证据 | 对应 REA 能力 |
|---|---|---|
| 侦察 | 文件哈希、架构、依赖清单 | inspect_binary_layout、binary_overview |
| 静态 | 函数/类清单、关键字符串、xref | analyze_function、search_strings、xrefs |
| 动态 | 参数样本、调用栈、时间点 | capture_process_scenario、observe_native_calls |
| 协议 | 原始请求/响应、签名输入输出对 | inspect_web_network_capture |
| 复现 | 可运行脚本 + 与目标一致的输出 | verify_reconstruction、compare_process_captures |
三条实践建议
- 先建 unknown 清单再开工。 把"我还不知道什么"显式记下来(REA
record_unknown),比记"我知道了什么"更能防止自欺。 - 动态观测要绑定环境。 同一份样本在不同版本 OS/浏览器上行为不同,记录版本号,否则复现会玄学失败。
- 用差分验证代替确认偏误。 改变一个输入看输出是否按预期变化(REA
compare_process_captures),比"跑通了"更可信。
9. 合规边界与常见坑#
先划边界,再动手。
- 只分析你拥有或被授权的目标。绕过付费墙、破解授权、批量爬取他人数据在多数司法辖区违法。
- 抓包前确认目标的服务条款与当地法律(如 CFAA、计算机犯罪法)。
- 涉及个人信息时,抓到的流量本身就含个人数据,注意存储与脱敏。
- 对外发布分析报告前,确认没有泄露第三方密钥、令牌、用户数据。
工程上的常见坑
| 症状 | 多半是 | 对策 |
|---|---|---|
| 附加进程就崩 | 反调试 / 完整性校验 | 先静态定位检测点,Hook 掉再附加 |
| 抓不到 HTTPS | 证书固定 / 走 QUIC | Hook 上层 API 或关闭 QUIC |
| 反编译全是壳代码 | 加固 | 先脱壳 |
| 补环境后结果不对 | 检测字段未对齐 | 逐字段对比真实浏览器取值 |
| 复现偶尔失败 | 依赖时间/随机数 | 固定随机数做差分 |
| 改了二进制就不启动 | 代码签名 | 重新签名,或关闭硬化运行时 |
10. 一页速查:从零到复现#
1. 侦察 file / otool / aapt2 / unzip → 我面对的是什么?
2. 静态 Ghidra / jadx / class-dump → 结构长什么样?
3. 动态 Frida / LLDB / CDP → 真实跑起来是什么样?
4. 协议 mitmproxy / Wireshark → 它和服务器说什么?
5. 复现 Python / Node / Playwright → 我能否独立重放?
每一步都问自己:这一步产出的证据,能不能让别人复核? 不能,就还没做完。
本指南由工作区内的 re-workflow 项目构建并部署到 Cloudflare Pages。工具清单为开源仓库,使用前请自行确认许可证与合规边界。