ART由C++11实现的,C++11中的类所占内存的大小主要是由成员变量(静态变量除外)决定的,成员函数(虚函数除外)是不计算在内的。成员函数的存储是以一般函数的模式进行存储。a.fun()是通过fun(a.this)来调用的,这时候this指针会做为隐藏的第一个参数传入成员函数,this指针的地址就是对象的地址。所谓成员函数只是名义上是在类里的。而成员函数的大小并不在类的对象里面,即同一个类的多个对象共享函数代码,因此可以简单将C++中的类当成C中的结构体即可。对象和成员函数的联系是靠:this指针,也是连接对象与其成员函数的唯一桥梁。
简单总结:
空的类是会占用内存空间的,而且大小是1,原因是c++要求每个实例在内存中都有独一无二的地址。
- 类内部的成员变量:
普通的变量:是要占用内存的,但是要注意对齐原则(这点和struct类型很相似)。
static修饰的静态变量:不占用内存,原因是编辑器将其放在全局变量区。 - 类内部的成员函数:
普通函数:不占用内存
虚函数:要占用4个以上字节,用来指定虚函数的虚函数表的入口地址。所以一个类的虚函数所占用的大小是不变的,和虚函数的个数是没有关系的。 32位下占4字节,64位占8字节。 - 多重继承,函数函数覆盖
考虑下面情况,三个父类虚函数表中的f()的位置被替换成子类的函数指针。这样就可以任一父类对象指针来指向子类,并调用子类的f()了。也会导致虚函数表的指针增加,继承几个就会增加几个。
假设,基类和派生类又如下关系:派生类i中覆盖了基类的虚函数f
art中的内存布局
将/art/runtime/dex_file.h下的DexFile类拷贝出来,除去属性外其余都删掉
在脱壳过程中拿到DexFile对象后,只需要知道它的begin和size的偏移
删除后的DexFile精简代码如下
1 | class DexFile { |
进行调用,分别以32和64位运行
1 | extern "C" JNIEXPORT jstring JNICALL |
输出日志:arm32 com.example.cpp6 I/cpp11: beginOffset: 4, sizeOffset: 8arm64 com.example.cpp6 I/cpp11: beginOffset: 8, sizeOffset: 16去不去掉前面的static属性,都对内存布局没影响,对于脱壳而言,内存布局从前完后,而我们之关注begin_,size_。可以再把上面的DexFile类精简为结构体
1 | struct DexFileStruct{ |
先测试一下,在libart.so中随便找一个函数参数有DexFile的,以 art::ClassLinker::LoadMethod(art::DexFile const&, art::ClassDataItemIterator const&, art::Handleart::mirror::Class, art::ArtMethod *) 为例,使用frida进行hook
1 | function hookart(){ |
1 | get a dex:size:730868--- 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF |
可以看到dex的模值dex 037,也验证了dex的内存布局
在函数粒度的修复与脱壳中,还需要关注ArtMethod
将/art/runtime/art_method.h下的ArtMethod类拷贝出来,除去属性外其余都删掉
删除后的ArtMethod精简代码如下
1 | class ArtMethod { |
抽取壳的过程主要关注两个字段,dex_code_item_offset_和dex_method_index_
脱壳过程中的应用在脱壳过程中,重点是从内存中提取出被保护或加密的DEX文件,或从已经加载到内存中的DEX文件中提取出方法字节码。这时,dex_code_item_offset_和dex_method_index_就起到了关键作用:
- 使用dex_code_item_offset_加上DEX文件基址,可以计算出方法字节码在内存中的实际地址。
- 通过dex_method_index_,可以找到该方法在DEX文件中的定义,从而获得方法的名称、所属类及其签名等信息。
ArtMethod是没有虚函数的,所以在32位或64位下dex_code_item_offset_和dex_method_index_的偏移都是8, 12
修改frida代码日志:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21if (LoadMethodaddr != null) {
console.log("start hook LoadMethodaddr");
Interceptor.attach(LoadMethodaddr, {
onEnter: function (args) {
var dexfileptr = args[1];
this.artmethodptr = args[4];
this.dexfilebegin = ptr(dexfileptr).add(Process.pointerSize * 1).readPointer();
this.dexfilesize = ptr(dexfileptr).add(Process.pointerSize * 2).readU32();
// console.warn("get a dex:size:" + dexfilesize + "---" + hexdump(dexfilebegin, {
// length: 16
// }))
// console.log("go into LoadMethodaddr->" + hexdump(dexfileptr, {
// length: 32
// }));
}, onLeave: function () {
var dex_code_item_offset = ptr(this.artmethodptr).add(8).readU32(); // 在dex文件中函数的偏移, dexfilebegin+偏移就是函数的绝对地址
var dex_method_index = ptr(this.artmethodptr).add(12).readU32(); // 在dex文件中的方法索引
console.log(this.dexfilesize + "-->LoadMethodaddr index:" + dex_method_index, "-->" + this.dexfilebegin.add(dex_code_item_offset));
}
})
}对app解压,也能看到dex的大小也是3926232,把dex放进010editor查看函数的执行顺序1
2
3
4
5
6
7
8
9
10_ZN3art11ClassLinker10LoadMethodERKNS_7DexFileERKNS_21ClassDataItemIteratorENS_6HandleINS_6mirror5ClassEEEPNS_9ArtMethodE 0x78ccc2868c
start hook LoadMethodaddr
3926232-->LoadMethodaddr index:29945 -->0x78984d1264
3926232-->LoadMethodaddr index:29946 -->0x7898686a3c
3926232-->LoadMethodaddr index:29949 -->0x7898686a54
3926232-->LoadMethodaddr index:29950 -->0x7898686bd0
3926232-->LoadMethodaddr index:29952 -->0x7898686e10
3926232-->LoadMethodaddr index:29947 -->0x7898687000
3926232-->LoadMethodaddr index:29948 -->0x789868711c
...1
2
3
4struct method_id_item method_id[29945] void s.h.e.l.l.S.<clinit>() 8A5E0h 8h Fg: Bg:0x008080 Method ID
struct method_id_item method_id[29946] void s.h.e.l.l.S.<init>() 8A5E8h 8h Fg: Bg:0x008080 Method ID
struct method_id_item method_id[29949] void s.h.e.l.l.S.c(java.util.zip.ZipFile, java.util.zip.ZipEntry, java.io.File) 8A600h 8h Fg: Bg:0x008080 Method ID
struct method_id_item method_id[29950] long s.h.e.l.l.S.g(java.io.File) 8A608h 8h Fg: Bg:0x008080 Method ID
打开apk的AndroidManifest.xml,跟artmethod打印的顺序是一致的,”s.h.e.l.l.S”是程序的入口
1 | <application android:theme="0x7f090002" android:label="0x7f080000" android:icon="0x7f020029" android:name="s.h.e.l.l.S" android:debuggable="true" android:allowBackup="true" android:largeHeap="true"> |
最先执行的两个函数分别是初始化类和对象,
当类加载时,JVM会解析类的字节码,并为每个方法生成对应的 ArtMethod 实例。这个过程确保了Java函数和 ArtMethod 是一一对应的关系。每个Java方法都有一个与之对应的 ArtMethod 对象,它负责存储和管理该方法的信息。
可以说Java函数和 ArtMethod 是一一对应的关系,它们之间的对应关系是固定的,且在类加载和初始化过程中被建立