Hook Java的的一個改進版本
《注入安卓進程,並Hook java世界的方法》這篇好文相信大家都看這,里面所提到的方法估計大家也都試過。不過里面的所用的方法,我發現有兩個可以改進的地方。
改進點一:更簡單地修改java方法為本地方法...
// hook method
int argsSize = calcMethodArgsSize(method->shorty);
if (!dvmIsStaticMethod(method))
argsSize++;
SET_METHOD_FLAG(method, ACC_NATIVE);
method->registersSize = method->insSize = argsSize;
method->outsSize = 0;
method->jniArgInfo = dvmComputeJniArgInfo(method->shorty);
// save info to insns
method->insns = (u2*)info;
// bind the bridge func,only one line
method->nativeFunc = method_handler;
LOGI("[+] %s->%s was hooked\n", classDesc, methodName);
...
直接把method->nativeFunc即可,無需重新調用JNIEnv的RegisterNatives方法,其中method_handler可以是下面兩種形式之一:
typedef void (*DalvikBridgeFunc)(const u4* args, JValue* pResult, const Method* method, struct Thread* self);
typedef void (*DalvikNativeFunc)(const u4* args, JValue* pResult);
這樣有一個好處,就是所有java方法都可以統一指向同一個native func,而不需要像為每一個java method方法指定一個native func。
改進點二:方法回調避免線程安全問題
原來的方法,是這樣的
//hook之前先拷貝
uint mlen = sizeof(Method);
Method *oldMeth = (Method*)malloc(mlen);
memcpy(oldMeth,method,mlen);
info->odlMethod = oldMeth;
info->curMethod = method;
//回調后再拷貝回來,再通過jni->callXXXXMethod調用,之后再重新hook
memcpy(hi->curMethod,hi->odlMethod,mlen);
jmethodID om = (jmethodID)hi->curMethod;
jenv->CallVoidMethod(me,om,gDevice_Sensors);
ClassMethodHook(jenv,&baiduhookInfos[0]);
這個方法,其實是有線程安全問題的,其中在dalvik中,有很多方法可以直接調用Method對象,比如dvmCallMethod, dvmCallMethodA, dvmCallMethodV,dvmInvokeMethod等等。針對DalvikBridgeFunc和DalvikNativeFunc的參數,我最后選擇使用dvmInvokeMethod,這個函數的原型是這樣的:
Object* dvmInvokeMethod(Object* obj, const Method* method, ArrayObject* argList, ArrayObject* params, ClassObject* returnType, bool noAccessCheck)
其中,obj是this或者null(如果是static方法),method可以直接使用hook之前copy的對象,比較麻煩是argList,params和returnType的獲取。獲取argList的方法,我在Proxy.cpp中到了現成的boxMethodArgs方法,而returnType通過Reflect.h中dvmGetBoxedReturnType的方法也可以獲取,而剩下的params只能自己寫代碼了,下面是我的代碼:
STATIC ArrayObject* dvmGetMethodParamTypes(const Method* method, const char* methodsig){
/* count args */
size_t argCount = dexProtoGetParameterCount(&method->prototype);
STATIC ClassObject* java_lang_object_array = dvmFindSystemClass("[Ljava/lang/Object;");
/* allocate storage */
ArrayObject* argTypes = dvmAllocArrayByClass(java_lang_object_array, argCount, ALLOC_DEFAULT);
if(argTypes == NULL){
return NULL;
}
Object** argObjects = (Object**) argTypes->contents;
const char *desc = (const char *)(strchr(methodsig, '(') + 1);
/*
* Fill in the array.
*/
size_t desc_index = 0;
size_t arg_index = 0;
bool isArray = false;
char descChar = desc[desc_index];
while (descChar != ')') {
switch (descChar) {
case 'Z':
case 'C':
case 'F':
case 'B':
case 'S':
case 'I':
case 'D':
case 'J':
if(!isArray){
argObjects[arg_index++] = dvmFindPrimitiveClass(descChar);
isArray = false;
}else{
char buf[3] = {0};
memcpy(buf, desc + desc_index - 1, 2);
argObjects[arg_index++] = dvmFindSystemClass(buf);
}
desc_index++;
break;
case '[':
isArray = true;
desc_index++;
break;
case 'L':
int s_pos = desc_index, e_pos = desc_index;
while(desc[++e_pos] != ';');
s_pos = isArray ? s_pos - 1 : s_pos;
isArray = false;
size_t len = e_pos - s_pos + 1;
char buf[128] = { 0 };
memcpy((void *)buf, (const void *)(desc + s_pos), len);
argObjects[arg_index++] = dvmFindClass(buf);
desc_index = e_pos + 1;
break;
}
descChar = desc[desc_index];
}
return argTypes;
}
通過上面幾個類型的獲取之后,最后再看一下整個method hook的實現,過程其實大同小異,不過直接把上述提及的向種類型信息預先獲取並保存到method->insns里頭了:
extern int __attribute__ ((visibility ("hidden"))) dalvik_java_method_hook(JNIEnv* env, HookInfo *info) {
const char* classDesc = info->classDesc;
const char* methodName = info->methodName;
const char* methodSig = info->methodSig;
const bool isStaticMethod = info->isStaticMethod;
jclass classObj = dvmFindJNIClass(env, classDesc);
if (classObj == NULL) {
LOGE("[-] %s class not found", classDesc);
return -1;
}
jmethodID methodId =
isStaticMethod ?
env->GetStaticMethodID(classObj, methodName, methodSig) :
env->GetMethodID(classObj, methodName, methodSig);
if (methodId == NULL) {
LOGE("[-] %s->%s method not found", classDesc, methodName);
return -1;
}
// backup method
Method* method = (Method*) methodId;
if(method->nativeFunc == method_handler){
LOGW("[*] %s->%s method had been hooked", classDesc, methodName);
return -1;
}
Method* bakMethod = (Method*) malloc(sizeof(Method));
memcpy(bakMethod, method, sizeof(Method));
// init info
info->originalMethod = (void *)bakMethod;
info->returnType = (void *)dvmGetBoxedReturnType(bakMethod);
info->paramTypes = dvmGetMethodParamTypes(bakMethod, info->methodSig);
// hook method
int argsSize = calcMethodArgsSize(method->shorty);
if (!dvmIsStaticMethod(method))
argsSize++;
SET_METHOD_FLAG(method, ACC_NATIVE);
method->registersSize = method->insSize = argsSize;
method->outsSize = 0;
method->jniArgInfo = dvmComputeJniArgInfo(method->shorty);
// save info to insns
method->insns = (u2*)info;
// bind the bridge func,only one line
method->nativeFunc = method_handler;
LOGI("[+] %s->%s was hooked\n", classDesc, methodName);
return 0;
}
然后是method_handler的實現,這個方法是所有java方法的跳轉函數,所以在這里可以注冊callback,不過這部分邏輯我沒有做上,有興趣的朋友可以加上。
STATIC void method_handler(const u4* args, JValue* pResult, const Method* method, struct Thread* self){
HookInfo* info = (HookInfo*)method->insns; //get hookinfo pointer from method-insns
LOGI("entry %s->%s", info->classDesc, info->methodName);
Method* originalMethod = reinterpret_cast(info->originalMethod);
Object* thisObject = (Object*)args[0];
ArrayObject* argTypes = dvmBoxMethodArgs(originalMethod, args + 1);
pResult->l = (void *)dvmInvokeMethod(thisObject, originalMethod, argTypes, (ArrayObject *)info->paramTypes, (ClassObject *)info->returnType, true);
dvmReleaseTrackedAlloc((Object *)argTypes, self);
}
最后通過dvmInvokeMethod就可以直接調回原來的函數了。
最后
寫這個代碼,主要是因為我在工作中要注入到某個系統進程,然后要hook java中的某些方法,但用cydia和xposed感覺太笨重了,特別是xposed,里面的很多參數的boxed/unboxed都是通過jni模塊自動轉換的,整個框架已經離不開dex文件了。
所以才想自己實現一套純本地的java hook代碼,而《注入安卓進程,並Hook java世界的方法》所介紹的方法,我感覺用起來不太方便,跟cydia和xposed兩個框架的主要區別就是缺少了一個“中轉函數”,所以而有了本碼。
代碼我上傳到github,目前只有java hook,我打算把目前的hook技術都集成到這里,包括inline hook, elf hook等等。