本文取自《深入理解android java虚拟机art》第10章,在读书过程中总结的一些能让自己豁然开朗的点,通过这些点我发现了hook java函数的方法,有了此文。
LinkCode函数当中有个重要的过程,就是GetEntryPointFromQuickCompiledCode函数,他的返回值也就是quick_code为空的话代表该方法没有进行编译,在oat文件当中没有对应的汇编代码,需要进入解释执行模式而不是jit,这里有一个分支,如果ArtMethod是jni方法的话会将类似于elf文件的ENTRYPOINT 设置为art_quick_generic_jni_trampoline,普通方法会设置为art_quick_to_interpreter_bridge,如下表(取自该书565页),这里是关键代表着我们可以通过修改这里来完成java或jni函数的跳转,也就是每次art虚拟机解释执行某个java函数的时候,都会跳转到该java函数的EntryPoint,那么只要把这里替换成我们自己的c函数的地址,就可以实现拦截java函数了,当然也要保留原函数的 执行过程。
了解了java函数大致的执行流程,下一补就是实操了,那么怎么更改一个java函数的EntryPoint呢?这是一个困难的问题,首先提出一点
1:是否可以通过hook关键art函数的方式来获得ArtMethod?
当然可以,但是我们要hook的是自身的dex,它的加载时机要早于我们的so加载进内存中,所以像LinkCode或者LoadMethod这种在dex加载流程上的函数是不行的,那就需要找一个系统函数,既有我们的函数的ArtMethod,又是在dex加载完成之后才进行的,这个就很难办了,首先通过函数名过滤ArtMethod就很难,其次dex加载完成之后也很难找到覆盖所有ArtMethod的函数,所以这种方法不可行
2:是否可以像frida一样Java choose内存搜索ArtMethod?
倒是可以通过java.lang.reflect.Method这个类来进行内存漫游,直接把Method类的实例全找出来,高版本可以再通过它父类的方法getArtMethod将它转化为ArtMethod的指针,但是内存漫游太难实现,可能一会会参考frida实现一下,目前不考虑,所以这种也不行
3:最终方案
最终的方案其实和frida差不多,回顾一下frida如何hook的java函数,先Java.use拿到类,然后用implementation直接覆盖原函数,那么我们也可以参考jni反射执行java函数的思路,通过FindClass拿到类,再通过GetMethodID的方式拿到一个jmethodID
那么已经拿到jmethodID了,要如何转化为ArtMethod呢,这一点可以通过安卓源码来查看在http://androidxref.com/8.1.0_r33/xref/art/runtime/jni_internal.h
目录,可以看到,它只是做了一个强制类型转换,那么就相当于我们拿到了我们想hook函数对应的ArtMethod
现在把他的EntryPoint改成我们想要的函数就好了,那么就又产生了2个问题
1.EntryPoint在哪我如何能找到它
庆幸的是安卓源码中有提供函数,也就是上面提到的GetEntryPointFromQuickCompiledCode那么只要通过ida查看它的实现就好了,遗憾的是这是一个inline函数,没有办法使用ida查看,只能通过自己修改源码,来查看偏移了,然后直接脱出libart.so,查看导出函数getmynative就好了
这样就知道了,偏移是40,那么直接更改就好了
2.直接改会少很多系统函数的调用能否成功
这个从理论上来说,是一定不成功的,看书上后面还有许多的过程,包括参数、栈的保存,都没有做,一定不可能成功,但是也不妨试一下,没准歪打正着了呢,第一版代码如下
居然没拉稀,和之前猜的不太一样,虽然成功执行了我们自己的函数,但是要思考一个问题就是如何执行原函数,这里如果通过反射直接执行的话,我们拿不到参数所以这种方案有点垃圾,我们还需要继续改进
既然上面的方案有瑕疵,就是有很多函数没有执行,导致我们拿不到参数,那么可以采取下一个,就是模仿Native函数的方式,将本函数改成Native函数,然后再使用将Native函数注册就好了,那么根据上面第一节的内容只要将EntryPoint改成art_quick_generic_jni_trampoline,就好了,再次用ida打开libart.so,发现art_quick_generic_jni_trampoline是一个函数,但它不是一个导出函数,使用导出表没有办法找到它,那么就用上篇文章提到的节头表索引发来搞定,最后再用RegisterNatives注册函数地址就好了
这样就完成了Java函数的Native化,那么试验一下好不好使,果然不行提示不是native函数无法完成注册,那么就看一下如何的判断是否是Native函数,之前编译rom的时候我就加入了,Native相关的判断,可以看到,公式(~*(_DWORD *)(a1 + 4) & 0x80100)
,就是判断是否是native函数的,只要它不等0,那么就可以认为我们的函数是Native函数.
那么我们就可以构造了,直接做异或就好了
这样准备就完成了,我们就可以像动态注册的jni函数一样,去实现我们的c层的add函数
,执行就正常了,这里不给大家贴图了,因为还有最后一个问题没解决,就是如何执行原函数。
c调用java函数只有一种方式,那就是反射,所以我们只能用反射,那么就要收集参数了,比如我下面的这个函数,我需要一个实例,一个env,2个参数
幸运的是,我们是动态注册的,动态注册的实例函数会自带env和this实例,所以我们直接调用就好了
直接死循环爆栈了........,太垃圾了,还是思路没找好,想想也是,我没有将函数恢复就直接反射,肯定会再次调用的,所以要想一个办法将ArtMethod恢复为当初java函数的样子,这里我又用了全局变量(没错就是inlinehook 时候坑了我那么久的东西,所以一会还要解决多函数的hook问题)
这里方案是这样的,将hook函数中保存的jump和nativ,直接复原,但是这里我没想好怎么搞,所以baocun函数要一个参数,就是ArtMethod指针,保存完还要回复到执行Native函数的状态
这样就很完美了,不管我怎么hook都没有问题了,也不好奔溃
但是当hook多个函数的时候就有问题了,我用的是全局变量,第二次hook的时候会覆盖第一次的baocun()和huifu()函数,所以我又采用了数组的方式来保存,这部分代码很简单就直接贴了
这样一个java层的hook框架就完成了,开始写的时候感觉比inlinehook难,完成之后发现代码量好少,原理就是从书上得到的,比较简单,主要获得ArtMethod不需要hook就节省了许多,jni方法太好用了
var MainActivity
=
Java.use(
"xxxxx"
);
MainActivity.xxxxx.implementation
=
function(){
return
xxxx;
}
var MainActivity
=
Java.use(
"xxxxx"
);
MainActivity.xxxxx.implementation
=
function(){
return
xxxx;
}
static inline ArtMethod
*
DecodeArtMethod(jmethodID method_id) {
return
reinterpret_cast<ArtMethod
*
>(method_id);
}
static inline ArtMethod
*
DecodeArtMethod(jmethodID method_id) {
return
reinterpret_cast<ArtMethod
*
>(method_id);
}
/
/
ArtMethod.cc
extern
"C"
const void
*
getmynative(ArtMethod
*
m)REQUIRES_SHARED(Locks::mutator_lock_){
m
-
>SetEntryPointFromQuickCompiledCode(GetQuickGenericJniStub());
m
-
>UnregisterNative();
const void
*
entry_point
=
m
-
>GetEntryPointFromQuickCompiledCode();
if
(m
-
>IsFastNative())
return
GetQuickGenericJniStub();
return
entry_point;
}
/
/
ArtMethod.cc
extern
"C"
const void
*
getmynative(ArtMethod
*
m)REQUIRES_SHARED(Locks::mutator_lock_){
m
-
>SetEntryPointFromQuickCompiledCode(GetQuickGenericJniStub());
m
-
>UnregisterNative();
const void
*
entry_point
=
m
-
>GetEntryPointFromQuickCompiledCode();
if
(m
-
>IsFastNative())
return
GetQuickGenericJniStub();
return
entry_point;
}
void
*
func(void
*
a,void
*
b,
int
c,
int
d){
__android_log_print(
6
,
"r0ysue"
,
"i am success"
);
return
reinterpret_cast<void
*
>(
5
);
}
jclass a
=
env
-
>FindClass(classname);
jmethodID b
=
env
-
>GetMethodID(a,methodname, shoty);
*
((
long
*
)a1
+
5
)
=
reinterpret_cast<
long
>(func);
void
*
func(void
*
a,void
*
b,
int
c,
int
d){
__android_log_print(
6
,
"r0ysue"
,
"i am success"
);
return
reinterpret_cast<void
*
>(
5
);
}
jclass a
=
env
-
>FindClass(classname);
jmethodID b
=
env
-
>GetMethodID(a,methodname, shoty);
*
((
long
*
)a1
+
5
)
=
reinterpret_cast<
long
>(func);
int
so
=
findsym(
"/system/lib64/libart.so"
,
"art_quick_generic_jni_trampoline"
);
*
((
long
*
)a1
+
5
)
=
reinterpret_cast<
long
>((char
*
) startr
+
so
-
0x25000
);
/
/
需要
-
0x25000
是因为libart.so的程序头在偏移为
0x25000
这里没遍历偷懒了
env
-
>RegisterNatives(a,getMethods,
1
);
int
so
=
findsym(
"/system/lib64/libart.so"
,
"art_quick_generic_jni_trampoline"
);
*
((
long
*
)a1
+
5
)
=
reinterpret_cast<
long
>((char
*
) startr
+
so
-
0x25000
);
/
/
需要
-
0x25000
是因为libart.so的程序头在偏移为
0x25000
这里没遍历偷懒了
env
-
>RegisterNatives(a,getMethods,
1
);
*
(_QWORD
*
)(a1
+
4
)
=
*
(_QWORD
*
)(a1
+
4
)^
0x80100
;
*
(_QWORD
*
)(a1
+
4
)
=
*
(_QWORD
*
)(a1
+
4
)^
0x80100
;
public
int
add2(
int
a,
int
b){
return
7
;
}
public
int
add2(
int
a,
int
b){
return
7
;
}
void
*
add(void
*
a,void
*
b,
int
c,
int
d){
JNIEnv
*
st
=
(JNIEnv
*
)a;
jclass a2
=
st
-
>FindClass(
"com/r0ysue/myjavahook/MainActivity"
);
jmethodID b2
=
st
-
>GetMethodID(a2,
"add2"
,
"(II)I"
);
int
yy
=
st
-
>CallIntMethod(static_cast<jobject>(b), b2, c, d);
__android_log_print(
6
,
"r0ysue"
,
"%x"
,yy);
return
reinterpret_cast<void
*
>(
5
);
}
void
*
add(void
*
a,void
*
b,
int
c,
int
d){
JNIEnv
*
st
=
(JNIEnv
*
)a;
jclass a2
=
st
-
>FindClass(
"com/r0ysue/myjavahook/MainActivity"
);
jmethodID b2
=
st
-
>GetMethodID(a2,
"add2"
,
"(II)I"
);
int
yy
=
st
-
>CallIntMethod(static_cast<jobject>(b), b2, c, d);
__android_log_print(
6
,
"r0ysue"
,
"%x"
,yy);
return
reinterpret_cast<void
*
>(
5
);
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)