前言
阅读art源码,思考dex不落地&加密加载的实现,并含有oat过程
android提供的内存加载接口
分析内存加载dex,探索内存加载dex的可行性。
正好,InMemoryDexClassLoader是Android8.0新增的类加载器,继承自BaseDexClassLoader,用于加载内存中的dex文件。
libcore\dalvik\src\main\java\dalvik\system\InMemoryDexClassLoader.java
很直观,传入ByteBuffer数组,用父类创建。注意一个ByteBuffer对象代表了一个字节缓冲区,即一个对象代表一个dex文件
BaseDexClassLoader
libcore\dalvik\src\main\java\dalvik\system\BaseDexClassLoader.java
DexPathList
libcore\dalvik\src\main\java\dalvik\system\DexPathList.java
和普通的创建方式类似,创建节点数组,代表文件和目录
makeInMemoryDexElements
dalvik\src\main\java\dalvik\system\DexPathList.java
和普通方式相同,创建DexFile对象,表示加载到内存的dex文件,elements表示一个节点。
DexFile
libcore\dalvik\src\main\java\dalvik\system\DexFile.java
调用native方法生成内存dex对象。参数:
- byte[],dex文件的字节数组
- int型,表示此缓冲区的位置(该位置可以设置)
- int,此缓冲区的限制
仔细一想….这个ByteBuffer不就是曾经研究javaNio的时候看的缓冲区类么……
position limit capacity分别代表读写当前位置、读限制、写限制
也就是说传入的就是byte[]数组,它的当前读取的位置,可读区的大小
接下来进入native层
art\runtime\native\dalvik_system_DexFile.cc
分配MemoryMap内存,通过jni拷贝dex进去,之后执行CreateSingleDexFileCookie
CreateSingleDexFileCookie
art\runtime\native\dalvik_system_DexFile.cc
调用了CreateDexFile去创建一个DexFile,之后调用和普通里一样的ConvertDexFilesToJavaArray方法返回jlongArray对象。注意oat_file为null。
可见返回的mCookie其实就是一个oat和多个dex的指针,但内存加载失去了oat
CreateDexFile
art\runtime\native\dalvik_system_DexFile.cc
调用open返回DexFile对象
Open
art\runtime\dex_file.cc
OpenCommon
art\runtime\dex_file.cc
调用DexFile的构造函数
DexFile
art\runtime\dex_file.cc
通过dex文件格式对其内存地址各项赋值
这里注意oat_dex_file传的是null,而在dexclassloader中,先经过dex2oat,再从磁盘中获取oat文件,之后从内存中的oat文件中获取dexfile。最后返回包装后的oat与dex文件
不落地加载参考
一直想解决怎么优化oat的问题….有个比较好的实现:https://github.com/xiaobaiyey/dexload
看了git上高星的:https://github.com/asLody/TurboDex
hook execv,发现dex2oat直接退出。可见fork的代码段是共享的。在fork前修可以影响子,fork后修改会拷贝副本修改。
调用fork之后,数据、堆、栈有两份,代码仍然为一份但是这个代码段成为两个进程的共享代码段都从fork函数中返回,箭头表示各自的执行处。当父子进程有一个想要修改数据或者堆栈时,两个进程真正分裂。
因此对dex2oat的过程也可以hook运行库去进行加密。
自己的实现
没有相关文章了,这部分必须自己看art加载dex源码理解透彻才能写。
我注释的art源码:
https://github.com/imbaya2466/art_read
项目地址:
https://github.com/imbaya2466/apksec
按文件偏移hook太不稳定了,应该从符号表、got。这样hook稳定点。
使用dlopen、dlsym来使用系统so的函数。
hook使用我的一次性hook正好,很稳定而且自动解除。
必须加载到内存再解密,因为读写大文件太费时了,不能在每次启动时进行。因此需要dex2oat的部分实现才可加密odex、vdex
杂记:
- 签名中的类名要以L开头以;结尾
- 保存全局对象在c++中要记得提升为全局,防止虚拟机delete,出错的话看报错的对象标号,debug找标号即可
- 模板的签名不用加<>如List< int>就用List就行,使用E的地方为Object。使用javap可以查看
- 构造函数的名字是
- dlopen返回的句柄是soinfo*
- native下log报错信息少时可以hook看栈的信息,存了函数调用路径
- xhook的plthook还挺好用的
- 用inline hook,hook mmap回调会有问题,猜测原因为mmalloc时使用的mmap有自己的处理
生成加密oat文件
比较容易被dump…但运行速度可以,之后结合dexvmp不太容易。
暂时先不动dex2oat进程。在主进程执行Dex2Oat方法时hook将文件解密,执行完后将dex加密,并加密odex与vdex。
在OatFileBase::OpenOatFile时解密odex与vdex,之后再加密
360的实现就是将加密的dex从内存放到磁盘,再进行dex2oat时解密加密-最好是到内存在解密-dlopen与mmap之后。
vdex是8.0新加的
google描述:
白名单:/system/etc/public.libraries.txt表示开放的so
注意没有art.so因此加载会失败。绕过so的限制最好就是自己实现dlopen与dlsym。
直接加载加密dex文件
容易被dump,需要dex函数运行时自解密,影响运行效率。如果实现了dexvmp这样直接使用dex效率可能更好点。
内存加载
8.0以上使用InMemoryDexClassLoader,hook解密。内存加载
reateCookieWithArray拷贝时是加密的。
DexFile::Open时解密map中内容。或者DexFile::OpenCommon时也可以,只要保证使用其中数据前解密即可。
落地加密
5.0以上使用hook阻止art生成oat。非内存加载。落地加密
hook阻止oat生成。
在DexFile::Open中获取句柄前解密即可。
DexFile::Open执行结束加密。