Android Hook (2) Java2java
原文地址:http://www.zhaoxiaodan.com/android/Android-Hook(2)-java2java.html
之前學習了如何做一個簡單android的函數勾子, 而這個勾子是用native 的函數去hook java函數, 現在來學習如何封裝讓他可以實現java hook java
不過不管怎么說, 這里已經不算是原理了, 因為原理就是改accessFlags并設置nativeFunc, 實際的hook 函數還是個native函數, 所以說這個是用這個原理來封裝
我們一般要hook一個方法, 有可能希望在三個時間點進行處理:
- 原方法執行前
- 替換原方法
- 原方法執行后
先來做個最簡單的, 在原方法執行之前 執行, 那么就需要如下的Handler 用來hook 原函數, 并且這個java層的hook 可以獲得原函數所在類的對象實例和所有參數值
public interface Handler {/** * 在原方法執行之前執行 * @param params 原方法參數 */public void before(Object instance, Object[] params); }所以在jni 函數hook 里將原函數的nativeFunc 修改為 forwardToHander, 然后forwardToHander里 解析原函數的參數值等信息之后, 再調用 Handler的before方法, 將原函數所在類實例和參數值傳遞給handler的before回調方法
具體代碼:
#include <jni.h> #include "log.h" #include "Dalvik.h"/** * dvmCallMethod 需要的是 ArrayObject*, 而 nativeFunction 傳入的參數是 const u4* * 做個轉換 */ ArrayObject* argsToArrayObject(const char* argsTypeDesc, const u4* args) {//全部參數都轉成Object型ClassObject* objectArrayClass = dvmFindArrayClass("[Ljava/lang/Object;", NULL);// 參數個數, 類型描述符都是一個字符, 所以 argsTypeDesc 字符串長度就是參數個數int argsSize = strlen(argsTypeDesc);// 分配空間ArrayObject* argArray = dvmAllocArrayByClass(objectArrayClass,argsSize ,ALLOC_DEFAULT);// 挨個賦值// 因為 args 并不是一個真正意義上的"數組", 當long 或者 double 形, 會占用 args[i]和args[i+1]// 所以需要多一個下標來指向args內存int pArgs = 0;for(int i=0;i<argsSize;i++){// 當前參數類型描述符const char descChar = argsTypeDesc[i];JValue value;Object* obj;switch(descChar){case 'Z':case 'C':case 'F':case 'B':case 'S':case 'I'://如果是基本數據類型, 則使用java的"自動裝配", 也就是 int 轉 Integer, long 轉 Long, 這樣就都轉成Object了value.i = args[pArgs++];obj = (Object*) dvmBoxPrimitive(value, dvmFindPrimitiveClass(descChar));dvmReleaseTrackedAlloc(obj, dvmThreadSelf());break;case 'D':case 'J':// 基本數據類型中, double 和 long 占用兩個字節, 所以 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的函數統一調用這個native方法, 由他來裝配之后跳轉到Handler */ static void forwardToHander(const u4* args, JValue* pResult,const Method* method, struct Thread* self) {// 之前放進去的 "附加參數"Object* handlerObject = const_cast<Object*>(reinterpret_cast<const Object*>(method->insns));// 在原方法執行之前, 我們是要調用 Handler 的 before 這個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" 說明原函數有一個Object型參數和一個long型參數, 返回值為 整形// 注意自動裝配類型, int long double, 如果是Integer, Long, Double, 則描述符為Ljava/lang/Integer,Ljava/lang/Long和Ljava/lang/Doubleconst char returnTypeDesc = method->shorty[0];const char* argsTypeDesc = &(method->shorty[1]);// 轉一下參數格式ArrayObject* argsArray ;Object* originalInstance;// 同樣的, 如果原函數不是static 的, args[0] 函數是所在類的實例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 前置調用先不修改原返回值了pResult->l = nullptr;}/** * 設置hook, 將原函數調用轉發給nativeFunc forwardToHander 進行處理 */ 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);// 找不到有可能是個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);//轉發method->nativeFunc = &forwardToHander;method->registersSize=method->insSize;method->outsSize=0;// "附加參數" 功能// 在hook 的時候傳進來的 handler 實現實例, 我們放到Method 結構體的insns 屬性中// 到時候dvm 調用 nativeFunc 的時候會帶回到 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{/** * 在原方法執行之前執行 * @param params 原方法參數 */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";/** * 設置hook * hook 住Demo2 這個類的 test 方法 * 實現一個Handler類來實現 回調函數 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}));/** * 測試靜態函數的hook, 則回調函數參數中 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 函數, 是個jni 函數 * @param clazzToHook * @param methodName * @param methodSig * @param handler */private native void hook(Class<?> clazzToHook, String methodName, String methodSig, Handler handler); }但是這個封裝其實也不好, 只是學習如何在nativeFunc 通過dvm 調用回java層代碼而已
它并沒有真正的轉發處理, 還是native來調用具體的方法; 也就是說如果hooker 需要做其他邏輯的話, 比如, 我是不是設置了before是不是有replace之類的, native代碼就多了
最好的就是, 我把原函數改掉, 然后獲取信息, 然后轉發給一個java層處理器, 告訴它, 被hook的是這個函數, 對象是這個, 參數是這些, 然后勾子是這個, 就可以了, Handler 勾子的調用都由這個java層處理器來做, 而不是natviceFunc里, native里不需要做太多邏輯
具體實現就不在做了, 因為alibaba/dexposed項目已經做的很好了, 具體可以看它的實現
我自己也做了些注釋:
pangliang/dexposed
總結
以上是生活随笔為你收集整理的Android Hook (2) Java2java的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Android Hook (1) Dex
- 下一篇: android sina oauth2.