文章目录

  • 代码抽取型壳
    • 内存重组脱壳法
    • 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
    • HashEntry 结构体定义

      typedef struct HashEntry {u4        hashValue;void*    data;    // DexOrJar* pDexOrJar
      };
      

      • data

        • 这是一个 DexOrJar 结构体类型的指针,描述了当前进程的 Dalvik 虚拟机环境引用的所有 DEX 和 jar 包
    • 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 数据
    • RawDexFile 结构体定义

      struct RawDexFile {char*        cacheFileName;DvmDex*        pDvmDex;
      };
      

      • cacheFileName

        • DEX 的缓存文件名,通过它可初步判断该 DEX 是否为脱壳目标
      • pDvmDex
        • 即前面提到的要定位的 DvmDex 结构体,通过它可定位 DexFile 结构体,为最后的内存重组脱壳提供方便

  • 内存重组脱壳法流程

    • libdvm.so -> gDvm -> userDexFiles -> pEntries.isDex -> pRawDexFile -> pDvmDex -> pDexFile
  • 完整代码流程:dumpDex 脱壳脚本


Hook 脱壳法

  • 针对早期的第二代壳

  • 由于 DEX 代码在内存中完整解密,除了上述方法,还可用 Hook 脱壳法,在 DEX 加载后进行内存重组脱壳

  • 不同点

    • 内存重组脱壳法:在 APK 运行后的任意时刻用 kill 命令让程序暂停,然后从内存中将其重组并 Dump
    • Hook 脱壳法:不用暂停程序运行,重点在于查找合适的 Hook 点
  • 一个合适的 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()
      • 基于 Class 的 forName() 方式
    • 隐式加载
      • 调用的是 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

第十二章 软件壳(四)(代码抽取型壳)相关推荐

  1. 第十二章 软件壳(三)(动态加载型壳)

    文章目录 动态加载型壳 缓存脱壳法 内存 Dump 脱壳法 动态调试脱壳法 总结 Hook 脱壳法 系统定制脱壳法 动态加载型壳 即第一代壳 其发展时期正是从 Android 4.4 向 Androi ...

  2. (软件工程复习核心重点)第十二章软件项目管理-第四节:软件配置管理和能力成熟度模型

    文章目录 一:软件配置管理 (1)相关概念 A:软件配置管理定义 B:目的 C:与维护的区别 (2)软件配置 A:软件配置项 B:基线 C:软件工具 (3)软件配置管理过程 A :标识软件配置中的对象 ...

  3. (软件工程复习核心重点)第十二章软件项目管理-第三节:人员组织和质量保证

    文章目录 一:人员组织 (1)必要性 (2)典型的组织方式 A:民主制程序员组 ①:定义 ②:要求 ③:优点 ④:缺点 B:主程序员组 ①:定义 ②:核心人员及其分工 ③:特点(优点) ④:缺点 ⑤: ...

  4. (软件工程复习核心重点)第十二章软件项目管理-第二节:进度计划

    文章目录 一:相关概念 (1)任务集合 (2)项目管理者的工作 A:目标 B:方法 (3)进度安排 A:定义 B:流程 二:估算开发时间 (1)利用成本估算模型估算开发时间 (2)特殊情况 A:描述 ...

  5. (软件工程复习核心重点)第十二章软件项目管理-第一节:软件项目管理综述、估算软件规模和工作量估算

    文章目录 一:软件项目管理综述 (1)管理 (2)软件项目管理 二:估算软件规模 (1)代码行技术 A:定义 B:方法 C:优缺点 (2)功能点技术 A:定义 B:信息域特性 C:估算功能点的步骤 ① ...

  6. (软件工程复习核心重点)第十二章软件项目管理习题

    选择题 填空题 功能点技术信息域特性有 输入项数 输出项数 查询数 主文件数 外部接口数 一个任务集合包括 一组软件工程工作任务 里程碑 可交付的产品 COCOMO2的三层模型 应用系统组成模型 早期 ...

  7. 《代码整洁之道 》第十二章 迭进

    第十二章 迭进 12.1 通过迭进设计达到整洁目的 据Kent认为,只要遵循了下面的规则,设计就能变得简单 运行所有测试 不可重复 表达了程序员的意图 尽可能减少类和方法的数量 12.2 简单设计规则 ...

  8. 《汇编语言》王爽(第四版) 第十二章 实验12

    文章目录 前言 一.思路分析 1.安装 2.设置中断向量 3.do0程序 4.测试 5.优化 二.最终成果 1.完整代码 2.效果图 总结 前言 本文是王爽老师<汇编语言>(第四版) 第十 ...

  9. 【哈工大软件构造】学习笔记10 第十章、第十一章、第十二章

    目录 第十章 面向可维护性的构造技术 1 软件维护和演化 2 可维护性的度量 3 模块化设计和模块性准则 模块划分的五个准则 模块设计的五个原则 耦合度和聚合度 4 OO设计准则:SOLID SRP ...

最新文章

  1. 阿里离职员工吐槽加班太疯狂,所有的高薪都是加班加出来的!被榨干到一丝精力都不剩!婚姻不保!...
  2. nginx以unix-domain-socket方式连接fastcgi(php)
  3. K8s 学习者绝对不能错过的最全知识图谱(内含 58个知识点链接)
  4. VTK:网格之InterpolateFieldDataDemo
  5. unistd.h 中int access(const char * pathname, int mode); 判断进程能否以mode模式访问pathname文件(可以用来判断文件/目录是否存在)...
  6. Java中关于Arrays.sort的两种重载方法的理解
  7. codeforces 数论分析题
  8. SAP BTP SDK for iOS 介绍
  9. linux nfs挂载域名,Linux系统挂载NFS的方法
  10. OpenCV距离变换函数:distanceTransform()介绍
  11. Android TabLayout定制CustomView与ViewPager交互双向联动
  12. 如何在'纯'Swift中创建弱协议引用(不带@objc)
  13. 开课吧Java课堂:什么是ArrayList类
  14. android 通过usb验证应用,Linux应用可通过USB访问Android设备-Chrome OS 75版发布
  15. yarn-site.xml相关配置参数
  16. UIView中的坐标转换
  17. Java 虚拟机启动
  18. 记录小米fastboot刷机遇到的驱动问题
  19. Cisco Packet Tracer思科模拟器路由器系统的备份与恢复
  20. Scheme 语言 第一次的感触!

热门文章

  1. chromedriver.exe安装
  2. 将栅格影像转换为CAD/GIS矢量的3种方法
  3. Source Monitor的使用
  4. java实现日期加一天
  5. 从互联网+角度看云计算的现状与未来(2)
  6. A. 贝壳找房性价比
  7. 作为元宇宙的雏形:GameFi领域年末正在不断成长
  8. python3for metro_Fluent Python 译本 读书笔记 第5章 一等函数
  9. 角度与弧度之间的转换
  10. [1] OPC UA基础知识简介