关闭 x
IT技术网
    技 采 号
    ITJS.cn - 技术改变世界
    • 实用工具
    • 菜鸟教程
    IT采购网 中国存储网 科技号 CIO智库

    IT技术网

    IT采购网
    • 首页
    • 行业资讯
    • 系统运维
      • 操作系统
        • Windows
        • Linux
        • Mac OS
      • 数据库
        • MySQL
        • Oracle
        • SQL Server
      • 网站建设
    • 人工智能
    • 半导体芯片
    • 笔记本电脑
    • 智能手机
    • 智能汽车
    • 编程语言
    IT技术网 - ITJS.CN
    首页 » 安卓开发 »Android中通过进程注入技术修改系统返回的Mac地址

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

    2014-12-19 00:00:00 出处:51CTO
    分享

    问题

    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项目

    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("%sn",pn);
    	printf("%sn",is);
    
    	pid_t target_pid;
    	target_pid = find_pid_of(pn);
    	printf("pid: %dn",target_pid);
    	int ret = inject_remote_process(target_pid, is, "InjectInterface", (void*)"I'm parameter!", strlen("I'm parameter!") );
    	printf("result: %dn",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ààDí:info
    #define log_(b) __android_log_write(ANDROID_LOG_INFO,"JNI_LOG_INFO",b); // LOGààDí: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]);
    }

    看一下getHookInfo函数

    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},  
    };

    看一下ClassMethodHook函数的定义

    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;
    }

    这个函数中有其他的函数调用,我们先来看看他们是怎么定义的

    1、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对象,这就是一种方法。

    2、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");

    我们去看一下这个类的源码(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对象,最后返回即可

    3、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方法。

    上面的代码我们就看完了

    下面来总结一下流程吧

    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源码:

    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 ..libsarmeabilibTest.so  /data/local/  
    adb push ..libsarmeabilibso.so  /data/local/  
    adb push ..libsarmeabiinject /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文件,然后放到编译项目的目录下,直接运行即可。

    拷贝工作完成了,下面来运行一下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、注入进程的相关知识

    上一篇返回首页 下一篇

    声明: 此文观点不代表本站立场;转载务必保留本文链接;版权疑问请联系我们。

    别人在看

    电脑屏幕不小心竖起来了?别慌,快捷键搞定

    Destoon 模板存放规则及语法参考

    Destoon系统常量与变量

    Destoon系统目录文件结构说明

    Destoon 系统安装指南

    Destoon会员公司主页模板风格添加方法

    Destoon 二次开发入门

    Microsoft 将于 2026 年 10 月终止对 Windows 11 SE 的支持

    Windows 11 存储感知如何设置?了解Windows 11 存储感知开启的好处

    Windows 11 24H2 更新灾难:系统升级了,SSD固态盘不见了...

    IT头条

    Synology 更新 ActiveProtect Manager 1.1 以增强企业网络弹性和合规性

    00:43

    新的 Rubrik Agent Cloud 加速了可信的企业 AI 代理部署

    00:34

    宇树科技 G1人形机器人,拉动一辆重达1.4吨的汽车

    00:21

    Cloudera 调查发现,96% 的企业已将 AI 集成到核心业务流程中,这表明 AI 已从竞争优势转变为强制性实践

    02:05

    投资者反对马斯克 1 万亿美元薪酬方案,要求重组特斯拉董事会

    01:18

    技术热点

    大型网站的 HTTPS 实践(三):基于协议和配置的优化

    ubuntu下右键菜单添加新建word、excel文档等快捷方式

    Sublime Text 简明教程

    用户定义SQL Server函数的描述

    怎么在windows 7开始菜单中添加下载选项?

    SQL Server 2016将有哪些功能改进?

      友情链接:
    • IT采购网
    • 科技号
    • 中国存储网
    • 存储网
    • 半导体联盟
    • 医疗软件网
    • 软件中国
    • ITbrand
    • 采购中国
    • CIO智库
    • 考研题库
    • 法务网
    • AI工具网
    • 电子芯片网
    • 安全库
    • 隐私保护
    • 版权申明
    • 联系我们
    IT技术网 版权所有 © 2020-2025,京ICP备14047533号-20,Power by OK设计网

    在上方输入关键词后,回车键 开始搜索。Esc键 取消该搜索窗口。