首页
社区
课程
招聘
[原创]NativeMethodHook后记
发表于: 2015-1-21 20:13 22020

[原创]NativeMethodHook后记

2015-1-21 20:13
22020
作者:ThomasKing
时间: 2015-01-21

最近使用之前实现的NDK HOOK,发现之前实现缺少对原函数的调用,故发帖对其进行补充。希望能有所帮助吧。限于小弟水平,难免会有错误和疏漏之处,请各位大大斧正,小弟感激不尽。

一、前言
最近使用Cydia JavaHook  Debug. isDebuggerConnected方法时,真机调试IDA老是连接不上,只好不用cydia。翻看源码,发现Debug. isDebuggerConnected其实调用VMDebug. isDebuggerConnected方法,而这个方法isDebuggerConnected是个Native方法。和之前帖子http://bbs.pediy.com/showthread.php?t=192047 需求不同,这次需要调用原来的NDK方法。故仔细研究了下并实现,作为对之前帖子的补充。限于水平,难免会有错误和疏漏之处,请各位大大斧正。

二、正文
这里再简单啰嗦下JNI HOOK的实现原理。从源码Native.cpp可以知道,同一个native方法课可以被Register多次,那么HOOk的就是再次Register,达到替换的目的,实现HOOK。类似其他HOOK技术,很多时候我们需要调用原来的方法。从上述实现原理可以不难发现,HOOK过程是获取不到原来的函数。(可能有读者会问,不用调用原来的函数,直接从全局符号gDvm动手实现Patch。当然,针对isDebuggerConnected可以实现,但其他方法就另当别论了)
不管是java方法还是Native方法,对于API函数调用者来说,是根本不关心的。调用时这部分工作是由VM完成。既然不关心方法类型,那么可以使用java hook实现。Java hook http://bbs.pediy.com/showthread.php?t=192803 这个帖子中已经实现得很简单方便了。
调用SO中的函数,仅仅只需要知道其地址即可。换句话说,Native函数和SO中的普通函数是没有区别的,获取其在SO中的起始地址并保存,即可在HOOK中调用原Native函数。既然Native函数已经包含于SO中,直接静态分析和动态调试即可找到其在SO偏移量,再加上SO的起始地址,得到其绝对地址。相对使用Java HOOK来实现调用原来的Native方法,直接分析SO得到原Native函数实现起来较简洁,但分析过程有时比较复杂,可能还不如Java HOOK直截了当。
在JNI中调用其他方法,都需要获取jmethodID。jmethodID在jni.h中注释为透明结构,其实质是Method*,简单看下Method结构体:
struct Method {
    ClassObject*    clazz;       
    u4              accessFlags;
    u2             methodIndex;
    u2              registersSize;
    u2              outsSize;
    u2              insSize;
    const char*     name;
    DexProto        prototype;
    const char*     shorty;
    const u2*       insns;
    int             jniArgInfo;
    DalvikBridgeFunc nativeFunc;
    bool fastJni;
    bool noRef;
    bool shouldTrace;       
    const RegisterMap* registerMap;
    bool            inProfile;
};
字段比较多,我们只关心红色部分。accessFlags其ACC_NATIVE位标识其为是否为Native方法。若标识为Native方法,那么insns即指向了Native方法的首地址。故HOOK时,先通过jmethodID找到原native方法,再Register进行替换。
简单改下原帖子http://bbs.pediy.com/showthread.php?t=192047中的Demo,测试通过。但HOOK  VMDebug. isDebuggerConnected测试时,程序崩溃了。分析发现,其insns指向NULL。翻看Object.h中,注释中有这么一句话:For internal-native this stays null.
原来isDebuggerConnected是一个internal-native,故其指向了NULL。
----------------------------------------------------------------------------------
感谢老党指出错误。对internal-native的HOOK,帖子之前那种方案是有问题的。
需要注意的是这一点:所有注册的JNI函数都是间接调用。对于internal-native,其insns始终指向NULL。如果函数第一次调用,其Method.nativeFunc指向
dvmLookupInternalNativeMethod。之后调用,其nativeFunc指向真正的Bridge函数。对Bridge函数调用时的参数构造,参看http://bbs.pediy.com/showthread.php?t=192803 即可。(写帖子记录之前,测试了几个internal-native函数,直接调用Bridge函数没什么问题。现发现问题所在:测试这几个函数都是无参)
eg: isDebuggerConnected
.text:0005F9B8                 PUSH            {R4,LR}
.text:0005F9BA                 MOV             R4, R1
.text:0005F9BC                 BL              dvmDbgIsDebuggerConnected
.text:0005F9C0                 STR             R0, [R4]
.text:0005F9C2                 POP             {R4,PC}  /
//dvmDbgIsDebuggerConnected未使用R4,返回时R0未被覆盖,直接调用Bridge没什么问题。(其他无参函数类似)

再次翻看源码和查看libdvm.so汇编揣摩,internal-native函数是不能获得其insns函数。普通的native函数通过注册后,被注册的函数通过Bridge函数回调调用。Bridge函数直接包裹了其调用函数,其insns指向NULL是合理的(internal-native函数在编译时已经可以确定,也没有必要通过回调)。

从HOOK的角度来说,对internal-native其实没有比较去获得原native函数。
因为libdvm.so是系统库并对照源码,分析起来比较容易(而经过加密混淆等加固后的SO文件,有时分析起来比较麻烦,直接使用NativeMethod去Patch也不失为一种方法),直接使用普通的SO hook即可,否则就有点生搬硬套了。(比如,dvmDbgIsDebuggerConnected实为libdvm.so的导出函数, 使用导出表HOOK or inline hook都行)
----------------------------------------------------------------------------------
至此,完成了对原帖http://bbs.pediy.com/showthread.php?t=192047的补充,算是对NativeMethodHOOK的完整实现吧。

三、参考文献
源码:Globals.h  Object.h  Native.cpp  InternalNative.cpp
http://bbs.pediy.com/showthread.php?t=192803
http://blog.csdn.net/luoshengyang/article/details/8923483
--------------------------------------------------------------------------
再次感谢老党指出错误!

[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)

收藏
免费 0
支持
分享
最新回复 (10)
雪    币: 188
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
多谢楼主分享,学习一下。
2015-1-21 20:19
0
雪    币: 76
活跃值: (13)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3
前排学习
2015-1-21 21:07
0
雪    币: 1222
活跃值: (4302)
能力值: ( LV5,RANK:69 )
在线值:
发帖
回帖
粉丝
4
第一次前排啊
2015-1-21 21:14
0
雪    币: 233
活跃值: (148)
能力值: ( LV9,RANK:210 )
在线值:
发帖
回帖
粉丝
5
赞,王总真是高产高质。
2015-1-22 10:35
0
雪    币: 370
活跃值: (1180)
能力值: ( LV9,RANK:310 )
在线值:
发帖
回帖
粉丝
6
额,梁哥,别拿小弟开玩笑了。
2015-1-22 11:33
0
雪    币: 14
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
7
Java声明
public static native boolean isDebuggerConnected();

Native声明
static void Dalvik_dalvik_system_VMDebug_isDebuggerConnected(const u4* args,
    JValue* pResult)

g_oldFunc 声明成 (jboolean(*)(JNIEnv *, jclass, ...))

能正常调回去?
2015-1-22 11:49
0
雪    币: 1185
活跃值: (458)
能力值: ( LV13,RANK:360 )
在线值:
发帖
回帖
粉丝
8
支持king总!又学习了!
2015-1-22 13:59
0
雪    币: 370
活跃值: (1180)
能力值: ( LV9,RANK:310 )
在线值:
发帖
回帖
粉丝
9
在native函数的回调理解有点问题,已经更正帖子。多谢指出错误之处!
2015-1-22 16:41
0
雪    币: 14
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
10
客气,你倒提供了一个思路,用个变通的法子应该是可行的,待我验证下
2015-1-22 17:40
0
雪    币: 36
活跃值: (30)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
11
mark一下,以后学习。
2015-1-30 11:41
0
游客
登录 | 注册 方可回帖
返回
//