应用安全 --- 逆向工程化
免责声明
本文档所有内容仅供安全研究、学术交流与技术学习使用,严禁用于任何未经授权的逆向破解、网络攻击、隐私窃取、恶意软件开发及其他违反《中华人民共和国网络安全法》《数据安全法》等法律法规的行为,使用者应确保已获得目标软件权利人的合法授权并自行承担因使用本文档内容所产生的一切法律责任与后果,作者不对任何直接或间接损害承担任何责任,继续阅读即视为您已知悉并同意上述全部条款。
我将逆向的大流程分为三个阶段
第一阶段:工具阶段,掌握主流工具和技术,可以进行动静态分析
第二阶段:脱壳阶段,掌握主流加固厂家的加密技术
第三阶段:还原阶段,完美还原源代码,甚至可以回编译回去
一张图

从这张图我们可以看出,我们的重点是静态分析,动态调试只是辅助,当我们对每个函数都有把握时,动态调试才不会跑飞。
我们为什么要工程化逆向流程,因为对于复杂需求适合,对于简单的算法分析可能直接分析更加高效。
预分析
为什么要预分析,有点像渗透测试阶段的信息收集,在充分收集足够多的信息后才能更好的开展行动,而不是将一个原本不可执行的文件作为可执行文件解析
〇、开始之前:明确你的目标
拿到文件后,先花 30 秒回答三个问题:
| 问题 | 示例回答 |
|---|---|
| 我要分析到什么程度? | A. 判断功能概览 / B. 提取核心算法 / C. 完整还原协议 / D. 提取密钥/配置 |
| 我的目标文件是什么? | 一个 APK / 一个 .exe / 一个 .so / 一个 .dll |
| 我有哪些约束? | 只能静态 / 可以动态调试 / 有无 root 设备 / 有无源码对照 |
写在文档开头。后续每一步都围绕这个目标做取舍,避免在无关细节上耗时间。同时完成环境工具的搭建
一、文件识别——搞清楚"它是什么"
目的
确定文件格式 → 选择正确的工具链。用错工具,后面全部白费。
操作流程
步骤 1:命令行快速识别
─────────────────────────────
# Linux / macOS
file target_file
# 输出示例:
# ELF 64-bit LSB shared object, ARM aarch64, version 1 (SYSV), dynamically linked
# PE32+ executable (GUI) x86-64, for MS Windows
# Mach-O 64-bit x86_64 executable
# Java archive data (JAR/APK)
步骤 2:GUI 工具交叉验证
─────────────────────────────
→ 将文件拖入 DIE(Detect It Easy)
→ 查看左上角识别结果:PE / ELF / Mach-O / DEX / ZIP(APK)
→ 记录:位数(32/64)、架构(x86/ARM/MIPS)、字节序(LSB/MSB)
步骤 3(Windows PE 专用):
─────────────────────────────
→ 用 ExeinfoPE 打开,查看更详细的 PE 子类型(EXE/DLL/SYS/OCX)
判断结果记录表
| 字段 | 填写 |
|---|---|
| 文件格式 | PE / ELF / Mach-O / DEX / APK / 其他:___ |
| 位数 | 32 / 64 |
| 架构 | x86 / x86_64 / ARM / ARM64(AArch64) / MIPS |
| 字节序 | 小端(LSB) / 大端(MSB) |
| 子类型 | 可执行文件 / 动态库 / 静态库 / 驱动 / 其他:___ |
| 目标平台 | Windows / Linux / macOS / Android / iOS |
如果是 APK
# 解压 APK 查看内部结构
unzip -l target.apk
# 关注以下文件:
# classes.dex → Java/Kotlin 字节码
# lib/arm64-v8a/*.so → Native 库
# assets/ → 资源/配置/JS 代码(框架相关)
# AndroidManifest.xml → 权限和组件声明
二、壳检测与熵值分析——判断"它有没有被包裹"
目的
加壳 = 静态分析几乎失效。必须先判断,有壳就先脱壳再做后续步骤。
操作流程
═══════════════════════════════════════
指标 1:熵值检测(最可靠的客观指标)
═══════════════════════════════════════
方法 A:DIE
→ 打开文件 → 点击「Entropy」按钮
→ 查看每个节区的熵值柱状图
→ 判断标准:
熵值 < 6.0 → 正常代码/数据
熵值 6.0~7.0 → 可能有压缩
熵值 > 7.0 → 高度疑似加壳/加密
熵值 ≈ 8.0 → 几乎确定加密
方法 B:binwalk(Linux/macOS)
binwalk -E target_file
→ 输出熵值曲线图,观察是否有大面积高熵区间
方法 C:rabin2(radare2 套件)
rabin2 -S target_file
→ 列出所有节区及大小,配合熵值工具交叉验证
═══════════════════════════════════════
指标 2:导入表大小(快速直觉判断)
═══════════════════════════════════════
# 查看导入函数数量
rabin2 -i target_file | wc -l
# 或在 IDA/Ghidra 中查看 Imports 窗口
→ 判断标准:
一个功能复杂的程序(文件 > 500KB),导入函数 < 10 个
且集中在 LoadLibraryA / GetProcAddress / VirtualAlloc / VirtualProtect
→ 极大概率有壳——壳会在运行时动态解析所有需要的 API,所以静态看到的导入表几乎是空的。
Android SO 文件只有少量导入且包含:
mmap / mprotect / __loader_dlopen
→ 疑似自定义加载器或壳
═══════════════════════════════════════
指标 3:节区名称异常
═══════════════════════════════════════
# 查看节区名
rabin2 -S target_file
# 或 readelf -S target_file(ELF)
# 或在 DIE/CFF Explorer 中查看(PE)
→ 异常节区名速查表:
UPX0, UPX1, UPX2 → UPX 壳
.petite → Petite 壳
.themida, .winlice → Themida/WinLicense
.vmp0, .vmp1 → VMProtect
.enigma1, .enigma2 → Enigma Protector
.ndata → NSIS 安装包
.shielden → ShieldenProtector
名称为乱码或非 ASCII → 自定义壳 / 混淆
═══════════════════════════════════════
Android 专用:APK/SO 壳检测
═══════════════════════════════════════
方法 A:MT 管理器(手机端)
→ 打开 APK → 点击「查看」→ 自动识别加固厂商
→ 常见结果:腾讯乐固 / 360加固 / 梆梆 / 爱加密 / 娜迦
方法 B:NP 管理器(手机端)
→ 功能类似 MT,打开即可识别
方法 C:命令行特征判断
ls -la lib/arm64-v8a/
# 如果看到以下文件,基本确认有壳:
# libjiagu.so / libjiagu_a64.so → 360加固
# libexec.so / libexecmain.so → 腾讯乐固
# libDexHelper.so → 梆梆加固
# libdexjni.so → 爱加密
# libsecexe.so → 娜迦
# 检查 Application 入口类
aapt dump xmltree target.apk AndroidManifest.xml | grep "application"
# 如果 Application 类名包含 Stub / Wrapper / Protect 等关键词 → 有壳
壳检测结果记录
| 字段 | 填写 |
|---|---|
| 是否有壳 | 是 / 否 / 疑似 |
| 壳类型 | UPX / VMProtect / Themida / 360 / 乐固 / 梆梆 / 未知自定义壳 |
| 高熵节区 | 节区名:___ 熵值:___ |
| 导入表异常 | 是(仅 ___ 个导入)/ 否(正常) |
下一步决策
如果 → 无壳:
继续第三步
如果 → UPX 等简单壳:
upx -d target_file # 直接脱壳
→ 脱壳后从第一步重新开始验证
如果 → VMProtect / Themida 等强壳:
静态脱壳极难,标记后续需要动态 dump
→ 先完成剩余预分析步骤,脱壳留到动态分析阶段
如果 → Android 商业加固:
使用 Frida + dexdump / FART / BlackDex 脱壳
→ 脱壳后重新分析 DEX 和 SO
三、安全特性检查——了解"它有哪些防护"
目的
安全特性决定后续动态调试和 hook 的策略。提前知道,避免撞墙。
操作流程
═══════════════════════════════════════
ELF 文件(Linux / Android SO)
═══════════════════════════════════════
# 方法 1:checksec(推荐)
checksec --file=libxxx.so
# 方法 2:rabin2
rabin2 -I libxxx.so
# 方法 3:readelf 手动检查
readelf -l libxxx.so | grep GNU_STACK # NX
readelf -l libxxx.so | grep GNU_RELRO # RELRO
readelf -d libxxx.so | grep FLAGS # PIE/BIND_NOW
readelf -s libxxx.so | grep __stack_chk # Canary
═══════════════════════════════════════
PE 文件(Windows EXE/DLL)
═══════════════════════════════════════
# 方法 1:winchecksec(命令行)
winchecksec.exe target.exe
# 方法 2:PE-bear / CFF Explorer(GUI)
→ 查看 Optional Header → DLL Characteristics 字段
# 方法 3:PowerShell
Get-ProcessMitigation -Name target.exe
═══════════════════════════════════════
Mach-O 文件(macOS / iOS)
═══════════════════════════════════════
# 检查 PIE
otool -hv target_binary | grep PIE
# 检查代码签名
codesign -dvvv target_binary
# 检查加密标志(iOS App Store 加密)
otool -l target_binary | grep -A4 LC_ENCRYPTION_INFO
# cryptid = 1 → 有 FairPlay 加密,需要先砸壳
各保护机制的实际影响速查表
| 保护机制 | 状态 | 对你的影响 | 应对策略 |
|---|---|---|---|
| PIE/ASLR | 开启 | 每次加载基址不同,断点地址需要动态计算 | 调试时先获取模块基址:base + offset |
| PIE/ASLR | 关闭 | 地址固定,断点设置方便 | 直接使用 IDA 中看到的地址 |
| NX/DEP | 开启 | 栈/数据段不可执行 | 不影响常规逆向,影响漏洞利用 |
| Full RELRO | 开启 | GOT 表只读,不可覆写 | 无法用 GOT hijack 做 hook,改用 inline hook |
| Partial RELRO | 开启 | GOT 表可写 | 可以用 GOT 表覆写做 hook |
| Stack Canary | 开启 | 函数有栈溢出检测 | 看到 __stack_chk_fail 引用是正常的,别误判为恶意行为 |
| FORTIFY | 开启 | 危险函数被替换为安全版本 | 看到 __memcpy_chk、__strcpy_chk 是 FORTIFY 产生的,等价于原函数 |
安全特性记录表
| 特性 | 状态(开启/关闭) |
|---|---|
| PIE/ASLR | |
| NX/DEP | |
| RELRO | None / Partial / Full |
| Stack Canary | |
| FORTIFY | |
| 代码签名 | |
| 加密(FairPlay等) |
四、元数据提取——系统性收集信息
前置条件
如果第二步检测到有壳 → 先脱壳 → 再执行本步骤。 壳会伪造/隐藏大部分元数据。
4.1 架构与编译器识别
═══════════════════════════════════════
确认架构(决定反汇编工具配置)
═══════════════════════════════════════
# ELF
readelf -h target | grep -E "Machine|Class"
# Machine: AArch64 → ARM64
# Machine: ARM → ARM32
# Machine: X86-64 → x86_64
# Class: ELF64 → 64位
# PE
# 在 DIE 或 CFF Explorer 中查看 Machine 字段
# 0x8664 → x86_64
# 0x14C → x86_32
# 0xAA64 → ARM64
# Mach-O
lipo -info target_binary
# 或 file target_binary
═══════════════════════════════════════
识别编译器
═══════════════════════════════════════
# 方法 1:DIE 自动识别(最简单)
→ 打开文件 → 查看 Compiler/Linker 识别结果
# 方法 2:字符串特征
strings target_file | grep -iE "gcc|clang|msvc|visual|borland|mingw|rustc|go\."
# 方法 3:IDA / Ghidra 中观察
→ GCC 特征:函数以 _Z 开头的 C++ name mangling,
.init_array/.fini_array 节区
→ MSVC 特征:SEH 异常处理结构,
_security_cookie 引用
→ Clang 特征:类似 GCC 但优化模式略有不同,
可能出现 __clang_call_terminate
→ Go 特征:runtime.goexit, runtime.mstart 等符号
→ Rust 特征:core::panicking, alloc:: 等符号前缀
编译器识别结果:___________________
4.2 导入导出表分析
═══════════════════════════════════════
导入表(程序依赖了哪些外部函数)
═══════════════════════════════════════
# ELF
rabin2 -i target.so # 简洁列表
readelf -d target.so | grep NEEDED # 依赖的库
objdump -T target.so # 动态符号表
# PE
rabin2 -i target.exe
# 或 dumpbin /IMPORTS target.exe(MSVC 工具链)
# 或 CFF Explorer → Import Directory
# 将导入表保存到文件便于分析
rabin2 -i target_file > imports.txt
═══════════════════════════════════════
导出表(程序对外提供了哪些函数)
═══════════════════════════════════════
# ELF
rabin2 -E target.so
nm -D target.so | grep " T " # 只看导出的函数
# PE
rabin2 -E target.dll
# 或 dumpbin /EXPORTS target.dll
# 将导出表保存到文件
rabin2 -E target_file > exports.txt
═══════════════════════════════════════
导入表快速分类(找到关键功能线索)
═══════════════════════════════════════
在 imports.txt 中搜索以下关键词:
网络通信:
grep -iE "socket|connect|send|recv|http|url|curl|ssl|tls" imports.txt
文件操作:
grep -iE "fopen|fread|fwrite|CreateFile|ReadFile|WriteFile" imports.txt
加密解密:
grep -iE "crypt|aes|des|rsa|md5|sha|hash|hmac|EVP_|cipher" imports.txt
进程/线程:
grep -iE "CreateProcess|fork|exec|pthread|CreateThread" imports.txt
内存操作:
grep -iE "malloc|calloc|mmap|VirtualAlloc|HeapAlloc" imports.txt
注册表(Windows):
grep -iE "RegOpen|RegQuery|RegSet|RegCreate" imports.txt
反调试:
grep -iE "IsDebugger|NtQuery|ptrace|sysctl" imports.txt
Android JNI:
grep -iE "JNI_OnLoad|RegisterNatives|FindClass|GetMethodID" imports.txt
4.3 节区/段信息
═══════════════════════════════════════
查看节区信息
═══════════════════════════════════════
# ELF
readelf -S target_file
rabin2 -S target_file
# PE(使用 CFF Explorer 或 PE-bear 更直观)
rabin2 -S target.exe
# 关注项:
# 1. 各节区大小 → 特别大的节区可能包含隐藏数据
# 2. 各节区权限 → RWX(可读可写可执行)的节区很可疑
# 3. 虚拟大小 vs 文件大小差异 → 差异大说明可能有运行时解包
# 4. 非标准节区名 → 参考第二步的壳检测
# 快速查找可疑的 RWX 节区
readelf -S target_file | grep "WXA"
4.4 字符串提取与智能筛选
═══════════════════════════════════════
提取字符串
═══════════════════════════════════════
# 基础提取(ASCII + Unicode)
strings -a target_file > strings_ascii.txt
strings -el target_file > strings_unicode.txt # UTF-16LE(Windows 常用)
# 更智能的提取(使用 FLOSS,可以提取混淆后的字符串)
floss target_file > strings_floss.txt
# rabin2 提取(包含地址信息,方便后续定位)
rabin2 -zz target_file > strings_with_addr.txt
# Android DEX 字符串
# 使用 jadx 打开 APK 后在搜索窗口全局搜索
═══════════════════════════════════════
AI 辅助筛选关键字符串
═══════════════════════════════════════
# 将提取的字符串发送给 LLM(Claude/Gemini/GPT)
# 使用以下提示词模板:
"""
以下是从一个 [PE/ELF/APK] 文件中提取的字符串列表。
请帮我分类并标注以下类别的关键字符串:
1. URL / IP 地址 / 域名(网络通信线索)
2. 文件路径(访问了哪些文件)
3. 注册表路径(Windows 相关)
4. 加密相关(密钥、算法名称、IV、Salt)
5. 错误信息 / 日志信息(帮助理解逻辑)
6. SQL 语句(数据库操作)
7. API 密钥 / Token / 证书
8. 调试信息 / 函数名 / 类名(辅助代码理解)
9. 配置项 / 命令行参数
10. 可疑字符串(Base64 编码、硬编码的长字符串等)
请忽略明显无意义的短字符串和系统库路径。
[粘贴字符串列表]
"""
═══════════════════════════════════════
手动快速筛选(如果字符串太多不想全发给 AI)
═══════════════════════════════════════
# 搜索 URL 和 IP
grep -iE "https?://|[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+" strings_ascii.txt
# 搜索文件路径
grep -iE "/data/|/sdcard/|C:\\\\|\\\\Users\\\\" strings_ascii.txt
# 搜索密钥/密码相关
grep -iE "key|password|passwd|secret|token|api_key|AES|DES|RSA" strings_ascii.txt
# 搜索 Base64 编码(长度>20 的纯字母数字+/=)
grep -E "^[A-Za-z0-9+/]{20,}={0,2}$" strings_ascii.txt
# 搜索错误信息
grep -iE "error|fail|exception|invalid|denied|unauthorized" strings_ascii.txt
# 搜索 JSON 键名
grep -iE "\"[a-z_]+\":" strings_ascii.txt
元数据提取汇总表
| 维度 | 结果 |
|---|---|
| 架构 | |
| 编译器 | |
| 依赖库 | |
| 关键导入函数 | |
| 关键导出函数 | |
| 可疑节区 | |
| 关键字符串(URL) | |
| 关键字符串(密钥) | |
| 关键字符串(路径) | |
| 关键字符串(其他) |
五、识别开发框架——精准定位核心代码
目的
避免分析框架的胶水代码,直接找到业务逻辑所在的文件。
操作流程
═══════════════════════════════════════
Android APK 框架识别
═══════════════════════════════════════
# 解压 APK 后,按以下顺序检查
# ---- Unity(游戏/3D应用) ----
检查文件:
ls lib/*/libil2cpp.so # IL2CPP 模式
ls lib/*/libmono.so # Mono 模式
ls assets/bin/Data/ # Unity 资源目录
ls assets/bin/Data/Managed/ # Mono 模式的 DLL
如果存在 libil2cpp.so:
→ 核心逻辑文件:lib/arm64-v8a/libil2cpp.so
→ 辅助文件:assets/bin/Data/Managed/Metadata/global-metadata.dat
→ 分析工具:Il2CppDumper → 导出函数名/类名 → 然后用 IDA 分析 SO
→ 操作命令:Il2CppDumper libil2cpp.so global-metadata.dat output/
如果存在 libmono.so:
→ 核心逻辑文件:assets/bin/Data/Managed/Assembly-CSharp.dll
→ 分析工具:dnSpy / ILSpy / dotPeek(直接反编译 C# 代码)
# ---- Flutter ----
检查文件:
ls lib/*/libflutter.so # Flutter 引擎
ls lib/*/libapp.so # 业务逻辑(Dart AOT 编译)
如果存在 libapp.so:
→ 核心逻辑文件:lib/arm64-v8a/libapp.so
→ 分析工具:reFlutter(修改引擎抓包)/ Doldrums / blutter
→ 注意:Flutter Dart AOT 逆向难度较高,优先考虑抓包分析网络协议
# ---- React Native ----
检查文件:
ls assets/index.android.bundle # JS Bundle(旧版)
ls assets/index.android.bundle # Hermes 字节码(新版)
file assets/index.android.bundle # 判断是 JS 明文还是 Hermes 字节码
如果是 JS 明文:
→ 核心逻辑文件:assets/index.android.bundle
→ 直接用文本编辑器 / beautifier 美化后阅读
如果是 Hermes 字节码(file 显示 Hermes 或不可读):
→ 工具:hermes-dec / hbctool
→ 操作:hbctool disasm index.android.bundle output/
→ 或 hermes-dec index.android.bundle > output.js
# ---- UniApp / 小程序框架 ----
检查文件:
ls assets/apps/ # UniApp 资源目录
ls assets/www/ # Cordova/Ionic 资源目录
# 寻找 .js / .json / .nvue 文件
如果存在 assets/apps/:
→ 核心逻辑文件:assets/apps/[appid]/www/ 下的 JS 文件
→ 通常是 app-service.js 或 app.js
→ 工具:JS 美化器 → 直接阅读 JS 代码
# ---- Xamarin ----
检查文件:
ls assemblies/*.dll # .NET DLL
ls lib/*/libmonodroid.so # Mono 运行时
ls lib/*/libxamarin*.so
如果存在 assemblies/ 目录:
→ 核心逻辑文件:assemblies/ 下的 DLL 文件(特别是与 App 同名的 DLL)
→ 工具:dnSpy / ILSpy 直接反编译 C# 代码
→ 注意:DLL 可能被 AOT 编译或使用 Bundle,需要先用 XamAsmUnZ 解包
# ---- Cocos2d-x ----
检查文件:
ls lib/*/libcocos2dcpp.so # C++ 编译的引擎+业务逻辑
ls assets/src/ # Lua 脚本(如果使用 Lua 绑定)
ls assets/script/ # JS 脚本(如果使用 JS 绑定)
如果存在 Lua/JS 脚本:
→ 核心逻辑文件:assets/src/ 或 assets/script/ 下的脚本
→ 可能经过 LuaJIT 编译或 xxtea 加密
→ 工具:unluac(Lua 反编译)/ xxtea-decrypt
如果只有 libcocos2dcpp.so:
→ 核心逻辑在 SO 中,按照 Native 逆向流程分析
我们还要识别公共库,这里我建议使用libchecker快速识别app中所有公共库,一般来讲我们不去分析公共的第三方库文件
═══════════════════════════════════════
Windows / 桌面端框架识别
═══════════════════════════════════════
# ---- Electron ----
检查文件:
ls resources/app.asar # 主要代码包
ls resources/app/ # 未打包的代码目录
如果存在 app.asar:
→ 工具:npx asar extract app.asar output/
→ 核心逻辑在解包后的 JS 文件中
# ---- .NET / C# ----
# 用 DIE 识别显示 .NET
→ 工具:dnSpy(推荐,可调试)/ ILSpy / dotPeek
→ 直接反编译为 C# 源码
# ---- Qt ----
# 依赖 Qt5Core.dll / Qt6Core.dll 等
→ 核心逻辑在主 EXE 中
→ QML 文件可能在 resources 中(可用 qrc_extractor 提取)
# ---- PyInstaller / cx_Freeze(Python 打包) ----
# DIE 识别或看到 python3x.dll
→ 工具:pyinstxtractor / uncompyle6
→ 操作:python pyinstxtractor.py target.exe
→ 然后 uncompyle6 提取的 .pyc 文件 > output.py
框架识别结果
| 字段 | 填写 |
|---|---|
| 开发框架 | 原生 / Unity / Flutter / RN / UniApp / Electron / .NET / 其他:___ |
| 核心逻辑文件 | |
| 推荐分析工具 | |
| 代码类型 | Native(C/C++) / C# / Java / JavaScript / Dart / Lua / Python |
工具链速查(按平台)
| 平台 | 反汇编/反编译 | 动态调试 | 辅助工具 |
|---|---|---|---|
| Windows PE | IDA Pro / Ghidra / x64dbg(内置) | x64dbg / WinDbg / IDA | CFF Explorer / PE-bear / API Monitor |
| Linux ELF | IDA Pro / Ghidra / radare2 | GDB + GEF/pwndbg / IDA Remote | checksec / strace / ltrace |
| Android Native | IDA Pro / Ghidra | IDA Remote / GDB / Frida | Frida / Objection / MT管理器 |
| Android Java | JADX / JEB / GDA | Frida / Xposed / JEB | Apktool / dex2jar |
| iOS | IDA Pro / Ghidra / Hopper | LLDB / Frida | class-dump / Clutch(砸壳) |
| Unity IL2CPP | Il2CppDumper + IDA | Frida / GameGuardian | Il2CppDumper / Il2CppInspector |
| Unity Mono | dnSpy / ILSpy | dnSpy(带调试) | — |
| .NET | dnSpy / ILSpy / dotPeek | dnSpy | de4dot(去混淆) |
| Flutter | blutter / reFlutter | Frida | reFlutter |
| Python打包 | pyinstxtractor + uncompyle6 | — | — |
最后将结果输出到一份文档中!!!
如果你的项目复杂就需要创建一个项目,我的项目如下。
我的项目/ ├── 00_生肉/ # 原始的目标文件的存放地 比如 libdexjni.so,是一切开始的地方 ├── 01_熟肉/ # 脱壳修复后文件 ├── 02_伪码/ # 反编译输出代码结果文件,包含ida反汇编反编译和ai代码重构后的文件,比如我的文件是sub_564B8_000564B8_子进程看门狗.txt ├── 03_pwn/ # 重点函数的详细分析报告 ├── 控制流/ # 函数调用链分析 ├── 数据流/ # 数据处理分析 ├── 06_脚本/ # frida或者py脚本,用于辅助分析
├── 06_抓包/ # 分析流量
└── README.md # 项目总览
阶段二:静态分析(核心)
静态分析的主要目的是重构为接近源码的表示格式
-
脱壳处理(如需)
-
有壳子先脱壳,自动脱不行就手动脱,配合dump内存映像文件,然后修复脱壳后的程序保证可以正常运行就是脱修完成了,这一步是最难的部分,需要深入学习,这里不讨论!!!
-
自动脱壳:UPX等简单壳
-
手动脱壳:找OEP → dump内存 → 修复IAT
-
加固方案:邦邦/ai加密/063等需专门处理
-
-
导出所有函数代码(https://www.cnblogs.com/GKLBB/p/19106488)
-
清理干扰项
-
删除垃圾函数(大小保持在 1KB 以下极小函数、名称具有C库特征(如 __write_chk、__strlen_chk)、函数名以 . 或 _ 开头、仅做简单的参数转发/跳转的函数、)
-
排除无关代码:
├── FLIRT签名 → 匹配标准库函数
├── IDA Lumina → 在线识别已知函数 - 这里应该使用脚本去除杂质,还未开发出来
-
-
-
函数作用分析 。如果有效文件很多超过了百个,那么通过人力分析已经不可能了,需要借助ai批量分析。
分析单个文件方法如下,
初筛(便宜AI,分析函数功能,通俗解释并分析是不是公共库函数,) → Gemini / DeepSeek/glm4.5/豆包code
↓
精析(重量级AI,如果是关键代码再用claude这种重量级的ai进行分析。因为大的代码更有可能是关键函数。) → Claude opus4.6/ evol/ https://lmarena.ai/,或者是在不行就某宝买一个稳定账号。
↓
输出:功能描述 + 重构代码 + 通俗解释
分析关键函数,
利用claude技术分析关键函数的功能将重构的源码追加到文件后比如
函数名称: sub_56BD8 起始地址: 0x00056BD8 结束地址: 0x00056C98 函数大小: 192 字节 ====================================================================== 反汇编代码 (包含地址信息): ====================================================================== 0x00056BD8: STR X19, [SP,#-0x10+var_10]!
====================================================================== 反编译代码 (Hex-Rays): ====================================================================== void __fastcall __noreturn sub_56BD8(int *a1) { int v1; // w19
====================================================================== 分析报告: ======================================================================
函数功能描述:xxx
参数作用描述:xxx
加密算法:xxx
外部引用描述:xxx
第三方库猜测:xxx
数据流和控制流简明描述:xxx
重构源代码:
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/prctl.h>
#include <sys/syscall.h>
#include <android/log.h>
void __attribute__((noreturn)) pipe_watchdog_thread(int *arg)
{
int pipe_read_fd = *arg;
free(arg);
通俗解释:xxx
详细比喻:xxx
分析完成后给每个关键函数文件名称重命名后缀,比如 sub_56BD8_00056BD8_监控看门狗管道连接本身.txt
一个一个分析效率太低,这里建议采用批量分析方法
合并发送给ai批量分析:
一般来讲一个so文件会反编译出大于2000个函数,去除无用函数后就剩下用户编写的函数,大约是800个左右,我们将这些函数按照从根函数(没有调用其他任何函数的叶子函数)开始合并为一个大文件一次性发送给claude可以极大的加速,为了加速分析,我们可以将多个小文件合并为一个大文件一次性发给大模型。
我的提示词是:
编写一个py脚本将这个目录exported_functions下的所有代码文件分批合并为每个文件,合并的方法是按照大小依次合并,注意 只提取反编译代码部分,合并后按照原始文件名称分隔,同时每次合并后的文件字数不能超出119,472个字母(这个字符数来自测试 https://arena.ai/网站得出的最大结果),如果超出就将这个函数文件放到下一个合并的文件中继续合并直到结束,如果单个文件已经超出了119,472个字母,那么将大文件分隔为多个小文件,但是要体现这是一个文件的分块,请编写代码
import os import glob # 定义常量 MAX_FILE_SIZE = 119986 INPUT_DIR = "exported_functions" OUTPUT_DIR = "merged_functions" # 创建输出目录 if not os.path.exists(OUTPUT_DIR): os.makedirs(OUTPUT_DIR) # 获取所有.txt文件 files = glob.glob(os.path.join(INPUT_DIR, "*.txt")) # 过滤掉非.txt文件 files = [f for f in files if f.endswith('.txt')] # 函数:提取文件中的反编译代码部分 def extract_decompiled_code(file_path): with open(file_path, 'r', encoding='utf-8', errors='ignore') as f: lines = f.readlines() # 找到反编译代码开始的位置 start_line = 0 for i, line in enumerate(lines): if "反编译代码 (Hex-Rays):" in line: start_line = i + 3 # 跳过标题行和分隔线 break # 提取反编译代码 if start_line < len(lines): return ''.join(lines[start_line:]) return '' # 计算每个文件的大小(只计算反编译代码部分的大小) file_info = [] for file_path in files: code = extract_decompiled_code(file_path) size = len(code) if size > 0: # 只处理有反编译代码的文件 # 计算文件标题和分隔符的大小 filename = os.path.basename(file_path) header_size = len(f"\n{'='*80}\n文件: {filename}\n{'='*80}\n\n") total_size = size + header_size file_info.append((file_path, size, total_size, code)) # 按照文件总大小排序(包括标题和分隔符) file_info.sort(key=lambda x: x[2]) # 函数:保存当前合并文件 def save_merged_file(files_to_save, output_index): output_file = os.path.join(OUTPUT_DIR, f"merged_{output_index}.txt") with open(output_file, 'w', encoding='utf-8') as f: for fp, c in files_to_save: filename = os.path.basename(fp) f.write(f"\n{'='*80}\n") f.write(f"文件: {filename}\n") f.write(f"{'='*80}\n\n") f.write(c) return output_index + 1 # 开始合并文件 current_size = 0 current_files = [] output_file_index = 1 for file_path, code_size, total_size, code in file_info: filename = os.path.basename(file_path) header_size = len(f"\n{'='*80}\n文件: {filename}\n{'='*80}\n\n") # 检查文件是否太大,需要单独处理 if total_size > MAX_FILE_SIZE: # 保存当前合并文件 if current_files: output_file_index = save_merged_file(current_files, output_file_index) current_size = 0 current_files = [] # 直接保存这个大文件 output_file = os.path.join(OUTPUT_DIR, f"merged_{output_file_index}.txt") with open(output_file, 'w', encoding='utf-8') as f: f.write(f"\n{'='*80}\n") f.write(f"文件: {filename}\n") f.write(f"{'='*80}\n\n") f.write(code) output_file_index += 1 else: # 检查是否能加入当前合并文件 if current_size + total_size <= MAX_FILE_SIZE: current_files.append((file_path, code)) current_size += total_size else: # 保存当前合并文件 if current_files: output_file_index = save_merged_file(current_files, output_file_index) current_size = 0 current_files = [] # 添加当前文件 current_files.append((file_path, code)) current_size += total_size # 保存最后一批文件 if current_files: save_merged_file(current_files, output_file_index) print(f"合并完成!生成了 {output_file_index-1} 个合并文件。") print(f"合并文件保存在: {OUTPUT_DIR}") # 验证所有合并文件的大小 print("\n验证合并文件大小:") merged_files = glob.glob(os.path.join(OUTPUT_DIR, "*.txt")) for file_path in merged_files: with open(file_path, 'r', encoding='utf-8', errors='ignore') as f: content = f.read() size = len(content) status = "✓" if size <= MAX_FILE_SIZE else "✗" print(f"{status} {os.path.basename(file_path)}: {size} 字符")
ai重构源码:
将上述得出的合并文件发送给ai,对于简单的非重点的函数不需要任何提示词,直接要结果即可,可以多开几个页面同时进行我的简单提示词
你是一个高级的逆向安全分析研究员,目标是将伪代码重构为源代码的表示
注意事项:
不要带有臆想出来的源码原来什么代码反编译后应该是什么代码,最终只保留最精确还原源码的一个版本
如果我一次发送了多个函数,请将反编译后的结果用函数原始名称分隔开来,如果函数已经有了完整的重构的反编译版本就跳过这个反编译
不要使用带有汇编代码的注释,注释应该全程使用中文,再每个关键代码处都应该具有注释
在所有代码分析完成后,单独列出所有函数重命名的列表清单
对于简单的非重点的函数不需要任何提示词,直接要结果即可
我给出的建议模板如下:
函数原始名称:sub_xxxxx
函数建议名称:函数来源(自定义函数、系统或第三方库函数、桩函数、导出函数、编译器生成函数等)_函数作用类型(加密函数、业务函数、反调试函数)_(有没有加密加固反调试代码)_如果是已知的库函数就用原有名称如果是自定义函数精简描述
sub_xxxx,重命名后的名称不要使用任何中文因为idapro不支持
函数描述: 函数来源: 安全代码:如果代码具有任何安全有关的代码,都应该这里表述,比如反调试,流量加密,签名验证等等 函数源码:
对于超大函数含需要单独分析,不要有任何忽略。
我们将所有的函数分析完成后,用trae写py脚本批量高效提取所有函数的重命名为单独的文件保存。
生成所有函数的元数据,并将重命名后的填入表
https://www.cnblogs.com/GKLBB/p/19618222
最后利用重命名后的函数名称定位关键函数,需要重点分析
什么是关键函数,如何找到关键函数,
一般来讲,被大量调用的函数是工具函数,大函数或者大量调用其他函数的函数一般是入口函数或者核心业务函数
入口函数和导出函数 → JNI_OnLoad / .init_array / DllMain(通过导出函数就可以定位这个函数)
加解密函数 → AES/RSA/自定义算法(通过关键导入函数就可以定位)
反调试函数 → ptrace检测/完整性校验(通过关键导入函数就可以定位)
核心业务函数 → 协议解析/数据处理/通信逻辑(一个业务函数通常他的调用链接最长 或者 交叉引用很少但内部逻辑复杂的函数可能是关键函数)
回调函数 → 被注册到框架中的处理函数(通过关键字字符串就可以定位)
调度函数 → 根据输入分发到不同处理逻辑 (内部控制流存在巨大的switch语句,通常有100个以上的判断)
工具函数 → 字符串处理、内存分配等(交叉引用多,被大量调用的函数通常就是)
一般来讲,我们无法使用函数名称直接定位,因为现在函数名称就是匿名化了,都是sub_开头。其余函数也可能是关键函数,这时候就需要重命名函数我们追个分析攻破就显得尤为重要了。
最后这一步结束后我们得出,
1.重构的源码文件
2.重命名的函数文件列表,包含对应重命名列和是否是关键函数列
例如

阶段三:控制流分析
控制流分析的目的是理解这个文件的到底要干啥,控制流分为内部控制流,比如if,for这些影响函数处理流程的语句,这部分ida已经实现了,外部控制流或者叫调用交叉引用链,是谁调用了我,我调用了谁。我们知道单独分析一个函数是没有意义的。我们重点分析的是外部控制流
- 导出函数控制流文件,https://www.cnblogs.com/GKLBB/p/19605218
-
我们将调用链文件和重命名的函数文件列表发送给claude去分析,比如得出如下结论,将分析结果保存在md文件中
利用Claude还原调用链: 入口函数 ├── 初始化函数A │ ├── 反调试检测 │ │ ├── ptrace自跟踪 │ │ ├── /proc/status检查 │ │ └── 管道心跳监控 │ ├── 完整性校验 │ │ ├── CRC32校验 │ │ └── 代码段hash │ └── 环境检测 │ ├── root检测 │ ├── 模拟器检测 │ └── hook框架检测 ├── 核心业务函数B │ ├── 解密DEX │ ├── 加载类 │ └── 注册Native方法 └── 清理函数C ├── 抹除痕迹 └── 释放资源
阶段四:数据流分析
数据流分析的目的是"数据从哪来、怎么变、到哪去" 。数据流分析一般包含在控制流中,不需要重点关注。有些特殊的数据需要单独追踪。我们直接跳过,不做展开。
https://www.cnblogs.com/GKLBB/p/19603685
阶段五:动态分析
动态分析目的就是查看指令和数据在内存中运行时的表现形式。这一步是最复杂的阶段,如果静态分析足够完整,动态分析就只是辅助,我们直接跳过,不做展开。
阶段六:重复重复再重复
不断重复2到5步, 最后将你分析的的每个字节在ida中标注出来,同时将未被分析的字节进行二次分析,包括未识别的函数和数据。直到所有的都是已知的即可。
阶段七:存档
存档的目的是将下一次遇见同样的问题时可以瞬间完成处理
执行的命令如下::
git clone 最后将项目上传到gitee获取上传链接
将.git文件夹复制到项目中来
cd 进入你的项目
git add .
git commit -m "第一个提交"
git push origin master
整个流程的口诀就是
导出、去杂、合并,给ai重构每个函数和函数名称
导出调用链,给ai分析处理流
最后得出结论
浙公网安备 33010602011771号