Legu3.0脱壳心路历程
字数 2363 2025-08-22 18:37:22
Legu3.0脱壳心路历程 - 完整教学文档
1. 概述
本教学文档详细记录了Legu3.0加固应用的完整脱壳过程,从Java层到Native层的分析方法和技巧。文档将按照实际分析流程,分步骤讲解脱壳的关键技术点。
2. 环境准备
- 测试设备:Dalvik虚拟机4.4版本
- 分析工具:
- IDA Pro 6.8/7.2
- JEB
- Jadx
- 010 Editor
- readelf
- 目标应用:TX加固的应用(特征:libshellxxx.so)
3. Java层分析
3.1 识别加固特征
-
Manifest分析:
- 入口类
LoadingActivity在APK中找不到 - Application类被替换为
com.tencent.StubShell.TxAppEntry
- 入口类
-
关键方法分析:
- 重点关注
attachBaseContext方法(在onCreate之前执行) - 主要调用链:
e(context) → 调试检查 b(this) → 库地址初始化 d(context) → 加载nfix/ufix库并调用native修复方法 a(context) → 核心加载逻辑
- 重点关注
3.2 attachBaseContext深入分析
-
关键方法
d(context):- 尝试加载不存在的库
nfix和ufix - 调用native方法
fixNativeResource和fixUnityResource
- 尝试加载不存在的库
-
关键方法
a(context):- 调用
e()和load(f) e()方法首次加载核心so库shellload(f)方法加载shella-3.0.0.0.so
- 调用
3.3 onCreate分析
- 主要行为:
- 反调试检查(
isDebugger) - 调用native方法
runCreate - 崩溃信息收集
- 反调试检查(
3.4 广播接收器分析
TxReceiver类:- 通过native方法
reciver实现广播接收 - 可能是动态注册的广播接收器
- 通过native方法
4. Native层分析
4.1 初始挑战:Anti-IDA
-
现象:
- IDA无法识别so文件的节格式
- 关键节头表(.dynstr, .dynsym)数据被抹空
-
解决方案:
- 使用010 Editor修改节头:
- 将.dynsym的s_size字段置0
- 或直接置空节数据
- IDA会转而使用程序头表进行分析
- 使用010 Editor修改节头:
4.2 JNI_OnLoad加密
-
发现:
- JNI_OnLoad函数被加密(虚拟地址0x274C)
- 解密逻辑应在.init或.init_array节
-
修复节头:
- 使用开源工具修复so文件节头
- 定位到.init_array节(地址0x3e84)
- 找到解密函数(地址0x944)
-
解密逻辑:
- 对0x1000开始的0x2AB4字节数据进行解密
- 包含JNI_OnLoad函数
4.3 解密方法选择
-
静态解密:
- 分析解密算法编写解密脚本
-
动态dump:
- 从内存中dump解密后的so文件
- IDA脚本示例:
static main() { auto i, fp; fp = fopen("d:\\dump", "wb"); auto start = 0x75FFD000; auto size = 32768; for (i = start; i < start + size; i++) { fputc(Byte(i), fp); } }
4.4 JNI_OnLoad动态分析
-
调用路径:
- 实际由
libdvm.so的dvmLoadNativeCode调用
- 实际由
-
PLT/GOT重定位:
- 第三方库函数调用通过PLT表跳转
- 首次调用会解析函数地址并写入GOT表
-
二次解密:
- 第一层JNI_OnLoad解密后执行第二层解密
- 从so文件尾部(偏移0x6D88)读取附加数据并解密
4.5 本地方法注册分析
-
注册过程:
- 使用
dlsym查找JNI_OnLoad符号 - 通过
RegisterNatives注册本地方法
- 使用
-
注册方法列表:
- load (0x75700B1D)
- runCreate (0x756fc469)
- changeEnv (0x756FB37D)
- receiver (0x756f7621)
- txEntries (0x756FB0F9)
5. 动态调试技巧
5.1 绕过反调试
-
方法一:
- 修改
isDebuggerConnected返回值 - 动态调试时修改寄存器/内存值
- 修改
-
方法二:
- 使用
mprop工具设置ro.debuggable属性 - 命令:
./mprop ro.debuggable 1
- 使用
5.2 处理Signal 11错误
-
原因:
- 重打包后程序崩溃
- 可能由签名或manifest修改引起
-
解决方案:
- 避免修改manifest调试属性
- 使用注入方式附加调试
6. Dex解密与dump
6.1 load方法分析
-
关键操作:
- 获取odex基址(如0x750DD000)
- 解密dex头部数据(0xE0字节)
- 结合解密后的头部和原始数据重建dex
-
dump脚本:
- dump头部:
static main(void) { auto fp, begin, end, ptr; fp = fopen("d:\\header.dex", "wb"); begin = 0x74fd7000; len = 0xe0; for (ptr = begin; ptr < begin + len; ptr++) fputc(Byte(ptr), fp); } - dump完整odex:
static main(void) { auto fp, begin, end, ptr; fp = fopen("d:\\dump.odex", "wb"); begin = 0x74fd7000; end = 0x75b2f000; for (ptr = begin; ptr < end; ptr++) fputc(Byte(ptr), fp); }
- dump头部:
6.2 重建Dex文件
- 步骤:
- 搜索magic值:
64 65 78 0a 30 33 35 - 提取前0xE0字节作为header
- 读取偏移0x20处的文件大小
- 计算dex文件偏移:
a1+0x6C = data_off a1+0x68 = data_size dex_addr = odex_base + 0x28 + dex_offset - 合并正确的header和body数据
- 搜索magic值:
7. 关键知识点总结
-
ELF文件分析:
- IDA在节头识别失败时会使用程序头表
- ELF节头表不会被装载进内存
-
动态调试技巧:
- 使用
TakeMemorySnapshot(1)保存内存快照 - 修改段属性时勾选loader选项
- 使用
-
Odex结构:
- 0x28为odex中dex_header的偏移
(odexAddr + 0x28)为dex header绝对地址
-
宏定义:
#define HIDWORD(l) ((DWORD)(((DWORDLONG)(l) >> 32) & 0xFFFFFFFF))
8. 参考资源
- 加固特征识别经验
- 手动绕过百度加固Debug.isDebuggerConnected反调试的方法
- ELF文件格式规范
- IDA Pro高级使用技巧
通过本教学文档,读者可以系统掌握从Java层到Native层的完整脱壳流程,包括对抗Anti-IDA、解密加密函数、动态调试技巧以及Dex重建等关键技术。