在art中有大量的inline内联函数,就先来了解什么是inline
什么是inline
inline: 内联函数是一种优化技术,可以减少函数调用的开销,渐少参数压栈时消耗空间,从而提高代码的执行效率。使用inline关键字声明的函数在编译时会被内联展开,即编译器会在每个调用该函数的地方插入该函数的实际代码,而不是生成函数调用。关键字inline必须与函数定于放在一起才能使函数成为内联函数,仅仅将inline放在函数声明前面是不起任何作用。inline对编译器来水只是一种建议,编译器可以选择忽略这个建议,因此有inline不一定就会被编译器内联编译。
ALWAYS_INLINE: 是强制内联,所有加 inline __attribute__((always_inline)) 修饰的函数在被调用的时候不会被编译成函数调用,而是直接扩展到调用函数体内。当含有递归的函数在任何情况下都不会被编译器进行inline 编译。
1 | __attribute__((always_inline)) int add(int a, int b) { |
在加与不加__attribute__((always_inline))的情况下,编译后的内容如下
1 | __int64 __fastcall sum(int a1) |
加了__attribute__((always_inline)) 后的sum函数
1 | __int64 __fastcall sum(int a1) |
少了 BL add,相比于原来的,汇编指令减少了,也省去了函数调用时栈空间的花销。
总结:
- 内联函数在编译器最终生成的代码中是没有定义的,这个函数是不存在的,也就无法实现对内联函数的hook。
- 内联函数没有普通函数调用时的额外开销(压栈,跳转,返回等)
- 内联函数是一种特殊的函数,在源码中具有普通函数的特征。
- 内联函数是对编译器的一种请求,因此编译器有可能拒绝这种请求。
- 内联函数由编译器处理,直接将编译后的函数插入调用的地方。
- 内联和宏的效果很像,但是宏代码是由预处理器处理,进行简单的文本替换,没有任何编译过程
基于frida版的art
上一篇写了art下DexFile的内存布局,那如何拿到DexFile的对象呢?通过查看源码发现在ART中获取dexfile对象的函数是个inline,那又要怎么去调用呢
1 | inline const DexFile* ArtMethod::GetDexFile() { |
当在源码中进行定制ART时,任何inline函数都可以直接访问,只需要注意访问权限即可,那如果是使用frida等hook技术进行ART的定制呢?自然inline函数无法被hook,那当需要这个inline函数的功能时,如何实现inline函数的主动调用呢?两种解决方案:1. 分析inline函数的源码逻辑,自行参考实现即可2. 在源码中添加一个导出函数调用该inline函数,编译后直接参考ida反编译的该函数内容即可实现第二种接下来解决从一个artmethod对象得到一个该对象所属的dexfile对象。在源码 /art/runtime/art_method-inl.h 添加如下代码导出dexfile对象
1 | extern "C" const DexFile* getDexFileByMethod(ArtMethod* artmethod) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { |
重新编译,找到编译好后lib32,lib64下的libart.so,ida打开找到添加的函数片段,在nexus 5 android6和pixel android8下
1 | // nexus5 arm32 |
实际是artmethod对象获取dexfile对象inline函数的内联编译生成的片段,现在参考ida反编译的该函数,根据自己的安卓系统版本做调整
1 |
|
打包解压,把lib目录下的so推送到手机上,修改777权限编写frida脚本
- 先加载导入的so,枚举该so的导出函数,找到编写的GetDexFile地址
- 如果是安卓8及以上,需要枚举libart.so的符号表,找到GetObsoleteDexCache的地址
- 声明GetDexFile函数
- 通过反射获取任意类的java层函数,通过$handle获取函数的引用,再由jni的函数fromReflectedMethod转为artmethod对象
- 将参数传入声明的GetDexFile函数得到dexfile对象
- 根据art中的DexFile内存布局dump内容打印内容如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64function getDexFileByMethod(){
Java.perform(function(){
console.log("go into init," + "Process.arch:" + Process.arch);
var module_libext = null;
// 加载导入的so
if (Process.arch == "arm64") {
module_libext = Module.load("/data/local/tmp/libmy_64.so");
} else if (Process.arch == "arm") {
module_libext = Module.load("/data/local/tmp/libmy.so");
}
var getDexFileByMethodFunc = null;
var getDexFileByMethodAddr = null;
if (module_libext != null) {
module_libext.enumerateExports().forEach(function(symbol){
// 找到编写的GetDexFile函数
if (symbol.name.indexOf("GetDexFile") != -1) {
console.log(JSON.stringify(symbol))
getDexFileByMethodAddr = symbol.address;
}
})
}
if (getDexFileByMethodAddr != null) {
getDexFileByMethodFunc = new NativeFunction(getDexFileByMethodAddr, "pointer", ["pointer"]);
// 第一个参数是artmethod,第二个参数是GetObsoleteDexCache
// getDexFileByMethodFunc = new NativeFunction(getDexFileByMethodAddr, "pointer", ["pointer", "pointer"]);
}
// 找到GetObsoleteDexCache,安卓8及以上才需要
// var GetObsoleteDexCacheAddr = null;
// var libartmodule = Process.getModuleByName("libart.so");
// libartmodule.enumerateSymbols().forEach(function(symbol){
// if (symbol.name.indexOf("GetObsoleteDexCache") != -1) {
// console.log(JSON.stringify(symbol))
// GetObsoleteDexCacheAddr = symbol.address;
// }
// })
// 通过反射获取任意一个java层函数, 再将函数转为artmethod对象
var MainActivity = Java.use("com.example.zsk.MainActivity");
var methods = MainActivity.class.getDeclaredMethods(); // 枚举所有函数
methods.forEach(function (method) {
// console.log(method);
var methodhandle = method.$handle; // 获取函数引用
console.log(methodhandle)
var ArtMethodPtr = Java.vm.tryGetEnv().fromReflectedMethod(methodhandle) // 将java函数转为artmethod对象
if (getDexFileByMethodFunc != null) {
var dexfilePtr = getDexFileByMethodFunc(ArtMethodPtr);
// var dexfilePtr = getDexFileByMethodFunc(ArtMethodPtr, GetObsoleteDexCacheAddr);
console.log(method.toString() + "------" + ArtMethodPtr + "------" + dexfilePtr);
var dexfileBegin = ptr(dexfilePtr).add(Process.pointerSize * 1).readPointer();
var 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
}));
}
})
})
}
function main(){
getDexFileByMethod();
}
setImmediate(main);1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16go into init,Process.arch:arm
{"type":"function","name":"GetDexFile","address":"0x9cb9d471"}
0x1004c2
protected void com.example.zsk.MainActivity.onCreate(android.os.Bundle)------0xb0697cf0------0xab0da3c0
get a dex:size:2100--- 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
9fc11078 64 65 78 0a 30 33 35 00 ee 74 d8 e6 95 2a 0a 65 dex.035..t...*.e
go into LoadMethodaddr-> 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
ab0da3c0 e8 3e c3 b4 78 10 c1 9f 34 08 00 00 41 00 00 00 .>..x...4...A...
ab0da3d0 32 00 00 00 40 b2 53 ab e5 9a 18 6c 00 00 00 00 2...@.S....l....
0x1004de
public native java.lang.String com.example.zsk.MainActivity.stringFromJNI()------0xb0697d18------0xab0da3c0
get a dex:size:2100--- 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
9fc11078 64 65 78 0a 30 33 35 00 ee 74 d8 e6 95 2a 0a 65 dex.035..t...*.e
go into LoadMethodaddr-> 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
ab0da3c0 e8 3e c3 b4 78 10 c1 9f 34 08 00 00 41 00 00 00 .>..x...4...A...
ab0da3d0 32 00 00 00 40 b2 53 ab e5 9a 18 6c 00 00 00 00 2...@.S....l....