吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 16798|回复: 36
收起左侧

[Android 原创] JNINativeInterfaceHook & trace

  [复制链接]
ThomasKing 发表于 2015-5-8 12:34
享一个之前写的小东西
------------------------------------------------------------------
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);
    }

    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的其他信息,即:
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, 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方法:

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

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

图 3
各种Native方法已经吐出来了。另外,加载过程的调用也已经trace到:

图4

找到了各个native函数,再任意输入查看调用(部分截图):

图 5
调用Base64参数:
[0x80c59851] FindClass(android/util/Base64) [0x40538928]
[0x80c59781] GetStaticMethodID(0x40538928, decode, (Ljava/lang/String;I)[B) [0x4297fef0]
[0x80c1591f] CallStaticObjectMethodV(0x40538928, 0x4297fef0, [L:0x4053a540][I:0x0]) [0x4053a620]
返回的0x4053a620后面被用于验证:
[0x80c8e8a1] FindClass(你好中国) [0x405209f8]
[0x80c8e9cf] GetStaticMethodID(0x405209f8, __x, ([B[B)Z) [0x42974578]
[0x80c31633] CallStaticBooleanMethodV(0x405209f8, 0x42974578, [L:0x4053a220][L:0x4053a620]) [False]    //比较
比较的返回值为False,已经很明显了吧。。。

限于篇幅,就不详细分析了吧,直接根据log和简单的动态调试即可获得答案:wsfduj。有兴趣的读者自己试试。

鉴于之前发动态链接库有小伙伴说难用,直接上静态链接库文件。

0x04 参考文献
老罗:Dalvik虚拟机的运行过程分析
Dalvik Jni相关源码

lib.rar (118.62 KB, 下载次数: 75) JNINativeInterface Hook.rar (1.05 MB, 下载次数: 102)
alictf4.rar (544.41 KB, 下载次数: 42)

免费评分

参与人数 9吾爱币 +1 热心值 +9 收起 理由
欧阳锋锋 + 1 + 1 谢谢@Thanks!
fenghaoda + 1 我很赞同!
ohyeah521 + 1 求个so文件,源码不会使用啊。
Lazy熊 + 1 我很赞同!
wanttobeno + 1 大神制作,顶!
dagangwood + 1 谢谢@Thanks!
真的很疼 + 1 有兴趣!!!先加分在慢慢看
双菜鱼 + 1 谢谢@Thanks!
gy3026 + 1 谢谢@Thanks!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

L4Nce 发表于 2015-5-27 16:20
前辈请问前
void trace_jInterfaceFunction(JNIEnv* env);
void Untrace_jInterfaceFunction(JNIEnv* env);
这里的参数env是怎么获取的,如果说把你提供的.a编译成so注入后,怎么获取原来so线程中env呢。
头像被屏蔽
K4NG 发表于 2015-5-9 21:07
刚开始少了一个子“分”享  JNINativeInterfaceHook 这名字取得确实够搓的 好歹加几个空格啊

免费评分

参与人数 1热心值 +1 收起 理由
liangdeshu + 1 你的头像,大哥很是喜欢那

查看全部评分

gy3026 发表于 2015-5-8 12:55
currwin 发表于 2015-5-8 13:18
前排排队膜拜
沧之殇 发表于 2015-5-8 13:37
完全看不懂,楼主真大神
Hmily 发表于 2015-5-8 13:38
我把附件本地上传编辑了下帖子,方便下载

免费评分

参与人数 1热心值 +1 收起 理由
X雷廷X + 1 帅哥你好!

查看全部评分

soulovess 发表于 2015-5-8 14:39
虽然现在看不懂,我一直在努力,支持大神发帖,期待更多精华。。
双菜鱼 发表于 2015-5-8 15:51
第一次离大神这么近,膜拜大神
阿基米无德 发表于 2015-5-8 15:58 来自手机
我勒个去,看不懂
蚯蚓翔龙 发表于 2015-5-8 20:21
还是看不懂这些精华帖。。。
yagamiRQF 发表于 2015-5-9 09:44
正在学习中,感谢大神的分享~
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-11-24 13:20

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表