第十二章 软件壳(四)(代码抽取型壳)
文章目录
- 代码抽取型壳
- 内存重组脱壳法
- Hook 脱壳法
- 系统定制脱壳法
- 脱壳工具
代码抽取型壳
- 即第二代壳
- 主要特点
- 即使 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 2int numEntries; // current #of "live" entriesint numDeadEntries; // current #of tombstone entriesHashEntry* pEntries; /* array on heap */ +0x0cHashFreeFunc freeFunc;pthread_mutex_t lock; } HashTable;
- numEntries、pEntries
- 描述了 HashTable 结构体的个数和结构体起始指针,通过它们可定位所有引用的 HashEntry
- numEntries、pEntries
HashEntry 结构体定义
typedef struct HashEntry {u4 hashValue;void* data; // DexOrJar* pDexOrJar };
- data
- 这是一个 DexOrJar 结构体类型的指针,描述了当前进程的 Dalvik 虚拟机环境引用的所有 DEX 和 jar 包
- data
DexOrJar 结构体定义
typedef struct DexOrJar {char* fileName;bool isDex;bool okayToFree;RawDexFile* pRawDexFile;JarFile* pJarFile; } DexOrJar;
- isDex
- 指定当前结构体描述的是一个 DEX 结构还是 jar 包结构。若其值为 true,则 pRawDexFile 字段有效,指向 RawDexFile 结构体类型的 DEX 数据;若其值为 false,则 pJarFile 字段有效,指向 JarFile 结构体类型的 jar 包。若是 DEX 脱壳,则要关注其值为 true 时指向的 RawDexFile 结构体类型的 DEX 数据
- isDex
RawDexFile 结构体定义
struct RawDexFile {char* cacheFileName;DvmDex* pDvmDex; };
- cacheFileName
- DEX 的缓存文件名,通过它可初步判断该 DEX 是否为脱壳目标
- pDvmDex
- 即前面提到的要定位的 DvmDex 结构体,通过它可定位 DexFile 结构体,为最后的内存重组脱壳提供方便
- cacheFileName
内存重组脱壳法流程
libdvm.so
->gDvm
->userDexFiles
->pEntries.isDex
->pRawDexFile
->pDvmDex
->pDexFile
完整代码流程:dumpDex 脱壳脚本
Hook 脱壳法
针对早期的第二代壳
由于 DEX 代码在内存中完整解密,除了上述方法,还可用 Hook 脱壳法,在 DEX 加载后进行内存重组脱壳
不同点
- 内存重组脱壳法:在 APK 运行后的任意时刻用
kill
命令让程序暂停,然后从内存中将其重组并 Dump - Hook 脱壳法:不用暂停程序运行,重点在于查找合适的 Hook 点
- 内存重组脱壳法:在 APK 运行后的任意时刻用
一个合适的 Hook点
libdvm.so 中的 dvmCallMethodV()
当一个 APK 启动时,首先会执行其 Application 类的 onCreate()
Dalvik 虚拟机通过 dvmCallMethodV() 启动 Java 方法,其实现位于 Android 源码
dalvik/vm/interp/Stack.cpp
,其函数原型:void dvmCallMethodV(Thread* self, const Method* method, Object* obj, bool fromJni, JValue* pResult, va_list args);
- 对第二个 Method 类型的 method 参数,可通过其 name 字段判断当前执行的方法名,确定是 onCreate() 时,可进一步判断方法所在的类的名字,从而确定其是否为脱壳目标。获取 ClassObject 类型的类对象指针后,可通过其 pDvmDex 字段获取内存重组脱壳法所用的 DvmDex 结构体信息,接下来的 DEX 内存重组步骤和前述方法一样
系统定制脱壳法
- 后期的第二代软件壳,不再一次性在内存中解密所有 DEX 方法,而在执行具体的方法时才解密方法内容
- 如此一来,若直接内存 Dump 或 Hook 脱壳,只能提取在内存中解密过的 DEX 方法,没启动过的 DEX 方法仍处于加密状态,前述两种方法因此失效
- 一次性将 DEX 中所有方法在内存中加载并解密是对抗这种壳的有效方法,涉及 DEX 的加载和初始化过程
- Java 的类加载
- 显示加载
- 基于 ClassLoader 的 loadClass() 方式
- 在 Dalvik 虚拟机中调用了
Dalvik_java_lang_Class_classForName()
- 在 Dalvik 虚拟机中调用了
- 基于 Class 的 forName() 方式
- 基于 ClassLoader 的 loadClass() 方式
- 隐式加载
- 调用的是 dvmResolveClass()
- 显示加载
- 它们在底层都会执行
Dalvik_dalvik_system_DexFile_defineClassNative()
,因此可修改 Dalvik 虚拟机中该方法的实现代码,通过调用 dvmDefineClass() 手动加载 DEX 中所有的类
脱壳工具
DexHunter
针对第二代壳的通用脱壳工具
其脱壳代码的核心是 DumpClass()
void* DumpClass(void* parament) {...const char* header = "Landroid";unsigned int num_class_defs = pDexFile->pHeader->classDefsSize;uint32_t total_pointer = mem->length - uint32_t(pDexFile->baseAddr - (const u1*)mem->addr);uint32_t rec = total_pointer;while (total_pointer)total_pointer++;int inc = total_pointer - rec;uint32_t start = pDexFile->pHeader->classDefsOff + sizeof(DexClassDef) * num_class_defs;uint32_t end = (uint32_t)((const u1*)mem->addr + mem->length - pDexFile->baseAddr);for (size_t i = 0; i < num_class_defs; i++) {...const DexClassDef* pClassDef = dexGetClassDef(pDvmDex->pDexFile, i);const char* descriptor = dexGetClassDescriptor(pDvmDex->pDexFile, pClassDef);if (!strncmp(header, descriptor, 8) || !pClassDef->classDataOff) {pass = true;goto classdef;}clazz = dvmDefineClass(pDvmDex, descriptor, loader);...if (!dvmIsClassInitialized(clazz))if (dvmInitClass(clazz))ALOGI("GOT IT init: %s", descriptor);if (pClassDef->classDataOff < start || pClassDef->classDataOff > end)need_extra = true;data = dexGetClassData(pDexFile, pClassDef);pData = ReadClassData(&data);if (!pData)continue;if (pData->directMethods) {...}if (pData->virtualMethods) {...}classdef:...if (need_extra) {...uint8_t* out = EncodeClassData(pData, class_data_len);...ALOGI("GOT IT classdata written");}else {if (pData) {free(pData);}}...}...time = dvmGetRelativeTimeMesc();ALOGI("GOT IT end: %d ms", time);return NULL; }
num_class_defs
- 代表所有要加载的类,通过它可遍历 DEX 中的类和方法
dexGetClassDef()
- 用于获取指定序号的 DEX 方法的 DexClassDef 结构体。将该结构体传递给 dexGetClassDescriptor(),可获取类的签名描述信息 descriptor
- 要想显式加载类的签名描述信息,可调用 dvmDefineClass()
- 对加载后的类,可遍历其实例方法和虚方法,进而修改其 DexCode
DexHunter 的做法是将不需要解密的数据和要解密的数据分别保存,最后合并成完整的 DEX
第十二章 软件壳(四)(代码抽取型壳)相关推荐
- 第十二章 软件壳(三)(动态加载型壳)
文章目录 动态加载型壳 缓存脱壳法 内存 Dump 脱壳法 动态调试脱壳法 总结 Hook 脱壳法 系统定制脱壳法 动态加载型壳 即第一代壳 其发展时期正是从 Android 4.4 向 Androi ...
- (软件工程复习核心重点)第十二章软件项目管理-第四节:软件配置管理和能力成熟度模型
文章目录 一:软件配置管理 (1)相关概念 A:软件配置管理定义 B:目的 C:与维护的区别 (2)软件配置 A:软件配置项 B:基线 C:软件工具 (3)软件配置管理过程 A :标识软件配置中的对象 ...
- (软件工程复习核心重点)第十二章软件项目管理-第三节:人员组织和质量保证
文章目录 一:人员组织 (1)必要性 (2)典型的组织方式 A:民主制程序员组 ①:定义 ②:要求 ③:优点 ④:缺点 B:主程序员组 ①:定义 ②:核心人员及其分工 ③:特点(优点) ④:缺点 ⑤: ...
- (软件工程复习核心重点)第十二章软件项目管理-第二节:进度计划
文章目录 一:相关概念 (1)任务集合 (2)项目管理者的工作 A:目标 B:方法 (3)进度安排 A:定义 B:流程 二:估算开发时间 (1)利用成本估算模型估算开发时间 (2)特殊情况 A:描述 ...
- (软件工程复习核心重点)第十二章软件项目管理-第一节:软件项目管理综述、估算软件规模和工作量估算
文章目录 一:软件项目管理综述 (1)管理 (2)软件项目管理 二:估算软件规模 (1)代码行技术 A:定义 B:方法 C:优缺点 (2)功能点技术 A:定义 B:信息域特性 C:估算功能点的步骤 ① ...
- (软件工程复习核心重点)第十二章软件项目管理习题
选择题 填空题 功能点技术信息域特性有 输入项数 输出项数 查询数 主文件数 外部接口数 一个任务集合包括 一组软件工程工作任务 里程碑 可交付的产品 COCOMO2的三层模型 应用系统组成模型 早期 ...
- 《代码整洁之道 》第十二章 迭进
第十二章 迭进 12.1 通过迭进设计达到整洁目的 据Kent认为,只要遵循了下面的规则,设计就能变得简单 运行所有测试 不可重复 表达了程序员的意图 尽可能减少类和方法的数量 12.2 简单设计规则 ...
- 《汇编语言》王爽(第四版) 第十二章 实验12
文章目录 前言 一.思路分析 1.安装 2.设置中断向量 3.do0程序 4.测试 5.优化 二.最终成果 1.完整代码 2.效果图 总结 前言 本文是王爽老师<汇编语言>(第四版) 第十 ...
- 【哈工大软件构造】学习笔记10 第十章、第十一章、第十二章
目录 第十章 面向可维护性的构造技术 1 软件维护和演化 2 可维护性的度量 3 模块化设计和模块性准则 模块划分的五个准则 模块设计的五个原则 耦合度和聚合度 4 OO设计准则:SOLID SRP ...
最新文章
- 阿里离职员工吐槽加班太疯狂,所有的高薪都是加班加出来的!被榨干到一丝精力都不剩!婚姻不保!...
- nginx以unix-domain-socket方式连接fastcgi(php)
- K8s 学习者绝对不能错过的最全知识图谱(内含 58个知识点链接)
- VTK:网格之InterpolateFieldDataDemo
- unistd.h 中int access(const char * pathname, int mode); 判断进程能否以mode模式访问pathname文件(可以用来判断文件/目录是否存在)...
- Java中关于Arrays.sort的两种重载方法的理解
- codeforces 数论分析题
- SAP BTP SDK for iOS 介绍
- linux nfs挂载域名,Linux系统挂载NFS的方法
- OpenCV距离变换函数:distanceTransform()介绍
- Android TabLayout定制CustomView与ViewPager交互双向联动
- 如何在'纯'Swift中创建弱协议引用(不带@objc)
- 开课吧Java课堂:什么是ArrayList类
- android 通过usb验证应用,Linux应用可通过USB访问Android设备-Chrome OS 75版发布
- yarn-site.xml相关配置参数
- UIView中的坐标转换
- Java 虚拟机启动
- 记录小米fastboot刷机遇到的驱动问题
- Cisco Packet Tracer思科模拟器路由器系统的备份与恢复
- Scheme 语言 第一次的感触!