Dalvik下一代壳的通用解决方案

本节主要分析在Dalvik时代怎么做到把壳脱下来的,通过Android 4.x的源码跟踪一下系统帮我们加载dex时候的流程,分析一下脱壳的原理和时机。
Dalvik下DexClassLoader加载dex源码流程分析

在libcore中查找DexClassLoader函数

- 可以看到它继承自BaseDexClassLoader(),这个函数里面只有一个构造函数没有太多的内容。接下来继续跟进BaseDexClassLoader()看一下

- 这个函数功能比较简单,将我们当前的ClassLoader的父节点设置为我们传入的parent,之后new了一个DexPathList()进行初始化,那就跟进去看它传递的参数是什么

DexPathList()的第一个参数是ClassLoader第二个才是我们要加载的DEX文件路径,之后的一些if判断对参数进行了一些校验,可以看到下面的makeDexElements()传入了dexPath,我们继续跟踪这个函数看一下
进来之后可以看到它的返回值是一个Element[]数组,在FART当中也会对Element这个匿名内部类有一定涉及,它也在DexPathList类当中进行了定义
从定义中可以看到它的参数中有包含DexFile对象,接下来还回过头去看makeDexElements()

- 可以看到这里面就调用了LoadDexFile(),在调用完之后再将这个DEX添加到Elements[]数组当中,接下来我们跟进LoadDexFile()去看一下

- 在LoadDexFile当中再次调用了DexFile类当中的loadDex(传入了三个参数),跟进DexFile类看一下loadDex()

- 传入的第一个参数是我们跟的dex路径,DexFile的返回值是new了一个DexFile的对象,继续跟进去看一下

- 在这里面又调用了openDexFile()来先对DEX进行处理,然后返回mCookie。我们继续跟进看一下

- 到这里我们看里面调用函数的名字就知道它进入了Native层来处理,是使用C/C++来实现的,再往下等后面学习NDK开发的时候还会涉及到这些。
查找CPP源码
第一种方法
我们在Android源码当中检索cpp的时候实际上每一个java类他当中的GNI实现是有一个规律的

- 比如说我们当前的类在,dalvik/system/DexFile.java中,那么它对应的cpp实现就在dalvik_system_DexFile中,就是把路径用下划线分割的CPP文件名

- 我们就可以在dalvik下面找到这个cpp文件,要打开这个文件我们只需要去这个路径下面去就可以了

第二种方法
直接在dalvik()中检索这个函数名

找到了CPP
进来这个函数之后

- 进来之后会先对当前文件进行一个校验判断是否是**.dex**后缀,校验通过了再进入dvmRawDexFileOpen()进行加载。跟进去看一下

- 进来之后这个函数比较长,它先执行了一些魔术头校验、设置文件优化后路径等操作,我们先继续跟进看一下这个文件优化dvmOptimizeDexFile()

这个函数也比较长,但是它的主要作用就是调用**/bin/dexopt生成优化后的odex**文件然后返回,下面我们去分析一下dexopt的源码
- 寻找dexopt


- 进来之后main()函数会先看我们传入的是什么文件,如果是dex文件就执行fromDex()。继续跟进去看一下

- 进来之后先进行了一系列的优化前的准备,准备完成的话就开始调用**dvmContinueOptimization()**方法进行优化。继续跟进

进来之后呢,会将dex文件映射到内存,然后对文件进行一个重写。我们先跟进去看rewriteDex()是怎么重写的
- 进来这个地方内存地址和长度都有了已经可以当作一个脱壳点了,再跟进去看里面还有什么
- 进去之后这里也包含有起始地址和长度
Dalvik下通用脱壳方案
在Dalvik下有很多的通用脱壳教程就是对dexFileParse()、dvmDexFileOpenPartial()这两个函数下断点或者进行hook然后取出第一个和第二个参数
第一个参数就是要dump的起始地址,第二个参数就是要dump的内存区域的长度
不过在这个版本的源码中可以脱壳的地方是非常多的不止这两个地方
比如前面用mmap()将dex文件映射到内存的地方如果对这个映射区域进行dump,就可以把从文件加载的dex给dump出来
还有包括这个地方也有内存中dex的起始地址和大小
另外进去这个rewriteDex()内部,它里面也有可以dex的起始地址和大小都可以dump下来
- 这样看下来可以用的脱壳点还是挺多的,然后尝试手动实现一下
- 实现的方式也有很多种选择:比如可以通过hook的形式(cydia、xposed、frida等等)都可以实现对关键函数的一个hook,这种方式在后面搞xposed框架写插件的时候再尝试
- 这次我们就简单的把它当作C/C++函数在里面添加几行代码把dex文件dump出来,使用这种方式我们需要对系统源码进行一个修改定制
因为这种脱壳方式比较老了对一些变种壳可能也没有很好的作用,下面只写一下代码学习一下原理,就不拿设备实测了
char dexfilepath[100]={0}; int pid=getpid(); sprintf(dexfilepath,"/sdcard/%d_%d_dvmDexFileOpenPartial.dex",len,pid); // 拼接一个保存的路径 //fopen int fd=open(dexfilepath,O_CREAT|O_RDWR,0666); // 因为很多壳都会把fopen()这些标准的C函数给hook掉,所以我们使用open()来打开文件避免使用标准读写文件函数 // 给一个合适的文件读写权限 if(fd>0) { write(fd,addr,len); close(fd); }
下载对应的Android源码,修改脱壳点处的代码重新编译源码刷入设备即可。
- 标题: Dalvik下一代壳的通用解决方案
- 作者: xiaoeryu
- 创建于 : 2023-09-04 00:07:34
- 更新于 : 2023-09-14 09:41:07
- 链接: https://github.com/xiaoeryu/2023/09/04/Dalvik下一代壳的通用解决方案/
- 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。