代码抽取型壳
- 即第二代壳
- 主要特点
- 即使 DEX 已加载到内存,仍处于加密状态(所有 DEX 方法都在运行时解密)
- 比第一代壳难脱
内存重组脱壳法
-
代码抽取型壳经历多次技术迭代
-
最初是将 DEX 的 DexCode 提取后填 0,将 DEX 的所有内容保存在 APK 中,APK 运行时会在内存中动态解密,所有解密的方法内容指针位于 DEX 文件结构体外部的内存中,从而有效避免了只知道 DEX 的起始地址即可快速 Dump 的问题
-
内存重组脱壳法能有效对付此种壳,其通过解析内存中 DEX 的格式,将其重新组合成 DEX,可实现百分百 DEX 代码还原,虽然出现过一些针对内存重组的 Anti,但理论上只要 DEX 在内存中是完整的,即可通过此法脱壳
-
在内存中加载完成的 DEX 是个 DvmDex 结构体:
typedef struct DvmDex { DexFile* pDexFile; const DexHeader* pHeader; struct StringObject** pResStrings; struct ClassObject** pResClasses; struct Method** pResMethods; struct Field** pResFields; struct AtomicCache* pInterfaceCache; MemMapping memMap; pthread_mutex_t modLock; } DvmDex;
-
pDexFile
- 遍历它的字段即可得到 DEX 的完整内容
-
首要问题
- 如何在内存中定位 DvmDex
-
通用的定位 DvmDex 结构体方法
-
Android 源码中
libdvm.so
导出一个 gDvm 符号,这是 DvmGlobals 结构体类型,其定义位于 Android 源码dalvik/vm/Globals.h
。DvmGlobals 结构体中有个 HashTable 结构体指针类型的 userDexFiles 字段typedef struct HashTable { int tableSize; // must be power of 2 int numEntries; // current #of "live" entries int numDeadEntries; // current #of tombstone entries HashEntry* pEntries; /* array on heap */ +0x0c HashFreeFunc freeFunc; pthread_mutex_t lock; } HashTable;
- numEntries、pEntries
- 描述了 HashTable 结构体的个数和结构体起始指针,通过它们可定位所有引用的 HashEntry
- numEntries、pEntries
-
HashEntry 结构体定义
typedef struct HashEntry { u4 hashValue; void* data; // DexOrJar* pDexOrJar
-