Android Hook (2) Java2java
原文地址:http://www.zhaoxiaodan.com/android/Android-Hook(2)-java2java.html
之前學(xué)習(xí)了如何做一個(gè)簡(jiǎn)單android的函數(shù)勾子, 而這個(gè)勾子是用native 的函數(shù)去hook java函數(shù), 現(xiàn)在來(lái)學(xué)習(xí)如何封裝讓他可以實(shí)現(xiàn)java hook java
不過(guò)不管怎么說(shuō), 這里已經(jīng)不算是原理了, 因?yàn)樵砭褪歉腶ccessFlags并設(shè)置nativeFunc, 實(shí)際的hook 函數(shù)還是個(gè)native函數(shù), 所以說(shuō)這個(gè)是用這個(gè)原理來(lái)封裝
我們一般要hook一個(gè)方法, 有可能希望在三個(gè)時(shí)間點(diǎn)進(jìn)行處理:
- 原方法執(zhí)行前
- 替換原方法
- 原方法執(zhí)行后
先來(lái)做個(gè)最簡(jiǎn)單的, 在原方法執(zhí)行之前 執(zhí)行, 那么就需要如下的Handler 用來(lái)hook 原函數(shù), 并且這個(gè)java層的hook 可以獲得原函數(shù)所在類的對(duì)象實(shí)例和所有參數(shù)值
public interface Handler {/** * 在原方法執(zhí)行之前執(zhí)行 * @param params 原方法參數(shù) */public void before(Object instance, Object[] params); }所以在jni 函數(shù)hook 里將原函數(shù)的nativeFunc 修改為 forwardToHander, 然后forwardToHander里 解析原函數(shù)的參數(shù)值等信息之后, 再調(diào)用 Handler的before方法, 將原函數(shù)所在類實(shí)例和參數(shù)值傳遞給handler的before回調(diào)方法
具體代碼:
#include <jni.h> #include "log.h" #include "Dalvik.h"/** * dvmCallMethod 需要的是 ArrayObject*, 而 nativeFunction 傳入的參數(shù)是 const u4* * 做個(gè)轉(zhuǎn)換 */ ArrayObject* argsToArrayObject(const char* argsTypeDesc, const u4* args) {//全部參數(shù)都轉(zhuǎn)成Object型ClassObject* objectArrayClass = dvmFindArrayClass("[Ljava/lang/Object;", NULL);// 參數(shù)個(gè)數(shù), 類型描述符都是一個(gè)字符, 所以 argsTypeDesc 字符串長(zhǎng)度就是參數(shù)個(gè)數(shù)int argsSize = strlen(argsTypeDesc);// 分配空間ArrayObject* argArray = dvmAllocArrayByClass(objectArrayClass,argsSize ,ALLOC_DEFAULT);// 挨個(gè)賦值// 因?yàn)?args 并不是一個(gè)真正意義上的"數(shù)組", 當(dāng)long 或者 double 形, 會(huì)占用 args[i]和args[i+1]// 所以需要多一個(gè)下標(biāo)來(lái)指向args內(nèi)存int pArgs = 0;for(int i=0;i<argsSize;i++){// 當(dāng)前參數(shù)類型描述符const char descChar = argsTypeDesc[i];JValue value;Object* obj;switch(descChar){case 'Z':case 'C':case 'F':case 'B':case 'S':case 'I'://如果是基本數(shù)據(jù)類型, 則使用java的"自動(dòng)裝配", 也就是 int 轉(zhuǎn) Integer, long 轉(zhuǎn) Long, 這樣就都轉(zhuǎn)成Object了value.i = args[pArgs++];obj = (Object*) dvmBoxPrimitive(value, dvmFindPrimitiveClass(descChar));dvmReleaseTrackedAlloc(obj, dvmThreadSelf());break;case 'D':case 'J':// 基本數(shù)據(jù)類型中, double 和 long 占用兩個(gè)字節(jié), 所以 pArgs 需要 +2value.j = dvmGetArgLong(args, pArgs++);pArgs++;obj = (Object*) dvmBoxPrimitive(value, dvmFindPrimitiveClass(descChar));dvmReleaseTrackedAlloc(obj, dvmThreadSelf());break;case '[':case 'L':obj = (Object*) args[pArgs++];break;default:LOGE("Unknown method signature description character: %c\n", descChar);obj = NULL;}// 塞到ArrayObject 中dvmSetObjectArrayElement(argArray,i,obj);}return argArray; }/** * 被hook的函數(shù)統(tǒng)一調(diào)用這個(gè)native方法, 由他來(lái)裝配之后跳轉(zhuǎn)到Handler */ static void forwardToHander(const u4* args, JValue* pResult,const Method* method, struct Thread* self) {// 之前放進(jìn)去的 "附加參數(shù)"Object* handlerObject = const_cast<Object*>(reinterpret_cast<const Object*>(method->insns));// 在原方法執(zhí)行之前, 我們是要調(diào)用 Handler 的 before 這個(gè)hookMethod* handlerBeforeMethod = dvmFindInterfaceMethodHierByDescriptor(handlerObject->clazz,"before","(Ljava/lang/Object;[Ljava/lang/Object;)V");if(nullptr != handlerBeforeMethod){LOGD("handlerBeforeMethod name:%s,desc:%s",handlerBeforeMethod->name,handlerBeforeMethod->shorty);JValue* result = new JValue();// method->shorty[0] 表示返回值類型, 比如"ILJ" 說(shuō)明原函數(shù)有一個(gè)Object型參數(shù)和一個(gè)long型參數(shù), 返回值為 整形// 注意自動(dòng)裝配類型, int long double, 如果是Integer, Long, Double, 則描述符為L(zhǎng)java/lang/Integer,Ljava/lang/Long和Ljava/lang/Doubleconst char returnTypeDesc = method->shorty[0];const char* argsTypeDesc = &(method->shorty[1]);// 轉(zhuǎn)一下參數(shù)格式ArrayObject* argsArray ;Object* originalInstance;// 同樣的, 如果原函數(shù)不是static 的, args[0] 函數(shù)是所在類的實(shí)例if(dvmIsStaticMethod(method)){argsArray = argsToArrayObject(argsTypeDesc,&(args[0]));originalInstance = nullptr;}else{argsArray = argsToArrayObject(argsTypeDesc,&(args[1]));originalInstance = (Object*)(args[0]);}dvmCallMethod(self,handlerBeforeMethod, handlerObject, result, originalInstance ,argsArray);}else{LOGE("method no found....");}// before 前置調(diào)用先不修改原返回值了pResult->l = nullptr;}/** * 設(shè)置hook, 將原函數(shù)調(diào)用轉(zhuǎn)發(fā)給nativeFunc forwardToHander 進(jìn)行處理 */ extern "C" JNIEXPORT void JNICALL Java_com_zhaoxiaodan_hookdemo_Demo2_hook(JNIEnv *env, jobject instance, jobject clazzToHook,jstring methodName_, jstring methodSig_,jobject handler) {const char *methodName = env->GetStringUTFChars(methodName_, 0);const char *methodSig = env->GetStringUTFChars(methodSig_, 0);jmethodID methodIDToHook = env->GetMethodID((jclass) clazzToHook,methodName,methodSig);// 找不到有可能是個(gè)staticif(nullptr == methodIDToHook){env->ExceptionClear();methodIDToHook = env->GetStaticMethodID((jclass) clazzToHook,methodName,methodSig);}if(methodIDToHook != nullptr){Method* method = reinterpret_cast<Method*>(methodIDToHook);LOGD("hook function %s, args desc:%s",method->name,method->shorty);SET_METHOD_FLAG(method, ACC_NATIVE);//轉(zhuǎn)發(fā)method->nativeFunc = &forwardToHander;method->registersSize=method->insSize;method->outsSize=0;// "附加參數(shù)" 功能// 在hook 的時(shí)候傳進(jìn)來(lái)的 handler 實(shí)現(xiàn)實(shí)例, 我們放到Method 結(jié)構(gòu)體的insns 屬性中// 到時(shí)候dvm 調(diào)用 nativeFunc 的時(shí)候會(huì)帶回到 nativeFunc 中, 我們就能取到了method->insns = reinterpret_cast<const u2*>(dvmDecodeIndirectRef(dvmThreadSelf(),handler));}env->ReleaseStringUTFChars(methodName_, methodName);env->ReleaseStringUTFChars(methodSig_, methodSig); }使用代碼為:
public class Demo2 {String TAG = "===[hookdemo]===";public interface Handler{/** * 在原方法執(zhí)行之前執(zhí)行 * @param params 原方法參數(shù) */public void before(Object instance, Object[] params);}public String test(String param1, long param2, int[] param3){return param1;}public static String staticTest(String param1, Long param2, int[] param3){return param1;}public void demo(){String param1 = "param1";/** * 設(shè)置hook * hook 住Demo2 這個(gè)類的 test 方法 * 實(shí)現(xiàn)一個(gè)Handler類來(lái)實(shí)現(xiàn) 回調(diào)函數(shù) before */hook(Demo2.class, "test", "(Ljava/lang/String;J[I)Ljava/lang/String;", new Handler(){@Overridepublic void before(Object instance, Object[] params){Log.d(TAG, "===========Handler before,param len: "+params.length+",instance:"+instance);for (int i = 0; i < params.length; i++){Log.d(TAG, "p" + i + "=" + params[i]);}}});Log.d(TAG, "===========call test" + this.test(param1,999L,new int[]{1,2,3}));/** * 測(cè)試靜態(tài)函數(shù)的hook, 則回調(diào)函數(shù)參數(shù)中 instance 為 null */hook(Demo2.class, "staticTest", "(Ljava/lang/String;Ljava/lang/Long;[I)Ljava/lang/String;", new Handler(){@Overridepublic void before(Object instance, Object[] params){Log.d(TAG, "===========Handler before,param len: "+params.length+",instance:"+instance);for (int i = 0; i < params.length; i++){Log.d(TAG, "p" + i + "=" + params[i]);}}});Log.d(TAG, "===========call test" + this.staticTest(param1, 999L, new int[]{1, 2, 3}));}/** * hook 函數(shù), 是個(gè)jni 函數(shù) * @param clazzToHook * @param methodName * @param methodSig * @param handler */private native void hook(Class<?> clazzToHook, String methodName, String methodSig, Handler handler); }但是這個(gè)封裝其實(shí)也不好, 只是學(xué)習(xí)如何在nativeFunc 通過(guò)dvm 調(diào)用回java層代碼而已
它并沒(méi)有真正的轉(zhuǎn)發(fā)處理, 還是native來(lái)調(diào)用具體的方法; 也就是說(shuō)如果hooker 需要做其他邏輯的話, 比如, 我是不是設(shè)置了before是不是有replace之類的, native代碼就多了
最好的就是, 我把原函數(shù)改掉, 然后獲取信息, 然后轉(zhuǎn)發(fā)給一個(gè)java層處理器, 告訴它, 被hook的是這個(gè)函數(shù), 對(duì)象是這個(gè), 參數(shù)是這些, 然后勾子是這個(gè), 就可以了, Handler 勾子的調(diào)用都由這個(gè)java層處理器來(lái)做, 而不是natviceFunc里, native里不需要做太多邏輯
具體實(shí)現(xiàn)就不在做了, 因?yàn)閍libaba/dexposed項(xiàng)目已經(jīng)做的很好了, 具體可以看它的實(shí)現(xiàn)
我自己也做了些注釋:
pangliang/dexposed
總結(jié)
以上是生活随笔為你收集整理的Android Hook (2) Java2java的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: Android Hook (1) Dex
- 下一篇: 利用xposed绕过安卓SSL证书的强校