android jni 注册函数,使用Frida分析动态注册jni函数绑定流程

原标题:使用Frida分析动态注册jni函数绑定流程

e4fc777ff3d998c5d93e53808d94ba45.png

本文为看雪论坛文章

看雪论坛作者ID:无造

本文为 看雪安卓高研3w班(7月班)优秀学员作品。

下面先让我们来看看讲师对学员学习成果的点评,以及学员的学习心得吧!

讲师点评

在对app进行逆向分析过程中,jni函数往往是非常关键的。不管是jni函数地址的绑定,还是jni函数的调用和执行过程,都离不开ART的支持。

得益于Android的开源,可以通过对源码进行阅读,一窥jni函数的注册原理,自然也就知道了静态注册的jni函数为什么必须按照固定的格式来定义函数名,同时又必须导出;而对于动态注册的jni函数的最终绑定的地址,也可以很容易定位。

文章使用frida来对jni函数注册的整个流程进行了深入研究,可见已经彻底掌握了jni函数的整个注册原理和流程。

学员感想

这是3W班7月份的练习题第一题。题目要求是分析总结动态注册jni函数时的流程,并编写frida脚本,达到能够跟踪测试apk中动态注册的jni函数的绑定流程的目标。

通过查看源码,使用Frida验证了部分绑定流程,加深了对之前使用的hook RegisterNative脚本原理的理解。

ps. 题目附件请点击“阅读原文”下载。

好消息!!

现在看雪《安卓高级研修班》线下班 & 网课(12月班)开始同步招生啦!

以前没报上高研班的小伙伴赶快抓紧机会报名,升职加薪唾手可得!!

解题过程

按照hanbing老师的讲解,native函数有2次绑定,这里分别查看绑定。

第一次绑定

Jni注册Demo

自己写Demo方便测试:

publicnativeString stringFromJNI;

publicnativeString stringFromJNI2;

publicnativevoidRegisterFunc;

publicnativevoidFunc;

extern"C"JNIEXPORT jstring JNICALL

Java_com_cwuzao_nativedemo_MainActivity_stringFromJNI(

JNIEnv *env,

jobject /* this */){

std:: stringhello = "Hello from C++";

returnenv->NewStringUTF(hello.c_str);

}

extern"C"JNIEXPORT jstring JNICALL

Java_com_cwuzao_nativedemo_MainActivity_stringFromJNI2(

JNIEnv *env,

jobject /* this */){

std:: stringhello = "Hello from C++2";

returnenv->NewStringUTF(hello.c_str);

}

voidF(JNIEnv *env, jobject thiz){

__android_log_print( 4, "nativedemo", "Call Func");

}

__attribute__ ((visibility ( "hidden"))) voidRF(JNIEnv *env, jobject thiz){

//注册另外一个函数

__android_log_print( 4, "nativedemo", "Call RegisterFunc New");

JNINativeMethod jniNativeMethod[] = {{ "Func", "V", ( void*) F}};

jclass MainActivityjclass = env->FindClass( "com/cwuzao/nativedemo/MainActivity");

env->RegisterNatives(MainActivityjclass, jniNativeMethod,

sizeof(jniNativeMethod) / sizeof(JNINativeMethod));

}

JNIEXPORT jint JNI_(JavaVM *vm, void*reserved){

JNIEnv *env = nullptr;

if(vm->GetEnv(( void**) &env, JNI_VERSION_1_6) == JNI_OK) {

__android_log_print( 4, "nativedemo", "jni->%s",

"vm->GetEnv((void**)&env,JNI_VERSION_1_6) success");

}

JNINativeMethod jniNativeMethod[] = {{ "RegisterFunc", "V", ( void*) RF}};

jclass MainActivityjclass = env->FindClass( "com/cwuzao/nativedemo/MainActivity");

env->RegisterNatives(MainActivityjclass, jniNativeMethod, sizeof(jniNativeMethod) / sizeof(JNINativeMethod));

returnJNI_VERSION_1_6;

}

初始化ArtMethod的安卓源码

从ClassLinker::DefineClass->ClassLinker::LoadClass->ClassLinker::LoadClassMembers进入到ArtMethod的初始化。

voidClassLinker::LoadClassMembers {

for( size_ti = 0; it.HasNextDirectMethod; i++, it.Next) {

ArtMethod* method = klass->GetDirectMethodUnchecked(i, image_pointer_size_);

LoadMethod(dex_file, it, klass, method);

LinkCode( this, method, oat_class_ptr, class_def_method_index);

}

这里就是主要看LinkCode方法中的method->IsNative条件。

if(method->IsNative) {

// Unregistering restores the dlsym lookup stub.

method->UnregisterNative;

}

voidArtMethod::UnregisterNative {

CHECK(IsNative && !IsFastNative) << PrettyMethod;

// restore stub to lookup native pointer via dlsym

SetEntryPointFromJni(GetJniDlsymLookupStub);

}

extern"C"void* art_jni_dlsym_lookup_stub(JNIEnv*, jobject);

staticinlineconstvoid* GetJniDlsymLookupStub{

returnreinterpret_cast< constvoid*>(art_jni_dlsym_lookup_stub);

}

ENTRY art_jni_dlsym_lookup_stub

// spill regs.

...

bl artFindNativeMethod

...

END art_jni_dlsym_lookup_stub

extern"C"constvoid* artFindNativeMethod{

ArtMethod* method = self->GetCurrentMethod( nullptr);

void* native_code = soa.Vm->FindCodeForNativeMethod(method);

returnmethod->RegisterNative(native_code, false);

}

} // namespace art

根据上面代码HOOK分析

静态和动态注册的方法都会先进行UnregisterNative;第一次绑定art_jni_dlsym_lookup_stub,最后还是通过RegisterNative注册。

这里就可以先HOOK artFindNativeMethod查看返回值:

varaddr_ArtMethod_UnRegisterNative = libart.findExportByName( "_ZN3art9ArtMethod16UnregisterNativeEv");

Interceptor.attach(addr_ArtMethod_UnRegisterNative, {

onEnter: function( args){

allocPrettyMethod.writeByteArray(allocPrettyMethodInit);

PrettyMethod(addr_ArtMethod_PrettyMethod, args[ 0], allocPrettyMethod, 0x100);

console.log( "addr_ArtMethod_UnRegisterNative onEnter:",allocPrettyMethod.readCString);

},

onLeave: function( retval){

}

});

varaddr_artFindNativeMethod = libart.findExportByName( "artFindNativeMethod");

Interceptor.attach(addr_artFindNativeMethod, {

onEnter: function( args){

},

onLeave: function( retval){

console.log( "addr_artFindNativeMethod onLeave:", retval);

}

});

varaddr_FindCodeForNativeMethod = libart.findExportByName( "_ZN3art9JavaVMExt23FindCodeForNativeMethodEPNS_9ArtMethodE");

Interceptor.attach(addr_FindCodeForNativeMethod, {

onEnter: function( args){

allocPrettyMethod.writeByteArray(allocPrettyMethodInit);

PrettyMethod(addr_ArtMethod_PrettyMethod, args[ 1], allocPrettyMethod, 0x100);

console.log( "addr_FindCodeForNativeMethod onEnter:",allocPrettyMethod.readCString );

},

onLeave: function( retval){

}

});

dlopen: libart.so

libart-> [object Object]

addr_FindCodeForNativeMethodonEnter: java.lang.Stringcom.cwuzao.nativedemo.MainActivity.stringFromJNI

addr_FindSymbol: Java_com_cwuzao_nativedemo_MainActivity_stringFromJNI

addr_artFindNativeMethodonLeave: 0 x7593752c0c

这里可以看到2个静态注册的stringFromJNI调用了绑定,但是stringFromJNI2没有绑定,因为程序中并没有调用stringFromJNI2,这里可以打印一下2个ArtMethod的差别:

addr_ArtMethod_UnRegisterNativeonEnter: void com.cwuzao.nativedemo.MainActivity.Func

addr_ArtMethod_UnRegisterNativeonEnter: void com.cwuzao.nativedemo.MainActivity.RegisterFunc

addr_ArtMethod_UnRegisterNativeonEnter: void com.cwuzao.nativedemo.MainActivity.onCreate(android.os.Bundle)

addr_ArtMethod_UnRegisterNativeonEnter: java.lang.String com.cwuzao.nativedemo.MainActivity.stringFromJNI

addr_ArtMethod_UnRegisterNativeonEnter: java.lang.String com.cwuzao.nativedemo.MainActivity.stringFromJNI2

methodName->public native java.lang.String com.cwuzao.nativedemo.MainActivity.stringFromJNI

Func.getArtMethod->0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF

75ae4c56c020 25 c8 12 01 01 00 08 00 00 00 00 b1 3b 00 00 %...........;..

75ae4c56d010 02 00 00 00 00 00 00 00 80 22 94 75 00 00 00 ..........".u...

75ae4c56e00c 2c 75 93 75 00 00 00 70 96 f1 ac 75 00 00 00 .,u.u...p...u...

methodName->public native java.lang.String com.cwuzao.nativedemo.MainActivity.stringFromJNI2

Func.getArtMethod->0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF

75ae4c56f020 25 c8 12 01 01 00 08 00 00 00 00 b2 3b 00 00 %...........;..

75ae4c570011 02 00 00 00 00 00 00 00 80 22 94 75 00 00 00 ..........".u...

75ae4c571000 f5 f0 ac 75 00 00 00 70 96 f1 ac 75 00 00 00 ....u...p...u...

//调用一次stringFromJNI2

addr_FindCodeForNativeMethodonEnter: java.lang.String com.cwuzao.nativedemo.MainActivity.stringFromJNI2

addr_FindSymbol: Java_com_cwuzao_nativedemo_MainActivity_stringFromJNI2

addr_artFindNativeMethodonLeave: 0x7593752d5c

methodName->public native java.lang.String com.cwuzao.nativedemo.MainActivity.stringFromJNI2

Func.getArtMethod->0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF

75ae4c56f020 25 c8 12 01 01 00 08 00 00 00 00 b2 3b 00 00 %...........;..

75ae4c570011 02 00 00 00 00 00 00 00 80 22 94 75 00 00 00 ..........".u...

75ae4c57105c 2d 75 93 75 00 00 00 70 96 f1 ac 75 00 00 00 -u.u...p...u...

//这ArtMethod在0x20处发生变化,打印调用前后stringFromJNI20x20的地址所在模块

[AOSPon msm8996::com.cwuzao.nativedemo]-> Process.findModuleByAddress(0x75acf0f500)

{

"base": "0x75aca2d000",

"name": "libart.so",

"path": "/system/lib64/libart.so",

"size": 6041600

}

[AOSPon msm8996::com.cwuzao.nativedemo]-> Process.findModuleByAddress(0x7593752d5c)

{

"base": "0x7593744000",

"name": "libnative-lib.so",

"path": "/data/app/com.cwuzao.nativedemo-UgxLVJ25_JE-ydv0Rl_x2A==/lib/arm64/libnative-lib.so",

"size": 212992

}

[AOSPon msm8996::com.cwuzao.nativedemo]-> console.log(hexdump(ptr(0x75acf0f500)))

01 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF

75acf0f500fd 7b bf a9 fd 03 00 91 e6 1f bf 6d e4 17 bf 6d .{.........m...m

75acf0f510e2 0f bf 6d e0 07 bf 6d e6 1f bf a9 e4 17 bf a9 ...m...m........

75acf0f520e2 0f bf a9 e0 07 bf a9 d7 01 ff 97 f1 03 00 aa ................

75acf0f530e0 07 c1 a8 e2 0f c1 a8 e4 17 c1 a8 e6 1f c1 a8 ................

75acf0f540e0 07 c1 6c e2 0f c1 6c e4 17 c1 6c e6 1f c1 6c ...l...l...l...l

75acf0f550fd 7b c1 a8 51 00 00 b4 20 02 1f d6 c0 03 5f d6 .{..Q... ....._.

//0x75acf0f500这段hex可以转成arm汇编就是art_jni_dlsym_lookup_stub

//0x7593752d5c-0x7593744000就是stringFromJNI2的代码位置

addr_FindCodeForNativeMethodonEnter: void com.cwuzao.nativedemo.MainActivity.Func

addr_FindSymbol: Java_com_cwuzao_nativedemo_MainActivity_Func

addr_FindSymbol: Java_com_cwuzao_nativedemo_MainActivity_Func__

addr_FindSymbol: Java_com_cwuzao_nativedemo_MainActivity_Func

addr_FindSymbol: Java_com_cwuzao_nativedemo_MainActivity_Func__

addr_artFindNativeMethodonLeave: 0x0

//运行到这里程序崩溃了

这里可以看出几点:

1. 静态注册和动态注册都会调用UnRegisterNative,并设置EntryPoint(0x20)为art_jni_dlsym_lookup_stub

2. 静态注册首次调用时会运行art_jni_dlsym_lookup_stub,通过addr_FindSymbol查找符号并注册后真正运行jni方法

3. 静态注册其实只是注册了art_jni_dlsym_lookup_stub,通过Java_xxx这种固定的函数名自动绑定,如果查找不到,则会程序崩溃,比如直接调用Func

动态注册的第二次绑定

查看ArtMethod在绑定前后的差别:

Java.perform( function( ){

varclassName = "com.cwuzao.nativedemo.MainActivity";

varclassResult = Java.use(className).class;

varmethodArr = classResult.getDeclaredMethods;

for( vari= 0;i

varmethodName = methodArr[i].toString;

console.log( "methodName->",methodName);

if(methodName.indexOf( ".Func(") > -1){

varartmethod = methodArr[i].getArtMethod;

console.log( "Func.getArtMethod->",hexdump(ptr(artmethod)));

}

}

});

Func.getArtMethod->0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF

9a9918e048 e6 93 9a 01 01 00 00 00 00 00 00 54 3b 00 00 H...........T;..

9a9918f013 02 00 00 00 00 00 00 00 80 b5 6d 7b 00 00 00 ...........m{...

9a99190000 e0 23 70 00 00 00 00 40 44 b5 6d 7b 00 00 00 ..#p....@D.m{...

Func.getArtMethod->0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF

9a9918e048 e6 93 9a 01 01 00 00 00 00 00 00 54 3b 00 00 H...........T;..

9a9918f013 02 00 00 00 00 00 00 00 80 b5 6d 7b 00 00 00 ...........m{...

9a9919007c 44 99 6d 7b 00 00 00 40 44 b5 6d 7b 00 00 00 |D.m{...@D.m{...

和上方静态注册一样,也就是0x20处地址发生变化。从art模块的art_jni_dlsym_lookup_stub换成了自己so文件的地址,下面分析就是修改的流程。

跟踪RegisterNatives

staticjint RegisterNatives(JNIEnv* env, jclass java_class, constJNINativeMethod* methods, jint method_count){

returnRegisterNativeMethods(env, java_class, methods, method_count, true);

}

staticjint RegisterNativeMethods(JNIEnv* env, jclass java_class, constJNINativeMethod* methods,jint method_count, boolreturn_errors){

for(jint i = 0; i < method_count; ++i) {

constvoid* fnPtr = methods[i].fnPtr;

constvoid* final_function_ptr = m->RegisterNative(fnPtr, is_fast);

}

}

constvoid* ArtMethod::RegisterNative( constvoid* native_method, boolis_fast) {

Runtime::Current->GetRuntimeCallbacks->RegisterNativeMethod( this,

native_method,

/*out*/&new_native_method);

SetEntryPointFromJni(new_native_method);

returnnew_native_method;

}

这里和静态注册一样也是走到了SetEntryPointFromJni,查看具体流程:

voidSetEntryPointFromJni( constvoid* entrypoint){

DCHECK(IsNative);

SetEntryPointFromJniPtrSize(entrypoint, kRuntimePointerSize);

}

ALWAYS_INLINE voidSetEntryPointFromJniPtrSize( constvoid* entrypoint, PointerSize pointer_size){

SetDataPtrSize(entrypoint, pointer_size);

}

ALWAYS_INLINE voidSetDataPtrSize( constvoid* data, PointerSize pointer_size){

DCHECK(IsImagePointerSize(pointer_size));

SetNativePointer(DataOffset(pointer_size), data, pointer_size);

}

...

涉及的方法还是比较多,大多数是ALWAYS_INLINE修饰,IDA查不到符号SetEntryPointFromJni,直接查看ArtMethod::RegisterNative末尾:

v25 = (art::RuntimeCallbacks *)art::Runtime::GetRuntimeCallbacks(MEMORY[ 0]);

art::RuntimeCallbacks::RegisterNativeMethod(v25, v5, v7, &v27);

result = v27;

*((_QWORD *)v5 + 4) = v27;

*(_QWORD *)(v4 + 40);

returnresult;

//v27应该就是具体指针,但是这里比较奇怪就是+4,查看汇编

.text: 00000000000DD1B4 BL _ZN3art16RuntimeCallbacks20RegisterNativeMethodEPNS_9ArtMethodEPKvPPv ; art::RuntimeCallbacks::RegisterNativeMethod(art::ArtMethod *,void const*,void **)

.text: 00000000000DD1B8 LDR X0, [SP, #0x70+var_68]

.text: 00000000000DD1BC STR X0, [X19, #0x20] //0x20和上面对应上

.text: 00000000000DD1C0 LDR X8, [X23, #0x28]

.text: 00000000000DD1C4 LDR X9, [SP, #0x70+var_48]

查看libart反编译源码发现这里其实就2步,0x20也和上面对应上了。

SetEntryPointFromJni(new_native_method),其实已经被优化成几句汇编了,主要就是STR X0, [X19,#0x20]。

这里也就验证了之前ArtMethod+0x20是EntryPoint,也可以通过源码计算内存偏移。

测试不通过RegisterNative

设置ArtMethod+0x20值

if(methodName.indexOf( ".Func") > -1){

varartmethod = methodArr[i].getArtMethod;

console.log( "Func.getArtMethod->",hexdump(ptr(artmethod)));

//获取指针

varlibNative = Process.findModuleByName( "libnative-lib.so");

varaddr_F = libNative.base.add( 0xf47c); //写死值,也可以查询符号表

console.log( "addr_F->", addr_F);

//修改地址

varresult = ptr(artmethod).add( 0x20).writePointer(addr_F);

console.log( "Func.getArtMethod update->",hexdump(ptr(artmethod)));

break;

}

这样修改后的确可以调用,没有报错。

hook给定的测试APP

varaddr_ArtMethod_RegisterNative = libart.findExportByName( "_ZN3art9ArtMethod14RegisterNativeEPKvb");

Interceptor.attach(addr_ArtMethod_RegisterNative, {

onEnter: function( args){

this.arg0 = args[ 0]; //this

allocPrettyMethod.writeByteArray(allocPrettyMethodInit);

PrettyMethod(addr_ArtMethod_PrettyMethod, args[ 0], allocPrettyMethod, 0x100);

console.log( "ArtMethod_RegisterNative onEnter:",allocPrettyMethod.readCString, this.arg0, args[ 1], args[ 2]);

},

onLeave: function( retval){

// console.log("ArtMethod_RegisterNative onLeave:",this.arg0);

}

});

ArtMethod_RegisterNativeonEnter: java.lang.Stringcom.jg.bh.BH.getCloundTag( android.content.Context) 0 xe9d4def40 xd1b503f10 x0

ArtMethod_RegisterNativeonEnter: java.lang.Stringcom.jg.bh.BH.getMsg( android.content.Context) 0 xe9d4df140 xd1b503f10 x0

ArtMethod_RegisterNativeonEnter: voidcom.jg.bh.BH.install( android.content.Context) 0 xe9d4df340 xd1b503f10 x0

ArtMethod_RegisterNativeonEnter: voidcom.jg.bh.BH.updateConfig( android.content.Context, java.lang.String) 0 xe9d4df940 xd1b503f10 x0

ArtMethod_RegisterNativeonEnter: java.lang.Stringcom.jg.bh.proxy.MyProxy.getEnable( android.content.Context) 0 xe9d4e0940 xd1b503f10 x0

ArtMethod_RegisterNativeonEnter: java.lang.Stringcom.jg.bh.proxy.MyProxy.getMetaRow( android.content.Context) 0 xe9d4e0b40 xd1b503f10 x0

ArtMethod_RegisterNativeonEnter: java.lang.Objectcom.jg.bh.proxy.MyProxy.getProxyObject( java.lang.Object, java.lang.reflect.InvocationHandler) 0 xe9d4e0d40 xd1b503f10 x0

ArtMethod_RegisterNativeonEnter: java.lang.Stringcom.jg.bh.proxy.MyProxy.getVersion( android.content.Context) 0 xe9d4e0f40 xd1b503f10 x0

ArtMethod_RegisterNativeonEnter: voidcom.jg.bh.proxy.MyProxy.installAll( android.content.Context) 0 xe9d4e1140 xd1b503f10 x0

ArtMethod_RegisterNativeonEnter: java.lang.Objectcom.jg.bh.proxy.MyProxy.newProxyInstance( java.lang.ClassLoader, java.lang.Class[], java.lang.reflect.InvocationHandler) 0 xe9d4e1340 xd1b503f10 x0

正常打印,第二个参数是注册地址。

看雪ID:无造

戳“阅读原文 ”一起来充电吧!返回搜狐,查看更多

责任编辑:

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值