作者: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期)