关闭 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、注入进程的相关知识

    上一篇返回首页 下一篇

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

    别人在看

    正版 Windows 11产品密钥怎么查找/查看?

    还有3个月,微软将停止 Windows 10 的更新

    Windows 10 终止支持后,企业为何要立即升级?

    Windows 10 将于 2025年10 月终止技术支持,建议迁移到 Windows 11

    Windows 12 发布推迟,微软正全力筹备Windows 11 25H2更新

    Linux 退出 mail的命令是什么

    Linux 提醒 No space left on device,但我的空间看起来还有不少空余呢

    hiberfil.sys文件可以删除吗?了解该文件并手把手教你删除C盘的hiberfil.sys文件

    Window 10和 Windows 11哪个好?答案是:看你自己的需求

    盗版软件成公司里的“隐形炸弹”?老板们的“法务噩梦” 有救了!

    IT头条

    公安部:我国在售汽车搭载的“智驾”系统都不具备“自动驾驶”功能

    02:03

    液冷服务器概念股走强,博汇、润泽等液冷概念股票大涨

    01:17

    亚太地区的 AI 驱动型医疗保健:2025 年及以后的下一步是什么?

    16:30

    智能手机市场风云:iPhone领跑销量榜,华为缺席引争议

    15:43

    大数据算法和“老师傅”经验叠加 智慧化收储粮食尽显“科技范”

    15:17

    技术热点

    SQL汉字转换为拼音的函数

    windows 7系统无法运行Photoshop CS3的解决方法

    巧用MySQL加密函数对Web网站敏感数据进行保护

    MySQL基础知识简介

    Windows7和WinXP下如何实现不输密码自动登录系统的设置方法介绍

    windows 7系统ip地址冲突怎么办?windows 7系统IP地址冲突问题的

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

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