Android中通过进程注入技术修改系统返回的Mac地址

致谢

感谢看雪论坛中的这位大神,分享了这个技术:http://bbs.pediy.com/showthread.php?t=186054,从这篇文章中学习到了很多内容,如果没有这篇好文章,我在研究的过程中会遇到很多困难,说不定我就放弃了~~在此感谢他。


前言

之前的几篇文章都是在介绍了OC的相关知识,之前的半个月也都是在搞IOS的相关东西,白天上班做Android工作,晚上回家还有弄IOS,感觉真的很伤了。不过OC的知识也学习了差不多了。不过在这段时间遗留了很多Android方面的问题都没有进行解决和总结。所以从这段时间开始,需要将解决的问题都总结一下吧。


问题

Android中通过注入技术修改系统返回的Mac地址


技术准备

下面来看一下这个技术需要哪些知识点

1、如何将非native方法变成native方法

2、如何将native方法直接注册(不需要jni这样的头文件了)

3、Android中的类加载器相关知识

4、如何编译Android系统引用系统头文件的NDK项目

虽然这里有这四个知识点,但是其中有两个我在之前的blog中已经介绍了:

Android中的类加载器:http://blog.csdn.net/jiangwei0910410003/article/details/41384667

如何编译Android系统引用系统头文件的NDK项目:http://blog.csdn.net/jiangwei0910410003/article/details/40949475

不过在这篇文章中,我们在介绍一种新的编译方式


项目测试

第一、Android项目

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. package com.example.testar;  
  2.   
  3. import android.net.wifi.WifiInfo;  
  4. import android.net.wifi.WifiManager;  
  5. import android.os.Bundle;  
  6. import android.app.Activity;  
  7. import android.content.Context;  
  8. import android.util.Log;  
  9. import android.view.Menu;  
  10. import android.view.View;  
  11. import android.widget.Button;  
  12.   
  13. public class MainActivity extends Activity {  
  14.       
  15.     @Override  
  16.     protected void onCreate(Bundle savedInstanceState) {  
  17.         super.onCreate(savedInstanceState);  
  18.         setContentView(R.layout.activity_main);  
  19.   
  20.         Button btn = (Button) findViewById(R.id.button1);  
  21.         btn.setOnClickListener(new View.OnClickListener() {  
  22.   
  23.             @Override  
  24.             public void onClick(View v) {  
  25.                 WifiManager wifi = (WifiManager) getSystemService(Context.WIFI_SERVICE);  
  26.                 WifiInfo info = wifi.getConnectionInfo();  
  27.                 System.out.println("Wifi mac :" + info.getMacAddress());  
  28.                 Log.d("DEMO""Wifi mac:"+info.getMacAddress());  
  29.             }  
  30.         });  
  31.     }  
  32.   
  33. }  
我们看到,这里的代码很简单,就是打印一下设备的Mac地址,现在我们要做的就是:注入这个Demo进程,然后修改Mac的值。


第二、底层的实现

首先来看一下inject.c

这个是注入进程的核心文件,由于代码比较多,这里只看核心的部分:

[cpp] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. int main(int argc, char** argv) {  
  2.     char *pn = "com.example.testar";  
  3.     char *is = "/data/local/libso.so";  
  4.     printf("%s\n",pn);  
  5.     printf("%s\n",is);  
  6.       
  7.     pid_t target_pid;  
  8.     target_pid = find_pid_of(pn);  
  9.     printf("pid: %d\n",target_pid);  
  10.     int ret = inject_remote_process(target_pid, is, "InjectInterface", (void*)"I'm parameter!", strlen("I'm parameter!") );  
  11.     printf("result: %d\n",ret);  
  12. }  
就是他的主函数代码

我们看到有一个重要的函数:

inject_remote_process

第一个参数:注入进程的id

第二个参数:需要注入到目标进程的so文件

第三个参数:so文件中需要执行的函数名

第四个参数:执行函数的参数

第五个参数:执行函数的参数的长度


主要还是前面三个参数。

这里我们通过find_pid_of(pn)函数来获取进程id

传递进去的是进程名

Android中应用的进程名就是包名

[cpp] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. char *pn = "com.example.testar";  
看到上面注入到目标进程的so文件

[cpp] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. char *is = "/data/local/libso.so";  
下面再来看一下这个so文件的源代码

so.cpp

[cpp] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. #include "jni.h"  
  2. #include <android_runtime/AndroidRuntime.h>  
  3. #include "android/log.h"  
  4. #include "stdio.h"  
  5. #include "stdlib.h"  
  6. #include "MethodHooker.h"  
  7. #include <utils/CallStack.h>  
  8. #define log(a,b) __android_log_write(ANDROID_LOG_INFO,a,b); // LOGÀàÐÍ:info  
  9. #define log_(b) __android_log_write(ANDROID_LOG_INFO,"JNI_LOG_INFO",b); // LOGÀàÐÍ:info  
  10.   
  11.   
  12. extern "C" void InjectInterface(char*arg){  
  13.     log_("*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*");  
  14.     log_("*-*-*-*-*-* Injected so *-*-*-*-*-*-*-*");  
  15.     log_("*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*");  
  16.     Hook();  
  17.     log_("*-*-*-*-*-*-*- End -*-*-*-*-*-*-*-*-*-*");  
  18. }  
  19.   
  20. extern "C" JNIEXPORT jstring JNICALL Java_com_example_testar_InjectApplication_test(JNIEnv *env, jclass clazz)  
  21. {  
  22.     return env->NewStringUTF("haha ");  
  23. }  
在这个文件中,我们看到了函数InjectInterface了,因为so是C++程序,但是inject是C程序,为了兼容,就有这种方式了

extern "C"  函数{

//do something

}

这个代码没什么难度和复杂性

这个函数中调用了Hook函数,下面在来看一下Hook函数的定义


MethodHooker.cpp的实现

[cpp] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. #include "MethodHooker.h"  
  2. #include "jni.h"  
  3. #include "android_runtime/AndroidRuntime.h"  
  4. #include "android/log.h"  
  5. #include "stdio.h"  
  6. #include "stdlib.h"  
  7. #include "native.h"  
  8. #include <dlfcn.h>  
  9. #define ANDROID_SMP 0  
  10. #include "Dalvik.h"  
  11. #include "alloc/Alloc.h"  
  12.   
  13. #define ALOG(...) __android_log_print(ANDROID_LOG_VERBOSE, __VA_ARGS__)  
  14.   
  15. static bool g_bAttatedT;  
  16. static JavaVM *g_JavaVM;  
  17.   
  18. void init()  
  19. {  
  20.     g_bAttatedT = false;  
  21.     g_JavaVM = android::AndroidRuntime::getJavaVM();  
  22. }  
  23.   
  24. static JNIEnv *GetEnv()  
  25. {  
  26.     int status;  
  27.     JNIEnv *envnow = NULL;  
  28.     status = g_JavaVM->GetEnv((void **)&envnow, JNI_VERSION_1_4);  
  29.     if(status < 0)  
  30.     {  
  31.         status = g_JavaVM->AttachCurrentThread(&envnow, NULL);  
  32.         if(status < 0)  
  33.         {  
  34.             return NULL;  
  35.         }  
  36.         g_bAttatedT = true;  
  37.     }  
  38.     return envnow;  
  39. }  
  40.   
  41. static void DetachCurrent()  
  42. {  
  43.     if(g_bAttatedT)  
  44.     {  
  45.         g_JavaVM->DetachCurrentThread();  
  46.     }  
  47. }  
  48.   
  49. static int computeJniArgInfo(const DexProto* proto)  
  50. {  
  51.     const char* sig = dexProtoGetShorty(proto);  
  52.     int returnType, jniArgInfo;  
  53.     u4 hints;  
  54.   
  55.     /* The first shorty character is the return type. */  
  56.     switch (*(sig++)) {  
  57.     case 'V':  
  58.         returnType = DALVIK_JNI_RETURN_VOID;  
  59.         break;  
  60.     case 'F':  
  61.         returnType = DALVIK_JNI_RETURN_FLOAT;  
  62.         break;  
  63.     case 'D':  
  64.         returnType = DALVIK_JNI_RETURN_DOUBLE;  
  65.         break;  
  66.     case 'J':  
  67.         returnType = DALVIK_JNI_RETURN_S8;  
  68.         break;  
  69.     case 'Z':  
  70.     case 'B':  
  71.         returnType = DALVIK_JNI_RETURN_S1;  
  72.         break;  
  73.     case 'C':  
  74.         returnType = DALVIK_JNI_RETURN_U2;  
  75.         break;  
  76.     case 'S':  
  77.         returnType = DALVIK_JNI_RETURN_S2;  
  78.         break;  
  79.     default:  
  80.         returnType = DALVIK_JNI_RETURN_S4;  
  81.         break;  
  82.     }  
  83.   
  84.     jniArgInfo = returnType << DALVIK_JNI_RETURN_SHIFT;  
  85.   
  86.     hints = dvmPlatformInvokeHints(proto);  
  87.   
  88.     if (hints & DALVIK_JNI_NO_ARG_INFO) {  
  89.         jniArgInfo |= DALVIK_JNI_NO_ARG_INFO;  
  90.     } else {  
  91.         assert((hints & DALVIK_JNI_RETURN_MASK) == 0);  
  92.         jniArgInfo |= hints;  
  93.     }  
  94.   
  95.     return jniArgInfo;  
  96. }  
  97.   
  98. int ClearException(JNIEnv *jenv){  
  99.     jthrowable exception = jenv->ExceptionOccurred();  
  100.     if (exception != NULL) {  
  101.         jenv->ExceptionDescribe();  
  102.         jenv->ExceptionClear();  
  103.         return true;  
  104.     }  
  105.     return false;  
  106. }  
  107.   
  108. bool isArt(){  
  109.     return true;  
  110. }  
  111.   
  112. static jclass findAppClass(JNIEnv *jenv,const char *apn){  
  113.     jclass clazzApplicationLoaders = jenv->FindClass("android/app/ApplicationLoaders");  
  114.     jthrowable exception = jenv->ExceptionOccurred();  
  115.     if (ClearException(jenv)) {  
  116.         ALOG("Exception","No class : %s""android/app/ApplicationLoaders");  
  117.         return NULL;  
  118.     }  
  119.     jfieldID fieldApplicationLoaders = jenv->GetStaticFieldID(clazzApplicationLoaders,"gApplicationLoaders","Landroid/app/ApplicationLoaders;");  
  120.     if (ClearException(jenv)) {  
  121.         ALOG("Exception","No Static Field :%s","gApplicationLoaders");  
  122.         return NULL;  
  123.     }  
  124.     jobject objApplicationLoaders = jenv->GetStaticObjectField(clazzApplicationLoaders,fieldApplicationLoaders);  
  125.     if (ClearException(jenv)) {  
  126.         ALOG("Exception","GetStaticObjectField is failed [%s","gApplicationLoaders");  
  127.         return NULL;  
  128.     }  
  129.     jfieldID fieldLoaders = jenv->GetFieldID(clazzApplicationLoaders,"mLoaders","Ljava/util/Map;");  
  130.     if (ClearException(jenv)) {  
  131.         ALOG("Exception","No Field :%s","mLoaders");  
  132.         return NULL;  
  133.     }  
  134.     jobject objLoaders = jenv->GetObjectField(objApplicationLoaders,fieldLoaders);  
  135.     if (ClearException(jenv)) {  
  136.         ALOG("Exception","No object :%s","mLoaders");  
  137.         return NULL;  
  138.     }  
  139.     jclass clazzHashMap = jenv->GetObjectClass(objLoaders);  
  140.     jmethodID methodValues = jenv->GetMethodID(clazzHashMap,"values","()Ljava/util/Collection;");  
  141.     jobject values = jenv->CallObjectMethod(objLoaders,methodValues);  
  142.   
  143.     jclass clazzValues = jenv->GetObjectClass(values);  
  144.     jmethodID methodToArray = jenv->GetMethodID(clazzValues,"toArray","()[Ljava/lang/Object;");  
  145.     if (ClearException(jenv)) {  
  146.         ALOG("Exception","No Method:%s","toArray");  
  147.         return NULL;  
  148.     }  
  149.   
  150.     jobjectArray classLoaders = (jobjectArray)jenv->CallObjectMethod(values,methodToArray);  
  151.     if (ClearException(jenv)) {  
  152.         ALOG("Exception","CallObjectMethod failed :%s","toArray");  
  153.         return NULL;  
  154.     }  
  155.   
  156.         int size = jenv->GetArrayLength(classLoaders);  
  157.   
  158.         for(int i = 0 ; i < size ; i ++){  
  159.             jobject classLoader = jenv->GetObjectArrayElement(classLoaders,i);  
  160.             jclass clazzCL = jenv->GetObjectClass(classLoader);  
  161.             jmethodID loadClass = jenv->GetMethodID(clazzCL,"loadClass","(Ljava/lang/String;)Ljava/lang/Class;");  
  162.             jstring param = jenv->NewStringUTF(apn);  
  163.             jclass tClazz = (jclass)jenv->CallObjectMethod(classLoader,loadClass,param);  
  164.             if (ClearException(jenv)) {  
  165.                 ALOG("Exception","No");  
  166.                 continue;  
  167.             }  
  168.             return tClazz;  
  169.         }  
  170.     ALOG("Exception","No");  
  171.     return NULL;  
  172. }  
  173.   
  174.   
  175.   
  176. bool HookDalvikMethod(jmethodID jmethod){  
  177.     Method *method = (Method*)jmethod;  
  178.     SET_METHOD_FLAG(method, ACC_NATIVE);  
  179.   
  180.     int argsSize = dvmComputeMethodArgsSize(method);  
  181.     if (!dvmIsStaticMethod(method))  
  182.         argsSize++;  
  183.   
  184.     method->registersSize = method->insSize = argsSize;  
  185.   
  186.     if (dvmIsNativeMethod(method)) {  
  187.         method->nativeFunc = dvmResolveNativeMethod;  
  188.         method->jniArgInfo = computeJniArgInfo(&method->prototype);  
  189.     }  
  190. }  
  191.   
  192. bool ClassMethodHook(HookInfo info){  
  193.   
  194.     JNIEnv *jenv = GetEnv();  
  195.   
  196.     jclass clazzTarget = jenv->FindClass(info.tClazz);  
  197.     if (ClearException(jenv)) {  
  198.         ALOG("Exception","ClassMethodHook[Can't find class:%s in bootclassloader",info.tClazz);  
  199.   
  200.         clazzTarget = findAppClass(jenv,info.tClazz);  
  201.         if(clazzTarget == NULL){  
  202.             ALOG("Exception","%s","Error in findAppClass");  
  203.             return false;  
  204.         }  
  205.     }  
  206.   
  207.     jmethodID method = jenv->GetMethodID(clazzTarget,info.tMethod,info.tMeihodSig);  
  208.     if(method==NULL){  
  209.         ALOG("Exception","ClassMethodHook[Can't find method:%s",info.tMethod);  
  210.         return false;  
  211.     }  
  212.   
  213.     /* 
  214.     if(isArt()){ 
  215.         HookArtMethod(jenv,method); 
  216.     }else{ 
  217.         HookDalvikMethod(method); 
  218.     } 
  219.     */  
  220.   
  221.     HookDalvikMethod(method);  
  222.   
  223.     JNINativeMethod gMethod[] = {  
  224.         {info.tMethod, info.tMeihodSig, info.handleFunc},  
  225.     };  
  226.   
  227.     if(info.handleFunc != NULL){  
  228.         if (jenv->RegisterNatives(clazzTarget, gMethod, 1) < 0) {  
  229.             ALOG("RegisterNatives","err");  
  230.             return false;  
  231.         }  
  232.     }  
  233.   
  234.     DetachCurrent();  
  235.     return true;  
  236. }  
  237.   
  238. int Hook(){  
  239.     init();  
  240.     void* handle = dlopen("/data/local/libTest.so",RTLD_NOW);  
  241.     const char *dlopen_error = dlerror();  
  242.     if(!handle){  
  243.         ALOG("Error","cannt load plugin :%s",dlopen_error);  
  244.         return -1;  
  245.     }  
  246.     SetupFunc setup = (SetupFunc)dlsym(handle,"getpHookInfo");  
  247.     const char *dlsym_error = dlerror();  
  248.     if (dlsym_error) {  
  249.         ALOG("Error","Cannot load symbol 'getpHookInfo' :%s" , dlsym_error);  
  250.         dlclose(handle);  
  251.         return 1;  
  252.     }  
  253.   
  254.     HookInfo *hookInfo;  
  255.     setup(&hookInfo);  
  256.   
  257.     ALOG("LOG","Target Class:%s",hookInfo[0].tClazz);  
  258.     ALOG("LOG","Target Method:%s",hookInfo[0].tMethod);  
  259.   
  260.     ClassMethodHook(hookInfo[0]);  
  261. }  
这个代码就有点多了,而且核心功能的代码都是在这里实现的。

首先来看一下Hook函数:

[cpp] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. int Hook(){  
  2.     init();  
  3.     void* handle = dlopen("/data/local/libTest.so",RTLD_NOW);  
  4.     const char *dlopen_error = dlerror();  
  5.     if(!handle){  
  6.         ALOG("Error","cannt load plugin :%s",dlopen_error);  
  7.         return -1;  
  8.     }  
  9.     SetupFunc setup = (SetupFunc)dlsym(handle,"getpHookInfo");  
  10.     const char *dlsym_error = dlerror();  
  11.     if (dlsym_error) {  
  12.         ALOG("Error","Cannot load symbol 'getpHookInfo' :%s" , dlsym_error);  
  13.         dlclose(handle);  
  14.         return 1;  
  15.     }  
  16.   
  17.     HookInfo *hookInfo;  
  18.     setup(&hookInfo);  
  19.   
  20.     ALOG("LOG","Target Class:%s",hookInfo[0].tClazz);  
  21.     ALOG("LOG","Target Method:%s",hookInfo[0].tMethod);  
  22.   
  23.     ClassMethodHook(hookInfo[0]);  
  24. }  
这个函数中,我们看到使用了dlopen系列的函数,主要是用来打开so文件,然后执行文件中的指定函数


我们看到主要还是执行getpHookInfo函数,我们就去看一下这个函数的定义

Test.c

[cpp] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. #include "native.h"  
  2. #include <android/log.h>  
  3. #include "stdio.h"  
  4. #include "stdlib.h"  
  5. #include "MethodHooker.h"  
  6.   
  7. #define log(a,b) __android_log_print(ANDROID_LOG_VERBOSE,a,b);   
  8. #define log_(b) __android_log_print(ANDROID_LOG_VERBOSE,"JNI_LOG_INFO",b);   
  9.   
  10. int getpHookInfo(HookInfo** pInfo);  
  11.   
  12. JNIEXPORT void JNICALL Java_com_example_testar_InjectClassloader_hookMethodNative  
  13.   (JNIEnv * jenv, jobject jboj, jobject jobj, jclass jclazz, jint slot)  
  14. {  
  15.     //log("TestAE","start Inject other process");  
  16. }  
  17.   
  18. JNIEXPORT jstring JNICALL test(JNIEnv *env, jclass clazz)    
  19. {    
  20.     //__android_log_print(ANDROID_LOG_VERBOSE, "tag", "call <native_printf> in java");  
  21.     return (*env)->NewStringUTF(env,"haha ");;  
  22. }  
  23.   
  24. HookInfo hookInfos[] = {  
  25.         {"android/net/wifi/WifiInfo","getMacAddress","()Ljava/lang/String;",(void*)test},  
  26.         //{"com/example/testar/MainActivity","test","()Ljava/lang/String;",(void*)test},  
  27.         //{"android/app/ApplicationLoaders","getText","()Ljava/lang/CharSequence;",(void*)test},  
  28. };  
  29.   
  30. int getpHookInfo(HookInfo** pInfo){  
  31.     *pInfo = hookInfos;  
  32.     return sizeof(hookInfos) / sizeof(hookInfos[0]);  
  33. }  

看一下getHookInfo函数

[cpp] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. int getpHookInfo(HookInfo** pInfo){  
  2.     *pInfo = hookInfos;  
  3.     return sizeof(hookInfos) / sizeof(hookInfos[0]);  
  4. }  
传递的参数是HookInfo的二级指针类型,我们在看一下HookInfo类型的定义


MethodHooker.h

[cpp] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. typedef struct{  
  2.     const char *tClazz;  
  3.     const char *tMethod;  
  4.     const char *tMeihodSig;  
  5.     void *handleFunc;  
  6. } HookInfo;  
  7.   
  8. typedef int(*SetupFunc)(HookInfo**);  
  9.   
  10. int Hook();  
HookInfo是一个结构体

有四个成员字段

tClazz:类的全称

tMethod:方法名

tMethodSig:方法签名

handleFounc:函数的指针

关于这四个字段的作用,我们来看一下HookInfo的内容:

Test.c

[cpp] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. HookInfo hookInfos[] = {  
  2.         {"android/net/wifi/WifiInfo","getMacAddress","()Ljava/lang/String;",(void*)test},  
  3.         //{"com/example/testar/MainActivity","test","()Ljava/lang/String;",(void*)test},  
  4.         //{"android/app/ApplicationLoaders","getText","()Ljava/lang/CharSequence;",(void*)test},  
  5. };  
这里看到了,我们现在需要修改Mac地址,Android中提供给我的的接口是WifiInfo这个类中的getMacAddress方法

第一个字段类的名称:android/net/wifi/WifiInfo,是全称

第二个字段方法名:getMacAddress

第三个字段方法的签名:()Ljava/lang/String;

第四个字段函数指针:test函数

因为我们是通过WifiInfo这个类中的getMacAddress方法来获取Mac地址的


看一下test函数

[cpp] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. JNIEXPORT jstring JNICALL test(JNIEnv *env, jclass clazz)    
  2. {    
  3.     //__android_log_print(ANDROID_LOG_VERBOSE, "tag", "call <native_printf> in java");  
  4.     return (*env)->NewStringUTF(env,"haha ");  
  5. }  
这个函数直接返回一个字符串:"haha "


再回到MethodHooker.cpp中的Hook函数

[cpp] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. int Hook(){  
  2.     init();  
  3.     void* handle = dlopen("/data/local/libTest.so",RTLD_NOW);  
  4.     const char *dlopen_error = dlerror();  
  5.     if(!handle){  
  6.         ALOG("Error","cannt load plugin :%s",dlopen_error);  
  7.         return -1;  
  8.     }  
  9.     SetupFunc setup = (SetupFunc)dlsym(handle,"getpHookInfo");  
  10.     const char *dlsym_error = dlerror();  
  11.     if (dlsym_error) {  
  12.         ALOG("Error","Cannot load symbol 'getpHookInfo' :%s" , dlsym_error);  
  13.         dlclose(handle);  
  14.         return 1;  
  15.     }  
  16.   
  17.     HookInfo *hookInfo;  
  18.     setup(&hookInfo);  
  19.   
  20.     ALOG("LOG","Target Class:%s",hookInfo[0].tClazz);  
  21.     ALOG("LOG","Target Method:%s",hookInfo[0].tMethod);  
  22.   
  23.     ClassMethodHook(hookInfo[0]);  
  24. }  
使用dlsym来获取函数指针:

SetupFunc是一个函数指针类型的,在MethodHooker.h中定义的

[cpp] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. typedef int(*SetupFunc)(HookInfo**);  
然后我们就开始执行函数了
[cpp] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. HookInfo *hookInfo;  
  2. setup(&hookInfo);  
因为我们之前看了getpHookInfo函数,他的参数是一个HookInfo的二级指针,所以可以进行值传递的。

执行完这个函数之后,hookInfo就有值了

其实上面的那段代码的功能就是:

获取HookInfo类型的内容


下面在来看一下ClassMethodHook函数

我们传递进去的是hookInfo[0],在Test.c代码中,我们定义了HookInfo数组,大小就是1,所以这里就直接传递第一个元素值。

[cpp] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. HookInfo hookInfos[] = {  
  2.         {"android/net/wifi/WifiInfo","getMacAddress","()Ljava/lang/String;",(void*)test},  
  3.         //{"com/example/testar/MainActivity","test","()Ljava/lang/String;",(void*)test},  
  4.         //{"android/app/ApplicationLoaders","getText","()Ljava/lang/CharSequence;",(void*)test},  
  5. };  


看一下ClassMethodHook函数的定义
[cpp] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. bool ClassMethodHook(HookInfo info){  
  2.   
  3.     //获取JNIEnv对象  
  4.     JNIEnv *jenv = GetEnv();  
  5.   
  6.     //查找类  
  7.     jclass clazzTarget = jenv->FindClass(info.tClazz);  
  8.     if (ClearException(jenv)) {  
  9.         ALOG("Exception","ClassMethodHook[Can't find class:%s in bootclassloader",info.tClazz);  
  10.   
  11.         clazzTarget = findAppClass(jenv,info.tClazz);  
  12.         if(clazzTarget == NULL){  
  13.             ALOG("Exception","%s","Error in findAppClass");  
  14.             return false;  
  15.         }  
  16.     }  
  17.   
  18.     //在类中查找方法  
  19.     jmethodID method = jenv->GetMethodID(clazzTarget,info.tMethod,info.tMeihodSig);  
  20.     if(method==NULL){  
  21.         ALOG("Exception","ClassMethodHook[Can't find method:%s",info.tMethod);  
  22.         return false;  
  23.     }  
  24.   
  25.     //将这个方法变成native  
  26.     HookDalvikMethod(method);  
  27.   
  28.     JNINativeMethod gMethod[] = {  
  29.         {info.tMethod, info.tMeihodSig, info.handleFunc},  
  30.     };  
  31.   
  32.     //注册native方法  
  33.     if(info.handleFunc != NULL){  
  34.         if (jenv->RegisterNatives(clazzTarget, gMethod, 1) < 0) {  
  35.             ALOG("RegisterNatives","err");  
  36.             return false;  
  37.         }  
  38.     }  
  39.   
  40.     DetachCurrent();  
  41.     return true;  
  42. }  
这个函数中有其他的函数调用,我们先来看看他们是怎么定义的

1、GetEnv()

[cpp] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. static JNIEnv *GetEnv()  
  2. {  
  3.     int status;  
  4.     JNIEnv *envnow = NULL;  
  5.     status = g_JavaVM->GetEnv((void **)&envnow, JNI_VERSION_1_4);  
  6.     if(status < 0)  
  7.     {  
  8.         status = g_JavaVM->AttachCurrentThread(&envnow, NULL);  
  9.         if(status < 0)  
  10.         {  
  11.             return NULL;  
  12.         }  
  13.         g_bAttatedT = true;  
  14.     }  
  15.     return envnow;  
  16. }  
这个函数的功能是通过JVM来获取当前线程的JNIEnv对象,我们知道JVM是进程级的,一个进程对应一个JVM,JNIEnv是线程级的,一个线程对应一个JNIEnv,因为这里没有使用上层Java中的native方法,所以无法得到JNIEnv对象,但是我们可以通过另外的一种方式:引入AndroidRuntime.h(这个系统头文件),通过其中系统定义的函数来获取JVM对象,有了JVM对象,就可以得到当前线程的JNIEnv对象了:
[cpp] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. static JavaVM *g_JavaVM;  
  2.   
  3. void init()  
  4. {  
  5.     g_bAttatedT = false;  
  6.     g_JavaVM = android::AndroidRuntime::getJavaVM();  
  7. }  
所以这里就介绍了,以后如果在底层没有和上层打交道,但是又想得到JNIEnv对象,这就是一种方法。


2、findClass函数

[cpp] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. static jclass findAppClass(JNIEnv *jenv,const char *apn){  
  2.     //通过类的全称来查找这个类,返回jclass对象  
  3.     jclass clazzApplicationLoaders = jenv->FindClass("android/app/ApplicationLoaders");  
  4.     jthrowable exception = jenv->ExceptionOccurred();  
  5.     if (ClearException(jenv)) {  
  6.         ALOG("Exception","No class : %s""android/app/ApplicationLoaders");  
  7.         return NULL;  
  8.     }  
  9.     jfieldID fieldApplicationLoaders =   
  10.             jenv->GetStaticFieldID(clazzApplicationLoaders,"gApplicationLoaders","Landroid/app/ApplicationLoaders;");  
  11.     if (ClearException(jenv)) {  
  12.         ALOG("Exception","No Static Field :%s","gApplicationLoaders");  
  13.         return NULL;  
  14.     }  
  15.     jobject objApplicationLoaders = jenv->GetStaticObjectField(clazzApplicationLoaders,fieldApplicationLoaders);  
  16.     if (ClearException(jenv)) {  
  17.         ALOG("Exception","GetStaticObjectField is failed [%s","gApplicationLoaders");  
  18.         return NULL;  
  19.     }  
  20.     jfieldID fieldLoaders = jenv->GetFieldID(clazzApplicationLoaders,"mLoaders","Ljava/util/Map;");  
  21.     if (ClearException(jenv)) {  
  22.         ALOG("Exception","No Field :%s","mLoaders");  
  23.         return NULL;  
  24.     }  
  25.     jobject objLoaders = jenv->GetObjectField(objApplicationLoaders,fieldLoaders);  
  26.     if (ClearException(jenv)) {  
  27.         ALOG("Exception","No object :%s","mLoaders");  
  28.         return NULL;  
  29.     }  
  30.       
  31.     jclass clazzHashMap = jenv->GetObjectClass(objLoaders);  
  32.     jmethodID methodValues = jenv->GetMethodID(clazzHashMap,"values","()Ljava/util/Collection;");  
  33.     jobject values = jenv->CallObjectMethod(objLoaders,methodValues);  
  34.   
  35.     jclass clazzValues = jenv->GetObjectClass(values);  
  36.     jmethodID methodToArray = jenv->GetMethodID(clazzValues,"toArray","()[Ljava/lang/Object;");  
  37.     if (ClearException(jenv)) {  
  38.         ALOG("Exception","No Method:%s","toArray");  
  39.         return NULL;  
  40.     }  
  41.   
  42.     jobjectArray classLoaders = (jobjectArray)jenv->CallObjectMethod(values,methodToArray);  
  43.     if (ClearException(jenv)) {  
  44.         ALOG("Exception","CallObjectMethod failed :%s","toArray");  
  45.         return NULL;  
  46.     }  
  47.   
  48.         int size = jenv->GetArrayLength(classLoaders);  
  49.   
  50.         for(int i = 0 ; i < size ; i ++){  
  51.             jobject classLoader = jenv->GetObjectArrayElement(classLoaders,i);  
  52.             jclass clazzCL = jenv->GetObjectClass(classLoader);  
  53.             jmethodID loadClass = jenv->GetMethodID(clazzCL,"loadClass","(Ljava/lang/String;)Ljava/lang/Class;");  
  54.             jstring param = jenv->NewStringUTF(apn);  
  55.             jclass tClazz = (jclass)jenv->CallObjectMethod(classLoader,loadClass,param);  
  56.             if (ClearException(jenv)) {  
  57.                 ALOG("Exception","No");  
  58.                 continue;  
  59.             }  
  60.             return tClazz;  
  61.         }  
  62.     ALOG("Exception","No");  
  63.     return NULL;  
  64. }  
这个函数的主要功能就是通过传递进来的类的全称字符串,然后进行查找这个类,返回jclass.

这里的原理是通过Android中的类加载器中来获取这个类对象

其他就没什么难度了,就是JNIEnv的操作,这个就和Java中反射机制很类似。

我们看到函数中有一个这样的类:

[cpp] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. jclass clazzApplicationLoaders = jenv->FindClass("android/app/ApplicationLoaders");  


我们去看一下这个类的源码(android/app/ApplicationLoaders):
[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. /* 
  2.  * Copyright (C) 2006 The Android Open Source Project 
  3.  * 
  4.  * Licensed under the Apache License, Version 2.0 (the "License"); 
  5.  * you may not use this file except in compliance with the License. 
  6.  * You may obtain a copy of the License at 
  7.  * 
  8.  *      http://www.apache.org/licenses/LICENSE-2.0 
  9.  * 
  10.  * Unless required by applicable law or agreed to in writing, software 
  11.  * distributed under the License is distributed on an "AS IS" BASIS, 
  12.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
  13.  * See the License for the specific language governing permissions and 
  14.  * limitations under the License. 
  15.  */  
  16.   
  17. package android.app;  
  18.   
  19. import android.os.Trace;  
  20. import android.util.ArrayMap;  
  21. import dalvik.system.PathClassLoader;  
  22.   
  23. class ApplicationLoaders  
  24. {  
  25.     public static ApplicationLoaders getDefault()  
  26.     {  
  27.         return gApplicationLoaders;  
  28.     }  
  29.   
  30.     public ClassLoader getClassLoader(String zip, String libPath, ClassLoader parent)  
  31.     {  
  32.         /* 
  33.          * This is the parent we use if they pass "null" in.  In theory 
  34.          * this should be the "system" class loader; in practice we 
  35.          * don't use that and can happily (and more efficiently) use the 
  36.          * bootstrap class loader. 
  37.          */  
  38.         ClassLoader baseParent = ClassLoader.getSystemClassLoader().getParent();  
  39.   
  40.         synchronized (mLoaders) {  
  41.             if (parent == null) {  
  42.                 parent = baseParent;  
  43.             }  
  44.   
  45.             /* 
  46.              * If we're one step up from the base class loader, find 
  47.              * something in our cache.  Otherwise, we create a whole 
  48.              * new ClassLoader for the zip archive. 
  49.              */  
  50.             if (parent == baseParent) {  
  51.                 ClassLoader loader = mLoaders.get(zip);  
  52.                 if (loader != null) {  
  53.                     return loader;  
  54.                 }  
  55.       
  56.                 Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, zip);  
  57.                 PathClassLoader pathClassloader =  
  58.                     new PathClassLoader(zip, libPath, parent);  
  59.                 Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);  
  60.   
  61.                 mLoaders.put(zip, pathClassloader);  
  62.                 return pathClassloader;  
  63.             }  
  64.   
  65.             Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, zip);  
  66.             PathClassLoader pathClassloader = new PathClassLoader(zip, parent);  
  67.             Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);  
  68.             return pathClassloader;  
  69.         }  
  70.     }  
  71.   
  72.     private final ArrayMap<String, ClassLoader> mLoaders = new ArrayMap<String, ClassLoader>();  
  73.   
  74.     private static final ApplicationLoaders gApplicationLoaders  
  75.         = new ApplicationLoaders();  
  76. }  
这个类的作用就是用来维护应用中的类加载器

看到他有一个私有的变量mLoaders.是ArrayMap类型的

ArrayMap类型就把他看成是ArrayList和Map的结合体。具体使用自行研究。

这个mLoaders变量中维护了ClassLoader对象,现在我们就需要获取这个ClassLoader对象

其中key是类的全称,value就是类加载器

因为这个类是包访问权限,又是单例模式,我们只能去调用他的getDefault方法,得到其对象,然后在获取他的mLoaders变量值


好了关于findClass函数的后续代码我就不解读了,因为没什么难度,说白了就是反射机制

1)、通过反射获取ApplicationLoaders对象中的mLoaders值

2)、通过反射去获取mLoaders中指定key的类加载器ClassLoader对象

3)、然后通过反射去调用类加载器中的loadClass方法,返回一个jclass对象,最后返回即可


3、HookDalvikMethod函数

[cpp] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. bool HookDalvikMethod(jmethodID jmethod){  
  2.     Method *method = (Method*)jmethod;  
  3.     //将方法method设置变成native  
  4.     SET_METHOD_FLAG(method, ACC_NATIVE);  
  5.   
  6.     //计算这个native方法需要的空间大小  
  7.     int argsSize = dvmComputeMethodArgsSize(method);  
  8.     if (!dvmIsStaticMethod(method))  
  9.         argsSize++;  
  10.   
  11.     method->registersSize = method->insSize = argsSize;  
  12.   
  13.     if (dvmIsNativeMethod(method)) {  
  14.         method->nativeFunc = dvmResolveNativeMethod;  
  15.         method->jniArgInfo = computeJniArgInfo(&method->prototype);  
  16.     }  
  17. }  
这个函数代码不多,但是他的功能是最关键的。

将传递进来的jmethodID方法变成native方法

这个就是可以将一个非native方法变成一个native方法

其实这段代码中有几个重要的函数:

SET_METHOD_FLAG

dvmComputeMethodArgsSize

dvmIsStaticMethod

dvmIsNativeMethos

devResolveNativeMethod

这些函数都是在系统中定义的,我们需要引入这个头文件:Dalvik.h


看完了,这些函数,我们还是需要回到我们开始的地方ClassMethosHook函数:

[cpp] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. bool ClassMethodHook(HookInfo info){  
  2.   
  3.     //获取JNIEnv对象  
  4.     JNIEnv *jenv = GetEnv();  
  5.   
  6.     //查找类  
  7.     jclass clazzTarget = jenv->FindClass(info.tClazz);  
  8.     if (ClearException(jenv)) {  
  9.         ALOG("Exception","ClassMethodHook[Can't find class:%s in bootclassloader",info.tClazz);  
  10.   
  11.         clazzTarget = findAppClass(jenv,info.tClazz);  
  12.         if(clazzTarget == NULL){  
  13.             ALOG("Exception","%s","Error in findAppClass");  
  14.             return false;  
  15.         }  
  16.     }  
  17.   
  18.     //在类中查找方法  
  19.     jmethodID method = jenv->GetMethodID(clazzTarget,info.tMethod,info.tMeihodSig);  
  20.     if(method==NULL){  
  21.         ALOG("Exception","ClassMethodHook[Can't find method:%s",info.tMethod);  
  22.         return false;  
  23.     }  
  24.   
  25.     //将这个方法变成native  
  26.     HookDalvikMethod(method);  
  27.   
  28.     JNINativeMethod gMethod[] = {  
  29.         {info.tMethod, info.tMeihodSig, info.handleFunc},  
  30.     };  
  31.   
  32.     //注册native方法  
  33.     if(info.handleFunc != NULL){  
  34.         if (jenv->RegisterNatives(clazzTarget, gMethod, 1) < 0) {  
  35.             ALOG("RegisterNatives","err");  
  36.             return false;  
  37.         }  
  38.     }  
  39.   
  40.     DetachCurrent();  
  41.     return true;  
  42. }  
还有一部分,调用JNIEnv对象中的RegisterNatives方法,进行注册native方法。


上面的代码我们就看完了

下面来总结一下流程吧

1、首先执行inject.c中的main函数,在这个函数中我们将我们自己的libso.so文件注入到目标进程中,然后执行InjectInterface函数

2、在InjectInterface函数中,我们在执行MethodHooker.cpp中的Hook函数

3、在Hook函数中,我们通过dlopen函数打开libTest.so文件,然后执行其中的getpHookInfo函数,获取HookInfo结构体类型的内容

4、在getpHookInfo函数中主要的功能是将初始化好的HookInfo结构体返回给Hook函数中

5、在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源码:

[cpp] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. if (pDexCode != NULL) {  
  2.         /* integer constants, copy over for faster access */  
  3.         meth->registersSize = pDexCode->registersSize;  
  4.         meth->insSize = pDexCode->insSize;  
  5.         meth->outsSize = pDexCode->outsSize;  
  6.   
  7.         /* pointer to code area */  
  8.         meth->insns = pDexCode->insns;  
  9.     } else {  
  10.         /* 
  11.          * We don't have a DexCode block, but we still want to know how 
  12.          * much space is needed for the arguments (so we don't have to 
  13.          * compute it later).  We also take this opportunity to compute 
  14.          * JNI argument info. 
  15.          * 
  16.          * We do this for abstract methods as well, because we want to 
  17.          * be able to substitute our exception-throwing "stub" in. 
  18.          */  
  19.         int argsSize = dvmComputeMethodArgsSize(meth);  
  20.         if (!dvmIsStaticMethod(meth))  
  21.             argsSize++;  
  22.         meth->registersSize = meth->insSize = argsSize;  
  23.         assert(meth->outsSize == 0);  
  24.         assert(meth->insns == NULL);  
  25.   
  26.         if (dvmIsNativeMethod(meth)) {  
  27.             meth->nativeFunc = dvmResolveNativeMethod;  
  28.             meth->jniArgInfo = computeJniArgInfo(&meth->prototype);  
  29.        }  
  30.     }  
我们直接看else中的代码:

该函数会从dex 文件中解析DexMethod 成dalvik中执行的method,if(pDexCode != NULL) 判断是否存在dex代码,看else部分说明,可以知道该部分是dalvik对java native method处理过程。
其中dvmResolveNativeMethod
调用了dvmLookupInternalNativeMethodlookupSharedLibMethod来查找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文件

[cpp] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. LOCAL_PATH := $(call my-dir)  
  2.   
  3. include $(CLEAR_VARS)  
  4.   
  5. LOCAL_MODULE:= so  
  6.   
  7. LOCAL_SRC_FILES := so.cpp MethodHooker.cpp  
  8.   
  9. LOCAL_LDLIBS+=   
  10.   
  11. LOCAL_CFLAGS    := -I./include/ -I./dalvik/vm/ -I./dalvik -DHAVE_LITTLE_ENDIAN  
  12.   
  13. LOCAL_LDFLAGS   :=  -L./lib/  -L$(SYSROOT)/usr/lib -llog -ldvm -landroid_runtime  -lart  
  14.   
  15. LOCAL_STATIC_LIBRARIES := hookart  
  16.   
  17. LOCAL_SHARED_LIBRARIES :=  
  18. include $(BUILD_SHARED_LIBRARY)  
  19.   
  20. #------------------------------------------------------------------------  
  21.   
  22. include $(CLEAR_VARS)  
  23.   
  24. LOCAL_MODULE:= Test  
  25.   
  26. LOCAL_SRC_FILES := Test.c  
  27.   
  28. LOCAL_LDLIBS+= -L./lib -llog  
  29.   
  30. LOCAL_CFLAGS    := -I./include/ -I./dalvik/vm/ -I./dalvik -fPIC -shared  
  31.   
  32. LOCAL_SHARED_LIBRARIES :=   
  33.   
  34. include $(BUILD_SHARED_LIBRARY)  
  35.   
  36. #------------------------------------------------------------------------  
  37.   
  38. include $(CLEAR_VARS)  
  39.   
  40. LOCAL_MODULE:= inject  
  41.   
  42. LOCAL_SRC_FILES := inject.c shellcode.s  
  43.   
  44. LOCAL_LDLIBS :=   
  45.   
  46. LOCAL_CFLAGS :=    
  47.   
  48. include $(BUILD_EXECUTABLE)  
这里对so.cpp,Test.c,inject.c进行编译。

看一下so.cpp的编译模块

[cpp] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. LOCAL_PATH := $(call my-dir)  
  2.   
  3. include $(CLEAR_VARS)  
  4.   
  5. LOCAL_MODULE:= so  
  6.   
  7. LOCAL_SRC_FILES := so.cpp MethodHooker.cpp  
  8.   
  9. LOCAL_LDLIBS+=   
  10.   
  11. LOCAL_CFLAGS    := -I./include/ -I./dalvik/vm/ -I./dalvik -DHAVE_LITTLE_ENDIAN  
  12.   
  13. LOCAL_LDFLAGS   :=  -L./lib/  -L$(SYSROOT)/usr/lib -llog -ldvm -landroid_runtime  -lart  
  14.   
  15. LOCAL_STATIC_LIBRARIES := hookart  
  16.   
  17. LOCAL_SHARED_LIBRARIES :=  
  18. include $(BUILD_SHARED_LIBRARY)  
我们需要用到的源文件为:so.cpp、MethodHooker.cpp

编译的过程中我们需要引入的头文件我们都放到了include文件夹下:


所以写法很简单:

[cpp] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. LOCAL_CFLAGS    := -I./include/ -I./dalvik/vm/ -I./dalvik -DHAVE_LITTLE_ENDIAN  
还有一些头文件放在dalvik文件夹下


这样就引入了需要的头文件

还需要导入so文件路径:


这里我把模拟器中的整个lib文件夹都拷贝过来了

[cpp] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. 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



当然我们还可以写一个简单的脚本文件一步到位

[javascript] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. adb push ..\libs\armeabi\libTest.so  /data/local/  
  2. adb push ..\libs\armeabi\libso.so  /data/local/  
  3. adb push ..\libs\armeabi\inject /data/local/  
  4. adb shell chmod 777 /data/local/inject  
  5. adb shell chmod 777 /data/local/libso.so  
  6. adb shell chmod 777 /data/local/libTest.so  
  7. adb shell su -c /data/local/inject  
  8. pause  
保存.bat文件,然后放到编译项目的目录下,直接运行即可。


拷贝工作完成了,下面来运行一下Android项目

注意应用的包名为: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方法了。


在这篇文章中我们学习到了几个知识点:

1、如何将一个非native方法变成一个native方法

2、如何手动的去注册一个native方法

3、学会了使用另外的一种编译项目的方式(引入头文件)

4、注入进程的相关知识


(PS:总算是说完了,文章中说道的项目已经给出下载地址了,想试验的同学,可以尝试编译,如果在这个过程中遇到什么问题,可以给我留言,我尽量解决一下~~)

展开阅读全文

没有更多推荐了,返回首页