JNINativeInterface Hook - 图文

2019-08-30 13:23

Powered by ThomasKing 2015.04.21. All Rights Reversed

JNINativeInterface Hook

ThomasKing 2015.05.07

0x00 概述

JNINativeInterface Hook指的是:HOOK JNI接口提供的方法,名字取得有点挫,暂时这样吧。HOOK JNI接口方法,trace 函数调用,使得APP JAVA与Native层的交互更加清晰,便于分析。下面,小弟将讲解HOOK的思路来源及实现,并通过2015年ALICTF第四题APK作为实例,感受下trace JNI接口的魅力。限于水平,难免会有疏漏和错误之处,请各位大大斧正,小弟感激不尽。

0x01 思路来源

有过NDK开发的读者都知道,比如NewStringUTF,调用时的C形式是:(*env)->NewStringUTF(env, “xxx”),其汇编形式: mov Ry, [Rx, #0x29c]; blx Ry。不难发现,*env指向了一个JNI接口的函数表:struct JNINativeInterface gNativeInterface(dalvik/vm/Jni.cpp)。这个表中存放了实际各种JNI接口函数指针,那么HOOK的基本思路就是替换这个表中的函数指针。

0x02 JNINativeInterface Hook

1. JNIEnv背后的秘密

在Dalvik虚拟机启动过程中,会调用dvmCreateJNIEnv方法创建JNIEnv:

JNIEnv* dvmCreateJNIEnv(Thread* self) {

JavaVMExt* vm = (JavaVMExt*) gDvmJni.jniVm; //JavaVMExt其实就是Onload方法传入的JavaVM

assert(vm != NULL);

JNIEnvExt* newEnv = (JNIEnvExt*) calloc(1, sizeof(JNIEnvExt)); newEnv->funcTable = &gNativeInterface;

if (self != NULL) {

dvmSetJniEnvThreadId((JNIEnv*) newEnv, self); assert(newEnv->envThreadId != 0); } else {

/* make it obvious if we fail to initialize these later */ newEnv->envThreadId = 0x77777775; newEnv->self = (Thread*) 0x77777779; }

if (gDvmJni.useCheckJni) {

dvmUseCheckedJniEnv(newEnv); }

Powered by ThomasKing 2015.04.21. All Rights Reversed

ScopedPthreadMutexLock lock(&vm->envListLock);

newEnv->next = vm->envList; assert(newEnv->prev == NULL); if (vm->envList == NULL) { // rare, but possible vm->envList = newEnv; } else {

vm->envList->prev = newEnv; }

vm->envList = newEnv;

return (JNIEnv*) newEnv; }

从return的指针转换可知,JNIEnv实际指向的是JNIEnvEx结构体:

struct JNIEnvExt {

const struct JNINativeInterface* funcTable;

const struct JNINativeInterface* baseFuncTable; u4 envThreadId; Thread* self; int critical;

struct JNIEnvExt* prev; struct JNIEnvExt* next; };

newEnv->funcTable = &gNativeInterface;初始化了JNI函数接口指针。另外,在Jni.cpp可以看到声明static const struct JNINativeInterface gNativeInterface,const关键字只是编译器限制,其实际存在放libdvm.so RW segment中,并且其保存的函数指针在加载时还需重定位。故HOOK时无需调用mprotect修改内存权限标示。

此时,JNI函数调用就比较清晰了:(*env)->NewStringUTF(env, “xxx”) -> (env-> funcTable + 0x29c)(env, “xxx”),和汇编代码完美吻合。

//插入双向链表

2. Hook实现

了解了JNIEnv背后的真实类型后,那么Hook就简单了。比如: pOld_NewStringUTF = (*env)->NewStringUTF (*env)->NewStringUTF = TK_ StringUTF

Jstring TK_StringNewStringUTF(JNIEnv* env, const char *str){

LOGD(“TK”, “HOOK!!!”);

Return pOld_NewStringUTF(env, str); }

Hook单个函数可以直接这么替换,不过要HOOK一部分或者全部的话,声明一大堆函数指针比较跪。考虑通过声明一个JNIEnvExt结构体实现来保存。这里不能直接声明一个JNInterface结构体,因为不包含Thread等信息。声明的JNIEnvExt需要拷贝当前的JNIEnv的其他信息,即:

Powered by ThomasKing 2015.04.21. All Rights Reversed

memcpy((char*)(pOldJNIEnvExt) + sizeof(struct TK_JNINativeInterface*), (char*)(env) + sizeof(struct TK_JNINativeInterface*), sizeof(struct TK_JNIEnvExt) - sizeof(struct TK_JNINativeInterface*));

另外,JNIEnvExt结构体在不同版本之间也有区别,这点需要注意。不然HOOK去读取Thread等信息时,由于字段偏移不同造成崩溃。 Android2.x:

typedef struct JNIEnvExt { const struct JNINativeInterface* funcTable; const struct JNINativeInterface* baseFuncTable; struct JavaVMExt* vm; u4 envThreadId; Thread* self; int critical; bool forceDataCopy; struct JNIEnvExt* prev; struct JNIEnvExt* next; }JNIEnvExt;

Android 4.x:

struct JNIEnvExt { const struct JNINativeInterface* funcTable; const struct JNINativeInterface* baseFuncTable; u4 envThreadId; Thread* self; int critical; struct JNIEnvExt* prev; struct JNIEnvExt* next; };

另外,一些JNI接口函数调用时支持变参,现考虑如何获取参数。比如方法: jobject (JNICALL *CallObjectMethod)

(JNIEnv *env, jobject obj, jmethodID methodID, ...); jobject (JNICALL *CallObjectMethodV)

(JNIEnv *env, jobject obj, jmethodID methodID, va_list args);

对于大多数开发者而言,通过习惯使用CallObjectMethod这种变参形式。其编译后实际会被转换为CallObjectMethodV这种形式,这点通过HOOK来验证。通过va_arg(va_list, type)可以获得各个参数,在stdarg.h中可以看到,va_arg其实际通过宏定义实现。不过还是没有解决一个问题,那就是参数个数。

Va_arg虽然可以获取下一个参数,不过并没有说明现在方法有多少个参数,那Dalvik虚拟机执行时是如何知道的呢?熟悉jmethod结构的读者可能已经想到,通过Method结构体的字段来表示。这里选择shorty字段,shorty字段第一个存放了返回类型的字符缩写,其后存放了各个参数类型,非基本类型用’L’表示。通过解析shorty字符串,即可获得参数类型和个数。另外,由于变参通过栈上传递,栈上4字节对其,取short等类型时,通过va_arg(va_list, jint)来取得。在本机测试时,由于float类型和double类型的取得存在问题,通过va_arg(va_list,

Powered by ThomasKing 2015.04.21. All Rights Reversed

jint)和va_arg(va_list, jlong)实现。

这里在啰嗦下,如果需要修改参数的值,需要采用类似调用点检测的方法来实现。虽然args其实际为一个指向栈上指针,由于va_list的宏展开编译时会引起一些异常。这里,通过GETR3汇编宏直接获取R3的值,其实也就是args的值。有了这个指针,即可修改调用参数。 另外,由于这些方法也被系统调用,故需要过滤。根据调用点地址,过滤掉从libdvm.so和libnativehelper.so中的调用,减少log的数量。

0x03 实例分析

在通过log分析ALICTF第四题时,先说明下log的格式:

[0x80c15971] CallObjectMethodV(0x40519d00, 0x427c4110, [I:0x0][L:0x405216f0]) [0x4051fe38] 0x80c15971 调用点

0x40519d00 jclazz or jobject 0x427c4110 jmethod

[I:0x0][L:0x405216f0] 两个参数,第一个参数类型int,第二个为非基本类型 0x4051fe38 返回值

此APK经过dex加固和SO加固,先脱出dex文件打开。其中包含你好中国各种native方法:

Powered by ThomasKing 2015.04.21. All Rights Reversed

图 1

各个类中有都调用了这个类中的native方法,即JAVA层和Native层频繁交互。到这里可以猜测,将java直接调用的方法,通过Native间接调用,模糊调用流程。 不过还是可以锁定btn的onclick方法:

图 2

不难发现,__bb方法即是verify方法。

到这里,基本分析完毕。现在需要解决一个很棘手的问题:这些native函数的地址,即找到Native函数。由于SO被加壳,而且汇编代码存在混淆,直接跟出代码费事费力。现在使用Hook trace下:


JNINativeInterface Hook - 图文.doc 将本文的Word文档下载到电脑 下载失败或者文档不完整,请联系客服人员解决!

下一篇:银行卡从业人员专业认证培训作业- 副本

相关阅读
本类排行
× 注册会员免费下载(下载后可以自由复制和排版)

马上注册会员

注:下载文档有可能“只有目录或者内容不全”等情况,请下载之前注意辨别,如果您已付费且无法下载或内容有问题,请联系我们协助你处理。
微信: QQ: