【胖虎的逆向之路】04——脱壳(一代壳)原理&脱壳相关概念详解

【胖虎的逆向之路】01——动态加载和类加载机制详解
【胖虎的逆向之路】02——Android整体加壳原理详解&实现
【胖虎的逆向之路】03——Android一代壳脱壳办法&实操


文章目录

  • 【胖虎的逆向之路】04——脱壳(一代壳)原理&脱壳相关概念详解
  • 前言
  • 一、Dex加载流程
  • 二、Dex2Oat编译流程
  • 三、类加载流程
  • 四、DexFile详解
    • (1)直接查找法
    • (2)间接查找法
  • 五、ArtMethod详解
  • 总结
  • 参考文献

前言

提示:这里可以添加本文要记录的大概内容:

在上文中,我们讲解了关于Android脱壳的基本办法和实际操作,现在我们来针对脱壳(一代壳)的原理和脱壳相关的基础知识介绍,由于作者能力有限,会尽力的详细描述 一代壳脱壳 的流程及原理,如本文中有任何错误,烦请指正,感谢~


一、Dex加载流程

在日常分析脱壳点过程中,Dex加载的基本流程也是要明白熟悉的

DexPathList:该类主要用来查找Dex、SO库的路径,并这些路径整体呈一个数组
Element:根据多路径的分隔符“;”将dexPath转换成File列表,记录所有的dexFile
DexFile:用来描述Dex文件,Dex的加载以及Class的查找都是由该类调用它的native方法完成的

我们依次来分析这个过程中的源码

DexPathList

/libcore/dalvik/src/main/java/dalvik/system/DexPathList.java
public DexPathList(ClassLoader definingContext, String dexPath,String librarySearchPath, File optimizedDirectory) {**********************      this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory,suppressedExceptions, definingContext);
**********************  }

makeDexElements

private static Element[] makeDexElements(List<File> files, File optimizedDirectory,List<IOException> suppressedExceptions, ClassLoader loader) {**********************            DexFile dex = loadDexFile(file, optimizedDirectory, loader, elements);
**********************         }

loadDexFile

private static DexFile loadDexFile(File file, File optimizedDirectory, ClassLoader loader,Element[] elements)throws IOException {if (optimizedDirectory == null) {return new DexFile(file, loader, elements);} else {String optimizedPath = optimizedPathFor(file, optimizedDirectory);return DexFile.loadDex(file.getPath(), optimizedPath, 0, loader, elements);}}

loadDex

static DexFile loadDex(String sourcePathName, String outputPathName,int flags, ClassLoader loader, DexPathList.Element[] elements) throws IOException {return new DexFile(sourcePathName, outputPathName, flags, loader, elements);}

DexFile

/libcore/dalvik/src/main/java/dalvik/system/DexFile.java
DexFile(String fileName, ClassLoader loader, DexPathList.Element[] elements) throws IOException {mCookie = openDexFile(fileName, null, 0, loader, elements);mInternalCookie = mCookie;mFileName = fileName;//System.out.println("DEX FILE cookie is " + mCookie + " fileName=" + fileName);}

这里出现的mCookie,mCookie在C/C++层中是DexFile的指针,我们在下面详细讲解

openDexFile

private static Object openDexFile(String sourceName, String outputName, int flags,ClassLoader loader, DexPathList.Element[] elements) throws IOException {// Use absolute paths to enable the use of relative paths when testing on host.return openDexFileNative(new File(sourceName).getAbsolutePath(),(outputName == null)? null: new File(outputName).getAbsolutePath(),flags,loader,elements);}

这里就进入了C/C++层

openDexFileNative

为了节约篇幅,我们快速分析,中间再经过一些函数

OpenDexFilesFromOat()
MakeUpToDate()
GenerateOatFileNoChecks()
Dex2Oat()

最后进入了Dex2Oat,这就进入了Dex2Oat的编译流程

反之如果我们在下面Dex2Oat的流程中通过Hook相关方法或execv或execve导致dex2oat失败,我们就会返回到OpenDexFilesFromOat

OpenDexFilesFromOat

会先在HasOriginalDexFiles里尝试加载我们的Dex,也就是说,倘若我们的壳阻断了dex2oat的编译流程,然后又调用了DexFile的Open函数。

DexFile::Open

校验dex的魔术字字段,然后调用DexFile::OpenFile

DexFile::OpenFile

/art/runtime/dex_file.cc
std::unique_ptr<const DexFile> DexFile::OpenFile(int fd,const std::string& location,bool verify,bool verify_checksum,std::string* error_msg) {**************************************std::unique_ptr<DexFile> dex_file = OpenCommon(map->Begin(),map->Size(),location,dex_header->checksum_,kNoOatDexFile,verify,verify_checksum,error_msg);   **************************************}

OpenCommon

最后又再次回到DexFile类,这里我们的dex文件加载基本流程分析完毕

二、Dex2Oat编译流程

Dex2oat是google公司为了提高编译效率的一种机制,从Android8.0开始实施,一些加壳厂商实现抽取壳往往会禁用Dex2oat,而针对整体加壳没有禁用的Dex2Oat也成为了脱壳点


Exec

/art/runtime/exec_utils.cc
bool Exec(std::vector<std::string>& arg_vector, std::string* error_msg) {int status = ExecAndReturnCode(arg_vector, error_msg);if (status != 0) {const std::string command_line(android::base::Join(arg_vector, ' '));*error_msg = StringPrintf("Failed execv(%s) because non-0 exit status",command_line.c_str());return false;}return true;
}

ExecAndReturnCode

而我们就可以通过Hook execv或execve来禁用Dex2Oat,而如果我们不禁用dex2oat,execve函数是用来调用dex2oat的二进制程序实现对dex文件的加载,我们这时候找到dex2oat.cc这个文件,找到main函数

/art/dex2oat/dex2oat.ccint main(int argc, char** argv) {int result = static_cast<int>(art::Dex2oat(argc, argv));if (!art::kIsDebugBuild && (RUNNING_ON_MEMORY_TOOL == 0)) {_exit(result);}return result;

这里我们调用了Dex2oat

Dex2Oat

/art/dex2oat/dex2oat.cc
static dex2oat::ReturnCode Dex2oat(int argc, char** argv) {**************************************dex2oat::ReturnCode setup_code = dex2oat->Setup();dex2oat::ReturnCode result;if (dex2oat->IsImage()) {result = CompileImage(*dex2oat);} else {result = CompileApp(*dex2oat);}**************************************
}

Dex2oat中会对dex文件进行逐个类逐个函数的编译,setup()函数完成对dex的加载

然后顺序执行,就会进入CompileApp

编译过程中会按照逐个函数进行编译,就会进入CompileMethod

到这里Dex2oat的基本流程就分析完毕

三、类加载流程

要理解DexFile为什么如此重要,首先我们要清除Android APP的类加载流程。Android的类加载一般分为两类隐式加载和显式加载

1.隐式加载:(1)创建类的实例,也就是new一个对象(2)访问某个类或接口的静态变量,或者对该静态变量赋值(3)调用类的静态方法(4)反射Class.forName("android.app.ActivityThread")(5)初始化一个类的子类(会首先初始化子类的父类)
2.显示加载:(1)使用LoadClass()加载(2)使用forName()加载

我们详细看一下显示加载:

Class.forName 和 ClassLoader.loadClass加载有何不同:
(1)ClassLoader.loadClass也能加载一个类,但是不会触发类的初始化(也就是说不会对类的静态变量,静态代码块进行初始化操作)
(2)Class.forName这种方式,不但会加载一个类,还会触发类的初始化阶段,也能够为这个类的静态变量,静态代码块进行初始化操作

我们在详细来看一下在类加载过程中的流程:

java层

我们可以发现类加载中关键的DexFile,该类用来描述Dex文件,所以我们的脱壳对象就是DexFile

这里从DexFile进入Native层中,还有一个关键的字段就是mCookie


后面我们详细的介绍mCookie的作用

我们进一步分析,进入Native层

Native层

/art/runtime/native/[dalvik_system_DexFile.cc

ConvertJavaArrayToDexFiles对cookie进行了处理


通过这里的分析,我们可以知道mCooike转换为C/C++层指针后,就是dexfile的索引

我们继续分析DefineClass

art/runtime/class_linker.cc
mirror::Class* ClassLinker::DefineClass(Thread* self,const char* descriptor,size_t hash,Handle<mirror::ClassLoader> class_loader,const DexFile& dex_file,const DexFile::ClassDef& dex_class_def) {***************
LoadClass(self, *new_dex_file, *new_class_def, klass);
***************
}

LoadClass

art/runtime/class_linker.cc
void ClassLinker::LoadClass(Thread* self,
3120                            const DexFile& dex_file,
3121                            const DexFile::ClassDef& dex_class_def,
3122                            Handle<mirror::Class> klass) {3123  const uint8_t* class_data = dex_file.GetClassData(dex_class_def);
3124  if (class_data == nullptr) {3125    return;  // no fields or methods - for example a marker interface
3126  }
3127  LoadClassMembers(self, dex_file, class_data, klass);
3128}

LoadClassMembers

art/runtime/class_linker.cc
void ClassLinker::LoadClassMembers(Thread* self,const DexFile& dex_file,const uint8_t* class_data,Handle<mirror::Class> klass) {***************LoadMethod(dex_file, it, klass, method);LinkCode(this, method, oat_class_ptr, class_def_method_index);
***************
}

LoadMethod

art/runtime/class_linker.cc
void ClassLinker::LoadMethod(const DexFile& dex_file,const ClassDataItemIterator& it,Handle<mirror::Class> klass,ArtMethod* dst) {}

LinkCode

我们可以发现这里就进入了从linkcode后就进入了解释器中,并对是否进行dex2oat进行了判断,我们直接进入解释器中继续分析

我们知道Art解释器分为两种:解释模式下和quick模式下,而我们又知道Android8.0开始进行dex2oat

如果壳没有禁用dex2oat,那类中的初始化函数运行在解释器模式下
如果壳禁用dex2oat,dex文件中的所有函数都运行在解释器模式下
则类的初始化函数运行在解释器模式下

所以一般的加壳厂商会禁用掉dex2oat,这样可以是所有的函数都运行在解释模式下,所以一些脱壳点选在dex2oat流程中,可能针对禁用dex2oat的情况并不使用,我们这里主要针对整体加壳,就不展开讲述,最后我们得知解释器中会运行在Execute下

Execute

art/runtime/interpreter/interpreter.cc
static inline JValue Execute(Thread* self,const DexFile::CodeItem* code_item,ShadowFrame& shadow_frame,JValue result_register,bool stay_in_interpreter = false) REQUIRES_SHARED(Locks::mutator_lock_){***************ArtMethod *method = shadow_frame.GetMethod();
***************}

这里我们大致分析完成了类加载的思路

四、DexFile详解

前面我们分析了很多,对dex加载、类加载等都已经有了一个很详细的了解,而最终一切的核心就是DexFile,DexFile就是我们脱壳所关注的重点,寒冰大佬在拨云见日:安卓APP脱壳的本质以及如何快速发现ART下的脱壳点中提到,在ART下只要获得了DexFile对象,那么我们就可以得到该dex文件在内存中的起始地址和大小,进而完成脱壳。

我们先查看一些DexFile的结构体


只要我们能获得起始地址begin和大小size,就可以成功的将dex文件脱取下来,这里我们记得DexFile含有虚函数表,所以根据C++布局,要偏移一个指针

而DexFile类还给我们提供了方便的API

这样只要我们找到函数中有DexFile对象,就可以通过调用API来进一步dump dex文件,由此按照寒冰大佬的思想,大量的脱壳点由此产生

(1)直接查找法

我们通过直接在Android源码中搜索DexFile,就可以获得海量的脱壳点


我们通过在IDA中搜索libart.so导出的DexFile,同样可以获得大量的脱壳点

(2)间接查找法

这里就是寒冰大佬在文章中提到的通过ArtMethod对象的getDexFile()获取到ArtMethod所属的DexFile对象的这种一级间接法,通过Thread的getCurrentMethod()函数首先获取到ArtMethod或者通过ShadowFrame的getMethod获取到ArtMethod对象,然后再通过getDexFile获取到ArtMethod对象所属的DexFile的二级间接法

getDexFile()
getMethod()

五、ArtMethod详解

上面我们已经详细分析了DexFile的文件结构,我们知道通过ArtMethod可以获得DexFile,那么为啥又要单独提ArtMethod呢,因为ArtMethod在抽取壳和VMP等壳中扮演了重要的角色

ArtMethod结构体

我们通过ArtMethod可以获得codeitem的偏移和方法索引,熟悉dex结构的朋友知道codeitem就是代码实际的值,而codeitem则再后续加壳技术扮演了至关重要的地址,而且ArtMethod还有非常丰富的方法,可以帮助大家实现很多功能,所以在脱壳工作中也是十分重要的

总结

以上就是今天要讲的内容,主要借鉴了 随风而行 大佬的一些思路及文章,将文章在复写一遍目的是为了加深记忆,在以后的学习过程中可以很方便的查看~

参考文献

https://security-kitchen.com/2022/12/04/Packer5/
https://bbs.kanxue.com/thread-254555.htm#msg_header_h2_2

【胖虎的逆向之路】04——脱壳(一代壳)原理脱壳相关概念详解相关推荐

  1. 【胖虎的逆向之路】01——动态加载和类加载机制详解

    胖虎的逆向之路 01--动态加载和类加载机制详解 一.前言 二.类的加载器 1. 双亲委派模式 2. Android 中的类加载机制 1)Android 基本类的预加载 2)Android类加载器层级 ...

  2. 【胖虎的逆向之路】03——Android一代壳脱壳办法罗列实操

    [胖虎的逆向之路]03--Android脱壳办法罗列&脱壳原理详解 [胖虎的逆向之路]01--动态加载和类加载机制详解 [胖虎的逆向之路]02--Android整体加壳原理详解&实现 ...

  3. 【胖虎的逆向之路】02——Android整体加壳原理详解实现

    [胖虎的逆向之路](02)--Android整体加壳原理详解&实现 Android Apk的加壳原理流程及详解 文章目录 [胖虎的逆向之路](02)--Android整体加壳原理详解& ...

  4. 【胖虎的逆向之路】如何绕过 Android11新特性之 “包的可见性“

    前言 距离Android11 发布已经过去了,当初我有大概了解过一些Android 11上的行为变更,总体变化虽然不少,但是要求我们必须去适配的地方并不算多.对于我而言可能需要注意的是文件相关权限,譬 ...

  5. 【胖虎的逆向之路】Android 7.0 上Magisk配合Xposed的相关问题

    基础环境 1.Android 7.1.0(硬件小米6 sagit): 2.Magisk V23.0 3.Xposed (由Magisk-模块-搜索下载) 安装 首先android刷机.Magisk R ...

  6. 脱壳工具:BlackDex的使用详解

    1.BlackDex:是一个运行在Android手机上的脱壳工具,支持5.0-12,无需依赖任何环境任何手机都可以使用,包括模拟器.只需几秒,即可对已安装包括未安装的APK进行脱壳. 备注:Black ...

  7. 脱壳工具:ZjDroid的使用详解

    一. ZjDroid概述 ZjDroid是基于Xposed Framewrok的动态逆向分析模块,可以完美解决二代加固. github地址:https://github.com/halfkiss/Zj ...

  8. Ubuntu18.04没有WiFi怎么解决(图文详解)

    博主一个月前在安装Ubuntu后出现了没有WiFi的问题,参考了很多教程,才成功解决这个问题,中间做了很多无用功,所以在此总结几位大佬的方法,希望对大家有帮助,少走弯路. 1.问题描述 登录Ubunt ...

  9. windows 10远程连接ubuntu 18.04 Gnome桌面:NoMachine工具使用详解

    文章目录 Gnome桌面环境 NoMachine工具 实际操作 Ubuntu操作 Windows操作 远程连接设置 总结 做为系统管理员,远程连接到各种服务器算是常规操作.如果本地是windows环境 ...

最新文章

  1. 0x61.图论 - 最短路
  2. Oracle 11.2 安装Oracle 11.1的HR schoma
  3. 排序---初级排序算法(选择排序、插入排序和希尔排序)
  4. 图书查找java_java第三季第一章:查找图书信息实现
  5. 仿京东左侧二级导航条
  6. [html] 你知道短链接的生成原理吗?
  7. 腾讯云连续四年排名中国音视频解决市场第一,头部厂商中RTC增速第一
  8. linux的odbc数据库连接失败,在Linux上通过ODBC进行Delphi SQLConnection无法连接到SQL
  9. NOIP 2000 进制转换
  10. 解决Tomcat严重: Parse error in application web.xml file at jndi:/localhost/ipws/WEB-INF/web.xml java.lan
  11. ms office excel2013教程 - 套用表格样式
  12. STM32 学习笔记 expected a type specifier
  13. gcc之 -ffunction-sections
  14. android:ListView的局部刷新
  15. Idea设置Java类注释模板和方法注释模板
  16. win10玩cf不能全屏_神奇的工作室ghost 不能启动
  17. elasticsearch win10 安装
  18. 长微博工具:菊子曰自动微博文字转图片,突破微博140字限制,一键发布完成...
  19. Windows 开发之VC++垃圾清理程序软件
  20. js filter 多条件过滤适合对象属性

热门文章

  1. JavaWeb基于老杜课程笔记的完善
  2. Neo4j和Cypher批量更新和批量插入优化
  3. ib数学ia选题例子
  4. html5 鼠标动画效果,7 个让人惊叹的 HTML5 鼠标动画
  5. Double precision
  6. 同程旅游——Java开发面经
  7. 5000词学英语——DAY7
  8. 荣耀X10 Max发布在即 屏幕优势显著
  9. 【软件工具类】常用科研办公软件工具汇总
  10. ipconfig失败