感謝看雪論壇中的這位大神,分享了這個技術:http://bbs.pediy.com/showthread.php?t=186054,從這篇文章中學習到了不少內容,若是沒有這篇好文章,我在研究的過程當中會遇到不少困難,說不定我就放棄了~~在此感謝他。javascript
以前的幾篇文章都是在介紹了OC的相關知識,以前的半個月也都是在搞IOS的相關東西,白天上班作Android工做,晚上回家還有弄IOS,感受真的很傷了。不過OC的知識也學習了差很少了。不過在這段時間遺留了不少Android方面的問題都沒有進行解決和總結。因此從這段時間開始,須要將解決的問題都總結一下吧。php
Android中經過注入技術修改系統返回的Mac地址java
下面來看一下這個技術須要哪些知識點android
一、如何將非native方法變成native方法shell
二、如何將native方法直接註冊(不須要jni這樣的頭文件了)express
三、Android中的類加載器相關知識apache
四、如何編譯Android系統引用系統頭文件的NDK項目bootstrap
雖然這裏有這四個知識點,可是其中有兩個我在以前的blog中已經介紹了:api
Android中的類加載器:http://blog.csdn.net/jiangwei0910410003/article/details/41384667數組
如何編譯Android系統引用系統頭文件的NDK項目:http://blog.csdn.net/jiangwei0910410003/article/details/40949475
不過在這篇文章中,咱們在介紹一種新的編譯方式
package com.example.testar; import android.net.wifi.WifiInfo; import android.net.wifi.WifiManager; import android.os.Bundle; import android.app.Activity; import android.content.Context; import android.util.Log; import android.view.Menu; import android.view.View; import android.widget.Button; public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Button btn = (Button) findViewById(R.id.button1); btn.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { WifiManager wifi = (WifiManager) getSystemService(Context.WIFI_SERVICE); WifiInfo info = wifi.getConnectionInfo(); System.out.println("Wifi mac :" + info.getMacAddress()); Log.d("DEMO", "Wifi mac:"+info.getMacAddress()); } }); } }咱們看到,這裏的代碼很簡單,就是打印一下設備的Mac地址,如今咱們要作的就是:注入這個Demo進程,而後修改Mac的值。
首先來看一下inject.c
這個是注入進程的核心文件,因爲代碼比較多,這裏只看核心的部分:
int main(int argc, char** argv) { char *pn = "com.example.testar"; char *is = "/data/local/libso.so"; printf("%s\n",pn); printf("%s\n",is); pid_t target_pid; target_pid = find_pid_of(pn); printf("pid: %d\n",target_pid); int ret = inject_remote_process(target_pid, is, "InjectInterface", (void*)"I'm parameter!", strlen("I'm parameter!") ); printf("result: %d\n",ret); }就是他的主函數代碼
咱們看到有一個重要的函數:
inject_remote_process
第一個參數:注入進程的id
第二個參數:須要注入到目標進程的so文件
第三個參數:so文件中須要執行的函數名
第四個參數:執行函數的參數
第五個參數:執行函數的參數的長度
主要仍是前面三個參數。
這裏咱們經過find_pid_of(pn)函數來獲取進程id
傳遞進去的是進程名
Android中應用的進程名就是包名
char *pn = "com.example.testar";看到上面注入到目標進程的so文件
char *is = "/data/local/libso.so";下面再來看一下這個so文件的源代碼
so.cpp
#include "jni.h" #include <android_runtime/AndroidRuntime.h> #include "android/log.h" #include "stdio.h" #include "stdlib.h" #include "MethodHooker.h" #include <utils/CallStack.h> #define log(a,b) __android_log_write(ANDROID_LOG_INFO,a,b); // LOGÀàÐÍ:info #define log_(b) __android_log_write(ANDROID_LOG_INFO,"JNI_LOG_INFO",b); // LOGÀàÐÍ:info extern "C" void InjectInterface(char*arg){ log_("*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*"); log_("*-*-*-*-*-* Injected so *-*-*-*-*-*-*-*"); log_("*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*"); Hook(); log_("*-*-*-*-*-*-*- End -*-*-*-*-*-*-*-*-*-*"); } extern "C" JNIEXPORT jstring JNICALL Java_com_example_testar_InjectApplication_test(JNIEnv *env, jclass clazz) { return env->NewStringUTF("haha "); }在這個文件中,咱們看到了函數InjectInterface了,由於so是C++程序,可是inject是C程序,爲了兼容,就有這種方式了
extern "C" 函數{
//do something
}
這個代碼沒什麼難度和複雜性
這個函數中調用了Hook函數,下面在來看一下Hook函數的定義
MethodHooker.cpp的實現
#include "MethodHooker.h" #include "jni.h" #include "android_runtime/AndroidRuntime.h" #include "android/log.h" #include "stdio.h" #include "stdlib.h" #include "native.h" #include <dlfcn.h> #define ANDROID_SMP 0 #include "Dalvik.h" #include "alloc/Alloc.h" #define ALOG(...) __android_log_print(ANDROID_LOG_VERBOSE, __VA_ARGS__) static bool g_bAttatedT; static JavaVM *g_JavaVM; void init() { g_bAttatedT = false; g_JavaVM = android::AndroidRuntime::getJavaVM(); } static JNIEnv *GetEnv() { int status; JNIEnv *envnow = NULL; status = g_JavaVM->GetEnv((void **)&envnow, JNI_VERSION_1_4); if(status < 0) { status = g_JavaVM->AttachCurrentThread(&envnow, NULL); if(status < 0) { return NULL; } g_bAttatedT = true; } return envnow; } static void DetachCurrent() { if(g_bAttatedT) { g_JavaVM->DetachCurrentThread(); } } static int computeJniArgInfo(const DexProto* proto) { const char* sig = dexProtoGetShorty(proto); int returnType, jniArgInfo; u4 hints; /* The first shorty character is the return type. */ switch (*(sig++)) { case 'V': returnType = DALVIK_JNI_RETURN_VOID; break; case 'F': returnType = DALVIK_JNI_RETURN_FLOAT; break; case 'D': returnType = DALVIK_JNI_RETURN_DOUBLE; break; case 'J': returnType = DALVIK_JNI_RETURN_S8; break; case 'Z': case 'B': returnType = DALVIK_JNI_RETURN_S1; break; case 'C': returnType = DALVIK_JNI_RETURN_U2; break; case 'S': returnType = DALVIK_JNI_RETURN_S2; break; default: returnType = DALVIK_JNI_RETURN_S4; break; } jniArgInfo = returnType << DALVIK_JNI_RETURN_SHIFT; hints = dvmPlatformInvokeHints(proto); if (hints & DALVIK_JNI_NO_ARG_INFO) { jniArgInfo |= DALVIK_JNI_NO_ARG_INFO; } else { assert((hints & DALVIK_JNI_RETURN_MASK) == 0); jniArgInfo |= hints; } return jniArgInfo; } int ClearException(JNIEnv *jenv){ jthrowable exception = jenv->ExceptionOccurred(); if (exception != NULL) { jenv->ExceptionDescribe(); jenv->ExceptionClear(); return true; } return false; } bool isArt(){ return true; } static jclass findAppClass(JNIEnv *jenv,const char *apn){ jclass clazzApplicationLoaders = jenv->FindClass("android/app/ApplicationLoaders"); jthrowable exception = jenv->ExceptionOccurred(); if (ClearException(jenv)) { ALOG("Exception","No class : %s", "android/app/ApplicationLoaders"); return NULL; } jfieldID fieldApplicationLoaders = jenv->GetStaticFieldID(clazzApplicationLoaders,"gApplicationLoaders","Landroid/app/ApplicationLoaders;"); if (ClearException(jenv)) { ALOG("Exception","No Static Field :%s","gApplicationLoaders"); return NULL; } jobject objApplicationLoaders = jenv->GetStaticObjectField(clazzApplicationLoaders,fieldApplicationLoaders); if (ClearException(jenv)) { ALOG("Exception","GetStaticObjectField is failed [%s","gApplicationLoaders"); return NULL; } jfieldID fieldLoaders = jenv->GetFieldID(clazzApplicationLoaders,"mLoaders","Ljava/util/Map;"); if (ClearException(jenv)) { ALOG("Exception","No Field :%s","mLoaders"); return NULL; } jobject objLoaders = jenv->GetObjectField(objApplicationLoaders,fieldLoaders); if (ClearException(jenv)) { ALOG("Exception","No object :%s","mLoaders"); return NULL; } jclass clazzHashMap = jenv->GetObjectClass(objLoaders); jmethodID methodValues = jenv->GetMethodID(clazzHashMap,"values","()Ljava/util/Collection;"); jobject values = jenv->CallObjectMethod(objLoaders,methodValues); jclass clazzValues = jenv->GetObjectClass(values); jmethodID methodToArray = jenv->GetMethodID(clazzValues,"toArray","()[Ljava/lang/Object;"); if (ClearException(jenv)) { ALOG("Exception","No Method:%s","toArray"); return NULL; } jobjectArray classLoaders = (jobjectArray)jenv->CallObjectMethod(values,methodToArray); if (ClearException(jenv)) { ALOG("Exception","CallObjectMethod failed :%s","toArray"); return NULL; } int size = jenv->GetArrayLength(classLoaders); for(int i = 0 ; i < size ; i ++){ jobject classLoader = jenv->GetObjectArrayElement(classLoaders,i); jclass clazzCL = jenv->GetObjectClass(classLoader); jmethodID loadClass = jenv->GetMethodID(clazzCL,"loadClass","(Ljava/lang/String;)Ljava/lang/Class;"); jstring param = jenv->NewStringUTF(apn); jclass tClazz = (jclass)jenv->CallObjectMethod(classLoader,loadClass,param); if (ClearException(jenv)) { ALOG("Exception","No"); continue; } return tClazz; } ALOG("Exception","No"); return NULL; } bool HookDalvikMethod(jmethodID jmethod){ Method *method = (Method*)jmethod; SET_METHOD_FLAG(method, ACC_NATIVE); int argsSize = dvmComputeMethodArgsSize(method); if (!dvmIsStaticMethod(method)) argsSize++; method->registersSize = method->insSize = argsSize; if (dvmIsNativeMethod(method)) { method->nativeFunc = dvmResolveNativeMethod; method->jniArgInfo = computeJniArgInfo(&method->prototype); } } bool ClassMethodHook(HookInfo info){ JNIEnv *jenv = GetEnv(); jclass clazzTarget = jenv->FindClass(info.tClazz); if (ClearException(jenv)) { ALOG("Exception","ClassMethodHook[Can't find class:%s in bootclassloader",info.tClazz); clazzTarget = findAppClass(jenv,info.tClazz); if(clazzTarget == NULL){ ALOG("Exception","%s","Error in findAppClass"); return false; } } jmethodID method = jenv->GetMethodID(clazzTarget,info.tMethod,info.tMeihodSig); if(method==NULL){ ALOG("Exception","ClassMethodHook[Can't find method:%s",info.tMethod); return false; } /* if(isArt()){ HookArtMethod(jenv,method); }else{ HookDalvikMethod(method); } */ HookDalvikMethod(method); JNINativeMethod gMethod[] = { {info.tMethod, info.tMeihodSig, info.handleFunc}, }; if(info.handleFunc != NULL){ if (jenv->RegisterNatives(clazzTarget, gMethod, 1) < 0) { ALOG("RegisterNatives","err"); return false; } } DetachCurrent(); return true; } int Hook(){ init(); void* handle = dlopen("/data/local/libTest.so",RTLD_NOW); const char *dlopen_error = dlerror(); if(!handle){ ALOG("Error","cannt load plugin :%s",dlopen_error); return -1; } SetupFunc setup = (SetupFunc)dlsym(handle,"getpHookInfo"); const char *dlsym_error = dlerror(); if (dlsym_error) { ALOG("Error","Cannot load symbol 'getpHookInfo' :%s" , dlsym_error); dlclose(handle); return 1; } HookInfo *hookInfo; setup(&hookInfo); ALOG("LOG","Target Class:%s",hookInfo[0].tClazz); ALOG("LOG","Target Method:%s",hookInfo[0].tMethod); ClassMethodHook(hookInfo[0]); }這個代碼就有點多了,並且核心功能的代碼都是在這裏實現的。
首先來看一下Hook函數:
int Hook(){ init(); void* handle = dlopen("/data/local/libTest.so",RTLD_NOW); const char *dlopen_error = dlerror(); if(!handle){ ALOG("Error","cannt load plugin :%s",dlopen_error); return -1; } SetupFunc setup = (SetupFunc)dlsym(handle,"getpHookInfo"); const char *dlsym_error = dlerror(); if (dlsym_error) { ALOG("Error","Cannot load symbol 'getpHookInfo' :%s" , dlsym_error); dlclose(handle); return 1; } HookInfo *hookInfo; setup(&hookInfo); ALOG("LOG","Target Class:%s",hookInfo[0].tClazz); ALOG("LOG","Target Method:%s",hookInfo[0].tMethod); ClassMethodHook(hookInfo[0]); }這個函數中,咱們看到使用了dlopen系列的函數,主要是用來打開so文件,而後執行文件中的指定函數
咱們看到主要仍是執行getpHookInfo函數,咱們就去看一下這個函數的定義
Test.c
#include "native.h" #include <android/log.h> #include "stdio.h" #include "stdlib.h" #include "MethodHooker.h" #define log(a,b) __android_log_print(ANDROID_LOG_VERBOSE,a,b); #define log_(b) __android_log_print(ANDROID_LOG_VERBOSE,"JNI_LOG_INFO",b); int getpHookInfo(HookInfo** pInfo); JNIEXPORT void JNICALL Java_com_example_testar_InjectClassloader_hookMethodNative (JNIEnv * jenv, jobject jboj, jobject jobj, jclass jclazz, jint slot) { //log("TestAE","start Inject other process"); } JNIEXPORT jstring JNICALL test(JNIEnv *env, jclass clazz) { //__android_log_print(ANDROID_LOG_VERBOSE, "tag", "call <native_printf> in java"); return (*env)->NewStringUTF(env,"haha ");; } HookInfo hookInfos[] = { {"android/net/wifi/WifiInfo","getMacAddress","()Ljava/lang/String;",(void*)test}, //{"com/example/testar/MainActivity","test","()Ljava/lang/String;",(void*)test}, //{"android/app/ApplicationLoaders","getText","()Ljava/lang/CharSequence;",(void*)test}, }; int getpHookInfo(HookInfo** pInfo){ *pInfo = hookInfos; return sizeof(hookInfos) / sizeof(hookInfos[0]); }
int getpHookInfo(HookInfo** pInfo){ *pInfo = hookInfos; return sizeof(hookInfos) / sizeof(hookInfos[0]); }傳遞的參數是HookInfo的二級指針類型,咱們在看一下HookInfo類型的定義
MethodHooker.h
typedef struct{ const char *tClazz; const char *tMethod; const char *tMeihodSig; void *handleFunc; } HookInfo; typedef int(*SetupFunc)(HookInfo**); int Hook();HookInfo是一個結構體
有四個成員字段
tClazz:類的全稱
tMethod:方法名
tMethodSig:方法簽名
handleFounc:函數的指針
關於這四個字段的做用,咱們來看一下HookInfo的內容:
Test.c
HookInfo hookInfos[] = { {"android/net/wifi/WifiInfo","getMacAddress","()Ljava/lang/String;",(void*)test}, //{"com/example/testar/MainActivity","test","()Ljava/lang/String;",(void*)test}, //{"android/app/ApplicationLoaders","getText","()Ljava/lang/CharSequence;",(void*)test}, };這裏看到了,咱們如今須要修改Mac地址,Android中提供給個人的接口是WifiInfo這個類中的getMacAddress方法
第一個字段類的名稱:android/net/wifi/WifiInfo,是全稱
第二個字段方法名:getMacAddress
第三個字段方法的簽名:()Ljava/lang/String;
第四個字段函數指針:test函數
由於咱們是經過WifiInfo這個類中的getMacAddress方法來獲取Mac地址的
看一下test函數
JNIEXPORT jstring JNICALL test(JNIEnv *env, jclass clazz) { //__android_log_print(ANDROID_LOG_VERBOSE, "tag", "call <native_printf> in java"); return (*env)->NewStringUTF(env,"haha "); }這個函數直接返回一個字符串:"haha "
再回到MethodHooker.cpp中的Hook函數
int Hook(){ init(); void* handle = dlopen("/data/local/libTest.so",RTLD_NOW); const char *dlopen_error = dlerror(); if(!handle){ ALOG("Error","cannt load plugin :%s",dlopen_error); return -1; } SetupFunc setup = (SetupFunc)dlsym(handle,"getpHookInfo"); const char *dlsym_error = dlerror(); if (dlsym_error) { ALOG("Error","Cannot load symbol 'getpHookInfo' :%s" , dlsym_error); dlclose(handle); return 1; } HookInfo *hookInfo; setup(&hookInfo); ALOG("LOG","Target Class:%s",hookInfo[0].tClazz); ALOG("LOG","Target Method:%s",hookInfo[0].tMethod); ClassMethodHook(hookInfo[0]); }使用dlsym來獲取函數指針:
SetupFunc是一個函數指針類型的,在MethodHooker.h中定義的
typedef int(*SetupFunc)(HookInfo**);而後咱們就開始執行函數了
HookInfo *hookInfo; setup(&hookInfo);由於咱們以前看了getpHookInfo函數,他的參數是一個HookInfo的二級指針,因此能夠進行值傳遞的。
執行完這個函數以後,hookInfo就有值了
其實上面的那段代碼的功能就是:
獲取HookInfo類型的內容
下面在來看一下ClassMethodHook函數
咱們傳遞進去的是hookInfo[0],在Test.c代碼中,咱們定義了HookInfo數組,大小就是1,因此這裏就直接傳遞第一個元素值。
HookInfo hookInfos[] = { {"android/net/wifi/WifiInfo","getMacAddress","()Ljava/lang/String;",(void*)test}, //{"com/example/testar/MainActivity","test","()Ljava/lang/String;",(void*)test}, //{"android/app/ApplicationLoaders","getText","()Ljava/lang/CharSequence;",(void*)test}, };
bool ClassMethodHook(HookInfo info){ //獲取JNIEnv對象 JNIEnv *jenv = GetEnv(); //查找類 jclass clazzTarget = jenv->FindClass(info.tClazz); if (ClearException(jenv)) { ALOG("Exception","ClassMethodHook[Can't find class:%s in bootclassloader",info.tClazz); clazzTarget = findAppClass(jenv,info.tClazz); if(clazzTarget == NULL){ ALOG("Exception","%s","Error in findAppClass"); return false; } } //在類中查找方法 jmethodID method = jenv->GetMethodID(clazzTarget,info.tMethod,info.tMeihodSig); if(method==NULL){ ALOG("Exception","ClassMethodHook[Can't find method:%s",info.tMethod); return false; } //將這個方法變成native HookDalvikMethod(method); JNINativeMethod gMethod[] = { {info.tMethod, info.tMeihodSig, info.handleFunc}, }; //註冊native方法 if(info.handleFunc != NULL){ if (jenv->RegisterNatives(clazzTarget, gMethod, 1) < 0) { ALOG("RegisterNatives","err"); return false; } } DetachCurrent(); return true; }這個函數中有其餘的函數調用,咱們先來看看他們是怎麼定義的
一、GetEnv()
static JNIEnv *GetEnv() { int status; JNIEnv *envnow = NULL; status = g_JavaVM->GetEnv((void **)&envnow, JNI_VERSION_1_4); if(status < 0) { status = g_JavaVM->AttachCurrentThread(&envnow, NULL); if(status < 0) { return NULL; } g_bAttatedT = true; } return envnow; }這個函數的功能是經過JVM來獲取當前線程的JNIEnv對象,咱們知道JVM是進程級的,一個進程對應一個JVM,JNIEnv是線程級的,一個線程對應一個JNIEnv,由於這裏沒有使用上層Java中的native方法,因此沒法獲得JNIEnv對象,可是咱們能夠經過另外的一種方式:引入AndroidRuntime.h(這個系統頭文件),經過其中系統定義的函數來獲取JVM對象,有了JVM對象,就能夠獲得當前線程的JNIEnv對象了:
static JavaVM *g_JavaVM; void init() { g_bAttatedT = false; g_JavaVM = android::AndroidRuntime::getJavaVM(); }因此這裏就介紹了,之後若是在底層沒有和上層打交道,可是又想獲得JNIEnv對象,這就是一種方法。
二、findClass函數
static jclass findAppClass(JNIEnv *jenv,const char *apn){ //經過類的全稱來查找這個類,返回jclass對象 jclass clazzApplicationLoaders = jenv->FindClass("android/app/ApplicationLoaders"); jthrowable exception = jenv->ExceptionOccurred(); if (ClearException(jenv)) { ALOG("Exception","No class : %s", "android/app/ApplicationLoaders"); return NULL; } jfieldID fieldApplicationLoaders = jenv->GetStaticFieldID(clazzApplicationLoaders,"gApplicationLoaders","Landroid/app/ApplicationLoaders;"); if (ClearException(jenv)) { ALOG("Exception","No Static Field :%s","gApplicationLoaders"); return NULL; } jobject objApplicationLoaders = jenv->GetStaticObjectField(clazzApplicationLoaders,fieldApplicationLoaders); if (ClearException(jenv)) { ALOG("Exception","GetStaticObjectField is failed [%s","gApplicationLoaders"); return NULL; } jfieldID fieldLoaders = jenv->GetFieldID(clazzApplicationLoaders,"mLoaders","Ljava/util/Map;"); if (ClearException(jenv)) { ALOG("Exception","No Field :%s","mLoaders"); return NULL; } jobject objLoaders = jenv->GetObjectField(objApplicationLoaders,fieldLoaders); if (ClearException(jenv)) { ALOG("Exception","No object :%s","mLoaders"); return NULL; } jclass clazzHashMap = jenv->GetObjectClass(objLoaders); jmethodID methodValues = jenv->GetMethodID(clazzHashMap,"values","()Ljava/util/Collection;"); jobject values = jenv->CallObjectMethod(objLoaders,methodValues); jclass clazzValues = jenv->GetObjectClass(values); jmethodID methodToArray = jenv->GetMethodID(clazzValues,"toArray","()[Ljava/lang/Object;"); if (ClearException(jenv)) { ALOG("Exception","No Method:%s","toArray"); return NULL; } jobjectArray classLoaders = (jobjectArray)jenv->CallObjectMethod(values,methodToArray); if (ClearException(jenv)) { ALOG("Exception","CallObjectMethod failed :%s","toArray"); return NULL; } int size = jenv->GetArrayLength(classLoaders); for(int i = 0 ; i < size ; i ++){ jobject classLoader = jenv->GetObjectArrayElement(classLoaders,i); jclass clazzCL = jenv->GetObjectClass(classLoader); jmethodID loadClass = jenv->GetMethodID(clazzCL,"loadClass","(Ljava/lang/String;)Ljava/lang/Class;"); jstring param = jenv->NewStringUTF(apn); jclass tClazz = (jclass)jenv->CallObjectMethod(classLoader,loadClass,param); if (ClearException(jenv)) { ALOG("Exception","No"); continue; } return tClazz; } ALOG("Exception","No"); return NULL; }這個函數的主要功能就是經過傳遞進來的類的全稱字符串,而後進行查找這個類,返回jclass.
這裏的原理是經過Android中的類加載器中來獲取這個類對象
其餘就沒什麼難度了,就是JNIEnv的操做,這個就和Java中反射機制很相似。
咱們看到函數中有一個這樣的類:
jclass clazzApplicationLoaders = jenv->FindClass("android/app/ApplicationLoaders");
/* * Copyright (C) 2006 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.app; import android.os.Trace; import android.util.ArrayMap; import dalvik.system.PathClassLoader; class ApplicationLoaders { public static ApplicationLoaders getDefault() { return gApplicationLoaders; } public ClassLoader getClassLoader(String zip, String libPath, ClassLoader parent) { /* * This is the parent we use if they pass "null" in. In theory * this should be the "system" class loader; in practice we * don't use that and can happily (and more efficiently) use the * bootstrap class loader. */ ClassLoader baseParent = ClassLoader.getSystemClassLoader().getParent(); synchronized (mLoaders) { if (parent == null) { parent = baseParent; } /* * If we're one step up from the base class loader, find * something in our cache. Otherwise, we create a whole * new ClassLoader for the zip archive. */ if (parent == baseParent) { ClassLoader loader = mLoaders.get(zip); if (loader != null) { return loader; } Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, zip); PathClassLoader pathClassloader = new PathClassLoader(zip, libPath, parent); Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); mLoaders.put(zip, pathClassloader); return pathClassloader; } Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, zip); PathClassLoader pathClassloader = new PathClassLoader(zip, parent); Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); return pathClassloader; } } private final ArrayMap<String, ClassLoader> mLoaders = new ArrayMap<String, ClassLoader>(); private static final ApplicationLoaders gApplicationLoaders = new ApplicationLoaders(); }這個類的做用就是用來維護應用中的類加載器
看到他有一個私有的變量mLoaders.是ArrayMap類型的
ArrayMap類型就把他當作是ArrayList和Map的結合體。具體使用自行研究。
這個mLoaders變量中維護了ClassLoader對象,如今咱們就須要獲取這個ClassLoader對象
其中key是類的全稱,value就是類加載器
由於這個類是包訪問權限,又是單例模式,咱們只能去調用他的getDefault方法,獲得其對象,而後在獲取他的mLoaders變量值
好了關於findClass函數的後續代碼我就不解讀了,由於沒什麼難度,說白了就是反射機制
1)、經過反射獲取ApplicationLoaders對象中的mLoaders值
2)、經過反射去獲取mLoaders中指定key的類加載器ClassLoader對象
3)、而後經過反射去調用類加載器中的loadClass方法,返回一個jclass對象,最後返回便可
三、HookDalvikMethod函數
bool HookDalvikMethod(jmethodID jmethod){ Method *method = (Method*)jmethod; //將方法method設置變成native SET_METHOD_FLAG(method, ACC_NATIVE); //計算這個native方法須要的空間大小 int argsSize = dvmComputeMethodArgsSize(method); if (!dvmIsStaticMethod(method)) argsSize++; method->registersSize = method->insSize = argsSize; if (dvmIsNativeMethod(method)) { method->nativeFunc = dvmResolveNativeMethod; method->jniArgInfo = computeJniArgInfo(&method->prototype); } }這個函數代碼很少,可是他的功能是最關鍵的。
將傳遞進來的jmethodID方法變成native方法
這個就是能夠將一個非native方法變成一個native方法
其實這段代碼中有幾個重要的函數:
SET_METHOD_FLAG
dvmComputeMethodArgsSize
dvmIsStaticMethod
dvmIsNativeMethos
devResolveNativeMethod
這些函數都是在系統中定義的,咱們須要引入這個頭文件:Dalvik.h
看完了,這些函數,咱們仍是須要回到咱們開始的地方ClassMethosHook函數:
bool ClassMethodHook(HookInfo info){ //獲取JNIEnv對象 JNIEnv *jenv = GetEnv(); //查找類 jclass clazzTarget = jenv->FindClass(info.tClazz); if (ClearException(jenv)) { ALOG("Exception","ClassMethodHook[Can't find class:%s in bootclassloader",info.tClazz); clazzTarget = findAppClass(jenv,info.tClazz); if(clazzTarget == NULL){ ALOG("Exception","%s","Error in findAppClass"); return false; } } //在類中查找方法 jmethodID method = jenv->GetMethodID(clazzTarget,info.tMethod,info.tMeihodSig); if(method==NULL){ ALOG("Exception","ClassMethodHook[Can't find method:%s",info.tMethod); return false; } //將這個方法變成native HookDalvikMethod(method); JNINativeMethod gMethod[] = { {info.tMethod, info.tMeihodSig, info.handleFunc}, }; //註冊native方法 if(info.handleFunc != NULL){ if (jenv->RegisterNatives(clazzTarget, gMethod, 1) < 0) { ALOG("RegisterNatives","err"); return false; } } DetachCurrent(); return true; }還有一部分,調用JNIEnv對象中的RegisterNatives方法,進行註冊native方法。
上面的代碼咱們就看完了
下面來總結一下流程吧
一、首先執行inject.c中的main函數,在這個函數中咱們將咱們本身的libso.so文件注入到目標進程中,而後執行InjectInterface函數
二、在InjectInterface函數中,咱們在執行MethodHooker.cpp中的Hook函數
三、在Hook函數中,咱們經過dlopen函數打開libTest.so文件,而後執行其中的getpHookInfo函數,獲取HookInfo結構體類型的內容
四、在getpHookInfo函數中主要的功能是將初始化好的HookInfo結構體返回給Hook函數中
五、在Hook函數中拿到getpHookInfo函數返回的HookInfo結構體內容,而後開始作兩部份內容
A:將結構體中的字段tMethod標示的方法變成native的
在這個過程當中,咱們首先須要獲取到這個方法所在的類,而後經過這個類來獲得jmethod對象,而後進行操做
B:將結構體中的字段tMethod標示的方法和字段handleFunc進行關聯註冊,調用JNIEnv對象中的RegisterNatives函數
如今咱們會想一下爲何咱們要這麼作呢?先把方法變成native的,而後在進行註冊
這個就須要瞭解一下Dalvik在執行指定方法的流程了
Dalvik在執行函數時會先調用dvmIsNativeMethod來判斷一個method是不是native方法。若是是native函數的話,那麼它所指向的一個Method對象的成員變量nativeFunc就指向該JNI方法的地址,所以就能夠直接對它進行調用。不然的話,就說明參數method描述的是一個Java函數,這時候就須要繼續調用函數dvmInterpret來執行它的代碼。所以咱們能夠把一個非native的java函數變成native method,讓Dalvik執行咱們的native方法而達到hook的目的。
在來看一下loadMethodFromDex源碼:
if (pDexCode != NULL) { /* integer constants, copy over for faster access */ meth->registersSize = pDexCode->registersSize; meth->insSize = pDexCode->insSize; meth->outsSize = pDexCode->outsSize; /* pointer to code area */ meth->insns = pDexCode->insns; } else { /* * We don't have a DexCode block, but we still want to know how * much space is needed for the arguments (so we don't have to * compute it later). We also take this opportunity to compute * JNI argument info. * * We do this for abstract methods as well, because we want to * be able to substitute our exception-throwing "stub" in. */ int argsSize = dvmComputeMethodArgsSize(meth); if (!dvmIsStaticMethod(meth)) argsSize++; meth->registersSize = meth->insSize = argsSize; assert(meth->outsSize == 0); assert(meth->insns == NULL); if (dvmIsNativeMethod(meth)) { meth->nativeFunc = dvmResolveNativeMethod; meth->jniArgInfo = computeJniArgInfo(&meth->prototype); } }咱們直接看else中的代碼:
該函數會從dex 文件中解析DexMethod 成dalvik中執行的method,if(pDexCode != NULL) 判斷是否存在dex代碼,看else部分說明,能夠知道該部分是dalvik對java native method處理過程。
其中dvmResolveNativeMethod調用了dvmLookupInternalNativeMethod和lookupSharedLibMethod來查找jni中註冊的native函數。 dalvik最後將執行獲得的java native函數.
經過上面的代碼片斷,咱們瞭解到要對一個java函數進行hook須要步驟有
[1] 把修改method的屬性修改爲native
[2] 修改method的registersSize、insSize、nativeFunc、computeJniArgInfo
[3] RegisterNatives註冊目標method的native函數
好了,到這裏咱們就把代碼都分析完了,原理也說清楚了,下面就開始動手測試了。
從上面咱們能夠看到在源文件中咱們引入了不少系統的頭文件,因此在這裏編譯會報錯的,因此咱們須要將這些頭文件拷貝到編譯工程中來,可是在次編譯仍是有問題,由於只有頭文件,沒有實現仍是報錯的,因此咱們須要把頭文件的實現也導入進來,這時候咱們就須要去Android系統中拷貝這些so文件了(是對這些頭文件的實現,而後編譯成動態庫so,咱們任然可使用的)。這些so文件是不少的,可是有一個規律的,就是每一個so文件的名字是:lib+頭文件名.so。好比AndroidRuntime.h頭文件對應的實現文件:
libandroid_runtime.so,簡單吧,那麼這些so文件咱們從哪裏進行拷貝呢?咱們能夠啓動一個Android模擬器,而後從模擬器的
/system/lib/目錄下進行拷貝:
這裏爲了防止出錯,把lib文件夾都拷貝過來了。
下面就能夠進行編譯了
看一下Android.mk文件
LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE:= so LOCAL_SRC_FILES := so.cpp MethodHooker.cpp LOCAL_LDLIBS+= LOCAL_CFLAGS := -I./include/ -I./dalvik/vm/ -I./dalvik -DHAVE_LITTLE_ENDIAN LOCAL_LDFLAGS := -L./lib/ -L$(SYSROOT)/usr/lib -llog -ldvm -landroid_runtime -lart LOCAL_STATIC_LIBRARIES := hookart LOCAL_SHARED_LIBRARIES := include $(BUILD_SHARED_LIBRARY) #------------------------------------------------------------------------ include $(CLEAR_VARS) LOCAL_MODULE:= Test LOCAL_SRC_FILES := Test.c LOCAL_LDLIBS+= -L./lib -llog LOCAL_CFLAGS := -I./include/ -I./dalvik/vm/ -I./dalvik -fPIC -shared LOCAL_SHARED_LIBRARIES := include $(BUILD_SHARED_LIBRARY) #------------------------------------------------------------------------ include $(CLEAR_VARS) LOCAL_MODULE:= inject LOCAL_SRC_FILES := inject.c shellcode.s LOCAL_LDLIBS := LOCAL_CFLAGS := include $(BUILD_EXECUTABLE)這裏對so.cpp,Test.c,inject.c進行編譯。
看一下so.cpp的編譯模塊
LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE:= so LOCAL_SRC_FILES := so.cpp MethodHooker.cpp LOCAL_LDLIBS+= LOCAL_CFLAGS := -I./include/ -I./dalvik/vm/ -I./dalvik -DHAVE_LITTLE_ENDIAN LOCAL_LDFLAGS := -L./lib/ -L$(SYSROOT)/usr/lib -llog -ldvm -landroid_runtime -lart LOCAL_STATIC_LIBRARIES := hookart LOCAL_SHARED_LIBRARIES := include $(BUILD_SHARED_LIBRARY)咱們須要用到的源文件爲:so.cpp、MethodHooker.cpp
編譯的過程當中咱們須要引入的頭文件咱們都放到了include文件夾下:
因此寫法很簡單:
LOCAL_CFLAGS := -I./include/ -I./dalvik/vm/ -I./dalvik -DHAVE_LITTLE_ENDIAN還有一些頭文件放在dalvik文件夾下
這樣就引入了須要的頭文件
還須要導入so文件路徑:
這裏我把模擬器中的整個lib文件夾都拷貝過來了
LOCAL_LDFLAGS := -L./lib/ -L$(SYSROOT)/usr/lib -llog -ldvm -landroid_runtime -lart
這樣就能夠編譯so.cpp了
後面的Test.c和inject.c編譯方法相似,這裏就不解釋了。
注:這裏其實說到了一種引入系統頭文件的編譯方式,以前在個人另一篇文章中:
http://blog.csdn.net/jiangwei0910410003/article/details/40949475
在這篇文章中,我用的方式是將so文件拷貝到NDK的目錄中的。
可是這個方式貌似更方便點,並且移植性比較好。自己就是一個項目了,不須要額外的工做就能夠編譯這個項目了。
編譯:
項目的下載地址:http://download.csdn.net/detail/jiangwei0910410003/8263113
由於每一個人的編譯環境都是不同的,因此若是在編譯過程當中遇到什麼問題,請給我留言,我儘可能幫助解決一下。
編譯工做完成以後,咱們應該有三個文件:
inject
libTest.so
libso.so
下面咱們須要將這三個文件拷貝到設備的/data/local/目錄下,爲何要拷貝到這個目錄呢?由於上面代碼中寫的是這個目錄呀。不記得的同窗在回過頭去看一下代碼:inject.c中的main函數中以及so.cpp中的Hook函數中
咱們先將這三個文件拷貝到指定的磁盤中(這裏我是Q盤)
開始拷貝:
adb push inject /data/local/
adb push libso.so /data/local/
adb push libTest.so /data/local/
在修改一下他們的權限
chmod 777 inject
chmod 777 libso.so
chmod 777 libTest.so
固然咱們還能夠寫一個簡單的腳本文件一步到位
adb push ..\libs\armeabi\libTest.so /data/local/ adb push ..\libs\armeabi\libso.so /data/local/ adb push ..\libs\armeabi\inject /data/local/ adb shell chmod 777 /data/local/inject adb shell chmod 777 /data/local/libso.so adb shell chmod 777 /data/local/libTest.so adb shell su -c /data/local/inject pause保存.bat文件,而後放到編譯項目的目錄下,直接運行便可。
注意應用的包名爲:com.example.testar
這個在inject.c中的main函數中咱們寫死了這個,由於這個包名就是進程名,咱們須要經過進程名來獲取進程id的。
運行結果:
這時候咱們開啓三個終端:
第一個終端:執行inject程序進行注入
./inject
第二個終端:監聽log信息
adb logcat -s LOG
這個log信息是底層打印的結果的
第三個終端:監聽log信息
adb logcat -s DEMO
這時候咱們會發現,打印的結果是"haha ",那麼咱們就成功了修改了系統返回的Mac地址了。
這個結果實際上是底層test函數返回的結果。說明系統在執行getMacAddress()方法的時候,其實調用了咱們在底層定義的test函數。
感受很爽,咱們既然能夠修改系統返回的一些設備信息了。哈哈!!
一樣的咱們能夠修改系統返回的IMEI等信息。
咱們是將WifiInfo類中的getMacAddress()方法首先變成native方法,而後再將底層的test函數和這個方法進行一一對應進行註冊。
系統在執行這個getMacAddress()方法的時候,發現他是一個native方法,就會去執行其對應的jni函數,因此這裏就作到了經過進程注入來修改系統方法返回的結果。
上面的例子算是結束了,也達到了咱們的需求了,下面在繼續看
上面咱們將系統調用的getMacAddress()方法執行的過程轉化成執行test函數了,可是這個test是在底層實現的,如今假如咱們想在上層去修改這個具體的返回值,那不能修改一次,就去從新編譯底層項目,而後還有拷貝工做,同時還須要從新注入。這個操做就太複雜了,因此咱們須要將這些工做移動到上層應用來。因此咱們能夠這麼作:
由於在上面的代碼中咱們看到即便上層沒有native方法,也能夠獲取到JNIEnv對象的,那麼咱們仍是用這個JNIEnv對象經過反射機制,去獲取調用上層的方法來獲取值。
這裏因爲篇幅的緣由就不在演示了,代碼實現起來不難。
終於說完了,其實這個問題我早就接觸到了,只是一直沒有時間去解決,今天就有點時間,爭取把他搞定,我之因此說這個問題。緣由是如今網上有兩個流行的框架:Xposed和Cydia,他們的做用就是注入到各類進程:
注入到系統進程修改系統的各類api值
注入到用戶進程修改特定方法的返回值,從而作到破解的效果:好比如今又一個遊戲金幣的遊戲,那麼我只要知道這個遊戲金幣的獲取方法,好比是:getMoney()相似的方法,那麼我就能夠用這個方法進行注入到這個遊戲進程,而後修改這個方法的返回值。那麼就能夠獲取到用不完的金幣了,固然這個提及來很容易,當用這個框架去操做的時候,會發現有不少問題的。這個我在後面的文章中會用這個框架進行操做的。
那麼我如今想說的是:其實這兩個框架的實現原理就是我今天講的這種方式實現的,只是上面的兩個框架在效率上比我這個好多了,優化工做也作的很好。我說了這篇文章就是想去解析他的原理。
若是你想去替換一個進程中運行的api:
將這個api方法變成native的,而後在用一個方法將其進行註冊
緣由就是虛擬機在運行的時候,發現這個api方法若是是native的話,就去執行它註冊以後的那個jni方法了。
在這篇文章中咱們學習到了幾個知識點:
一、如何將一個非native方法變成一個native方法
二、如何手動的去註冊一個native方法
三、學會了使用另外的一種編譯項目的方式(引入頭文件)
四、注入進程的相關知識
(PS:總算是說完了,文章中說道的項目已經給出下載地址了,想試驗的同窗,能夠嘗試編譯,若是在這個過程當中遇到什麼問題,能夠給我留言,我儘可能解決一下~~)