Android进程so注入Hook java方法

本文博客链接:http://blog.csdn.net/qq1084283172/article/details/53769331

Andorid的Hook方式比较多,现在来学习下,基于Android进程so注入Hook java方法的原理,可以说现在Android这种方式的Hook已经很成熟了,比较好的Android注入框架如:Xposed、ddi、cydia substrate等都是采用这种方式进行Android的java方法的Hook。对于基于Android进程so注入Hook java方法的原理和使用主要针对网上几位大牛的博文的思路和实现方法来深入的学习,争取在搞懂这种方式的Android的Hook的思路的同时也将需要注意的一些细节的地方弄明白,有理解不正确的地方,希望大牛能指出来。本文主要是基于作者malokch在看雪论坛发的文章《注入安卓进程,并hook java世界的方法》来展开学习。


一、基于Android进程so注入Hook java方法的整体思路

看下作者malokch在文章中提供的代码结构,MainActivity.java是用于编译生成被注入和Hook的目标apk进程,其中inject是进程so注入的工具,libso.so是被注入到目标进程中的库文件,libTest.so是被libso.so调用并且用于替换目标apk进程中被Hook函数的方法体的实现,替换的test函数是一个native层的函数。



1.> 设备root条件下,使用Android的进程注入工具inject将动态库文件libso.so注入到目标进程pid_apk中

2.> 当动态库文件libso.so注入成功时,加载动态库文件libTest.so,获取需要被Hook的函数相关信息HookInfo(目标类、目标方法)以及Hook目标函数用的替换函数实现

3.> 先获取JavaVM指针然后获取JNIEnv指针,调用JNIEnv指针的FindClass函数在目标进程pid_apk中查找到已经加载的目标类

4.> 如果目标类没有加载,需要调用ClassLoader类的对象的实例成员方法loadClass加载目标类

5.>调用JNIEnv指针的GetMethodID函数通过函数的签名等信息,查找到被Hook的目标函数的jmethodID指针(这个指针在Dalvik里其实就是Method类,类型转换一下 就行)

6.>对需要被Hook的目标函数的Method类结构体的accessFlags、registersSize、insSize、nativeFunc等成员变量值进行修改,使目标函数由java方法变成native方法

7.>在JNI环境下,调用JNIEnv指针的RegisterNatives函数将目标函数的实现注册替换为新的自定义的函数实现

8.> 本文基于Android进程so注入Hook java方法的说明中,主要学习的是在dalvik环境下的Hook,暂时不考虑art环境下的,目标函数被Hook以后还涉及到还原调用原来的函数


二、基于Android进程so注入Hook java方法的设计的细节问题

1.> 作者malokch给出的函数Hook的关键点

修改被Hook的目标函数的属性为native(作者Hook的是android系统函数getgetMacAddress)



替换被Hook目标函数的实现代码为自定义的新函数



被Hook目标函数的原来方法实现的还原调用



2.> 调用JNIEnv指针的FindClass函数时,如果遇到目标进程中目标类没有被加载,需要自己主动加载目标类到进程的内存中,具体的实现见findAppClass函数。findAppClass函数实现主动加载目标类的操作,可以参考Android源码的 /frameworks/base/core/java/android/app/ApplicationLoaders.java文件里的ApplicationLoaders类的代码:



/**通过类ClassLoader对象的loadClass函数加载目标类到目标进程的内存中*/
static jclass findAppClass(JNIEnv *jenv, const char *apn){
	
    // 获取android.app.ApplicationLoaders类
    jclass clazzApplicationLoaders = jenv->FindClass("android/app/ApplicationLoaders");
	
	// 异常清除
    jthrowable exception = jenv->ExceptionOccurred();
    if (ClearException(jenv)) {
		
        ALOG("Exception", "No class : %s", "android/app/ApplicationLoaders");
        return NULL;
    }
	
	// 获取类ApplicationLoaders类静态成员变量gApplicationLoaders的FieldID
    jfieldID fieldApplicationLoaders = jenv->GetStaticFieldID(clazzApplicationLoaders, "gApplicationLoaders", "Landroid/app/ApplicationLoaders;");
	// 异常清除
    if (ClearException(jenv)) {
		
        ALOG("Exception", "No Static Field :%s", "gApplicationLoaders");
        return NULL;
    }
	
	// 获取类ApplicationLoaders类静态成员变量gApplicationLoaders对象
    jobject objApplicationLoaders = jenv->GetStaticObjectField(clazzApplicationLoaders, fieldApplicationLoaders);
	// 异常清除
    if (ClearException(jenv)) {
		
        ALOG("Exception", "GetStaticObjectField is failed [%s", "gApplicationLoaders");
        return NULL;
    }
	
	// 获取类ApplicationLoaders类实例成员变量mLoaders的FieldID
    jfieldID fieldLoaders = jenv->GetFieldID(clazzApplicationLoaders, "mLoaders", "Ljava/util/Map;");
	// 异常清除
    if (ClearException(jenv)) {
		
        ALOG("Exception", "No Field :%s", "mLoaders");
        return NULL;
    }
	
	// 获取类ApplicationLoaders类实例成员变量mLoaders对象
    jobject objLoaders = jenv->GetObjectField(objApplicationLoaders, fieldLoaders);
	// 异常清除
    if (ClearException(jenv)) {
		
        ALOG("Exception","No object :%s", "mLoaders");
        return NULL;
    }
	
    // 获取mLoaders对象的类java.util.Map
    jclass clazzHashMap = jenv->GetObjectClass(objLoaders);
	// 获取类java.util.Map的方法values的实例方法MethodID
    jmethodID methodValues = jenv->GetMethodID(clazzHashMap, "values", "()Ljava/util/Collection;");
	// 调用java.util.Map的方法values得到Collectio类型的值对象
    jobject values = jenv->CallObjectMethod(objLoaders, methodValues);

	// 获取类java.util.Collection
    jclass clazzValues = jenv->GetObjectClass(values);
	// 获取类java.util.Collection实例方法toArray的MethodID
    jmethodID methodToArray = jenv->GetMethodID(clazzValues, "toArray", "()[Ljava/lang/Object;");
	// 异常清除
    if (ClearException(jenv)) {
		
        ALOG("Exception","No Method:%s","toArray");
        return NULL;
    }

	// 调用类java.util.Collection的实例方法toArray得到java.lang.Object数组对象
    jobjectArray classLoaders = (jobjectArray)jenv->CallObjectMethod(values, methodToArray);
	// 异常清除
    if (ClearException(jenv)) {
		
        ALOG("Exception", "CallObjectMethod failed :%s", "toArray"); 
        return NULL; 
    }
    
    // 上面的一段代码的实现,可以参考Android源码的 /frameworks/base/core/java/android/app/ApplicationLoaders.java 文件里的ApplicationLoaders类
    /***
     * ApplicationLoaders类中的成员变量,其中实例变量mLoaders是<String, ClassLoader>类型的ArrayMap数组,
     * mLoaders变量中记载着当前apk进程加载的所有的类加载器的信息即ClassLoader结构体
     
     private final ArrayMap<String, ClassLoader> mLoaders = new ArrayMap<String, ClassLoader>();

	 private static final ApplicationLoaders gApplicationLoaders = new ApplicationLoaders();
	 ***/
    // 上面这段代码,饶了这么一圈,目的就是为了获取变量mLoaders

	// 获取java.lang.Object数组对象的个数即获取mLoaders数组的大小
	int size = jenv->GetArrayLength(classLoaders);

	// 遍历当前apk进程的mLoaders数组对象
	for(int i = 0 ; i < size ; i++) {
		
		// 获取mLoaders数组对象的第i个元素即类加载器ClassLoader对象classLoader
		jobject classLoader = jenv->GetObjectArrayElement(classLoaders, i);
		// 获取classLoader对象的类ClassLoader
		jclass clazzCL = jenv->GetObjectClass(classLoader);
		// 获取classLoader对象的类ClassLoader的实例loadClass方法
		jmethodID loadClass = jenv->GetMethodID(clazzCL, "loadClass"," (Ljava/lang/String;)Ljava/lang/Class;");
		
		// 将需要加载的类的名称字符串转成UTF-8格式即C语言形式的字符串
		jstring param = jenv->NewStringUTF(apn);
		
		// 调用类ClassLoader的实例方法loadClass加载指定的名称的类
		jclass tClazz = (jclass)jenv->CallObjectMethod(classLoader, loadClass, param);
		// 异常清除
		if (ClearException(jenv)) { 
		
			ALOG("Exception", "No");
			continue;
		}
		
		// 返回加载后的类的信息结构体jclass
		return tClazz;
	}
	
    ALOG("Exception", "No");
    
    // 上面的代码意思是:调用ClassLoader.loadClass("需要加载的类名称") 加载指定的类
    /*** ClassLoader类的成员方法
	 public Class<?> loadClass(String className) throws ClassNotFoundException {
        return loadClass(className, false);
    }
    ***/
	
    return NULL;
}


3.> 函数HookDalvikMethod-- 修改将被Hook的java方法的函数属性为native以及Method的相关参数的修改的依据,可以参考Android源码的 dalvik/vm/oo/class.cppLoadMethodFromDex函数 的实现。





三、基于Android进程so注入Hook java方法的代码的详细分析

//被注入的目标pid_apk进程apk的实现代码:

MainActivity.java文件

package com.example.testar;

import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;

import dalvik.system.DexClassLoader;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
import android.os.Bundle;
import android.app.Activity;
import android.content.Context;
import android.text.GetChars;
import android.util.Log;
import android.view.Menu;
import android.view.View;
import android.widget.Button;

public class MainActivity extends Activity {
	
    private final Map<String, ClassLoader> mLoaders = new HashMap<String, ClassLoader>();

    @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) {

				// 获取Android的Wifi管理器对象
                WifiManager wifi = (WifiManager) getSystemService(Context.WIFI_SERVICE);
                // 获取Wifi的连接相关的信息
                WifiInfo info = wifi.getConnectionInfo();
                // 打印当前Wifi的MacAddress地址
                System.out.println("Wifi mac :" + info.getMacAddress());
                
                // 调用test()函数
                System.out.println("return " + test());
                
                // 注意了作者,Hook的是android系统函数getMacAddress,不是java方法test函数
            }
        });
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.main, menu);
        return true;
    }
	
    private String test() {
		
        return "real";
    }
	
	
}


//注入到目标进程pid_apk中的so库文件libso.so的实现so.cpp,MethodHooker.cpp:
so.cpp文件

#include "jni.h"
#include "android_runtime/AndroidRuntime.h"
#include "android/log.h"
#include "stdio.h"
#include "stdlib.h"
#include "MethodHooker.h"   // 将so注入到目标进程中,执行Hook功能的函数
#include <utils/CallStack.h>
#include "art.h"
#define log(a,b) __android_log_write(ANDROID_LOG_INFO,a,b); // LOG ?:info
#define log_(b) __android_log_write(ANDROID_LOG_INFO,"JNI_LOG_INFO",b); // LOG ?:info


// InjectInterface为so库文件注入到目标进程中后将被调用的函数--执行Hook java方法getMacAddress的作用
extern "C" void InjectInterface(char*arg){
	
    log_("*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*");
    log_("*-*-*-*-*-* Injected so *-*-*-*-*-*-*-*");
    log_("*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*");
	
	// 执行Hook目标进程的java方法的作用(在MethodHooker.cpp中实现的)
    Hook();
	
    log_("*-*-*-*-*-*-*- End -*-*-*-*-*-*-*-*-*-*");
	
}


// 作者没有使用
extern "C" JNIEXPORT jstring JNICALL Java_com_example_testar_InjectApplication_test(JNIEnv *env, jclass clazz)
{
    Abort_();
	
    return env->NewStringUTF("haha ");;
}


MethodHooker.h以及MethodHooker.cpp文件:

MethodHooker.h文件

typedef struct{
	//被Hook的函数的所在类
	const char *tClazz;
	//被Hook的函数方法
	const char *tMethod;
	//被Hook的函数的方法签名
	const char *tMeihodSig;
	//被Hook函数的Native层实现的函数方法指针
	void *handleFunc;
} HookInfo;

/**定义函数的指针*/
typedef int(*SetupFunc)(HookInfo**);

/**声明Hook函数*/
int 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"

#include "art.h"

#define ALOG(...) __android_log_print(ANDROID_LOG_VERBOSE, __VA_ARGS__)


// 保存是否附加到Android目标进程的JavaVM虚拟机标记
static bool g_bAttatedT;
// 保存Android目标进程的JavaVM对象
static JavaVM *g_JavaVM;


// 获取Android进程的JavaVM虚拟机对象
void init()
{
    g_bAttatedT = false;
	// 获取Android进程的JavaVM对象
    g_JavaVM = android::AndroidRuntime::getJavaVM();
}

/****获取当前apk进程的JNIEnv指针****/
static JNIEnv *GetEnv()
{
	int status;
    JNIEnv *envnow = NULL;
    
	// 获取当前apk进程JNIEnv指针以及虚拟机的运行状态
    status = g_JavaVM->GetEnv((void **)&envnow, JNI_VERSION_1_4);
    
    /**** JNIEnv是和线程相关的,使用前一定记得将其附加到当前进程,也要在适当的时候将其销毁  ***/
    if(status < 0)
    {
		// 附加到当前apk进程的Android虚拟机线程
        status = g_JavaVM->AttachCurrentThread(&envnow, NULL);
        if(status < 0)
        {
            return NULL;
        }
		
		// 已经附加当前apk进程的虚拟机的标记
        g_bAttatedT = true;
    }
	
    return envnow;
}

/***释放附加的Android的jvm虚拟机进程***/
static void DetachCurrent()
{
	// 判断是否附加Android的jvm虚拟机线程
    if(g_bAttatedT)
    {
		// 若已经附加,释放分离附加的Android虚拟机线程
        g_JavaVM->DetachCurrentThread();
    }
}

/***JNIEnv环境的异常处理*****/
int ClearException(JNIEnv *jenv){
	
	// 获取当前环境的异常
    jthrowable exception = jenv->ExceptionOccurred();
    if (exception != NULL) {
		
        jenv->ExceptionDescribe();
		// 清除异常
        jenv->ExceptionClear();
		
        return true;
    }
	
    return false;
}

/**获取当前Hook运行的模式是否是ART*/
bool isArt(){
	
	// 返回当前运行模式为dvm
    return false;
}

/**通过类ClassLoader对象的loadClass函数加载目标类到目标进程的内存中*/
static jclass findAppClass(JNIEnv *jenv, const char *apn){
	
    // 获取android.app.ApplicationLoaders类
    jclass clazzApplicationLoaders = jenv->FindClass("android/app/ApplicationLoaders");
	
	// 异常清除
    jthrowable exception = jenv->ExceptionOccurred();
    if (ClearException(jenv)) {
		
        ALOG("Exception", "No class : %s", "android/app/ApplicationLoaders");
        return NULL;
    }
	
	// 获取类ApplicationLoaders类静态成员变量gApplicationLoaders的FieldID
    jfieldID fieldApplicationLoaders = jenv->GetStaticFieldID(clazzApplicationLoaders, "gApplicationLoaders", "Landroid/app/ApplicationLoaders;");
	// 异常清除
    if (ClearException(jenv)) {
		
        ALOG("Exception", "No Static Field :%s", "gApplicationLoaders");
        return NULL;
    }
	
	// 获取类ApplicationLoaders类静态成员变量gApplicationLoaders对象
    jobject objApplicationLoaders = jenv->GetStaticObjectField(clazzApplicationLoaders, fieldApplicationLoaders);
	// 异常清除
    if (ClearException(jenv)) {
		
        ALOG("Exception", "GetStaticObjectField is failed [%s", "gApplicationLoaders");
        return NULL;
    }
	
	// 获取类ApplicationLoaders类实例成员变量mLoaders的FieldID
    jfieldID fieldLoaders = jenv->GetFieldID(clazzApplicationLoaders, "mLoaders", "Ljava/util/Map;");
	// 异常清除
    if (ClearException(jenv)) {
		
        ALOG("Exception", "No Field :%s", "mLoaders");
        return NULL;
    }
	
	// 获取类ApplicationLoaders类实例成员变量mLoaders对象
    jobject objLoaders = jenv->GetObjectField(objApplicationLoaders, fieldLoaders);
	// 异常清除
    if (ClearException(jenv)) {
		
        ALOG("Exception","No object :%s", "mLoaders");
        return NULL;
    }
	
    // 获取mLoaders对象的类java.util.Map
    jclass clazzHashMap = jenv->GetObjectClass(objLoaders);
	// 获取类java.util.Map的方法values的实例方法MethodID
    jmethodID methodValues = jenv->GetMethodID(clazzHashMap, "values", "()Ljava/util/Collection;");
	// 调用java.util.Map的方法values得到Collectio类型的值对象
    jobject values = jenv->CallObjectMethod(objLoaders, methodValues);

	// 获取类java.util.Collection
    jclass clazzValues = jenv->GetObjectClass(values);
	// 获取类java.util.Collection实例方法toArray的MethodID
    jmethodID methodToArray = jenv->GetMethodID(clazzValues, "toArray", "()[Ljava/lang/Object;");
	// 异常清除
    if (ClearException(jenv)) {
		
        ALOG("Exception","No Method:%s","toArray");
        return NULL;
    }

	// 调用类java.util.Collection的实例方法toArray得到java.lang.Object数组对象
    jobjectArray classLoaders = (jobjectArray)jenv->CallObjectMethod(values, methodToArray);
	// 异常清除
    if (ClearException(jenv)) {
		
        ALOG("Exception", "CallObjectMethod failed :%s", "toArray"); 
        return NULL; 
    }
    
    // 上面的一段代码的实现,可以参考Android源码的 /frameworks/base/core/java/android/app/ApplicationLoaders.java 文件里的ApplicationLoaders类
    /***
     * ApplicationLoaders类中的成员变量,其中实例变量mLoaders是<String, ClassLoader>类型的ArrayMap数组,
     * mLoaders变量中记载着当前apk进程加载的所有的类加载器的信息即ClassLoader结构体
     
     private final ArrayMap<String, ClassLoader> mLoaders = new ArrayMap<String, ClassLoader>();

	 private static final ApplicationLoaders gApplicationLoaders = new ApplicationLoaders();
	 ***/
    // 上面这段代码,饶了这么一圈,目的就是为了获取变量mLoaders

	// 获取java.lang.Object数组对象的个数即获取mLoaders数组的大小
	int size = jenv->GetArrayLength(classLoaders);

	// 遍历当前apk进程的mLoaders数组对象
	for(int i = 0 ; i < size ; i++) {
		
		// 获取mLoaders数组对象的第i个元素即类加载器ClassLoader对象classLoader
		jobject classLoader = jenv->GetObjectArrayElement(classLoaders, i);
		// 获取classLoader对象的类ClassLoader
		jclass clazzCL = jenv->GetObjectClass(classLoader);
		// 获取classLoader对象的类ClassLoader的实例loadClass方法
		jmethodID loadClass = jenv->GetMethodID(clazzCL, "loadClass"," (Ljava/lang/String;)Ljava/lang/Class;");
		
		// 将需要加载的类的名称字符串转成UTF-8格式即C语言形式的字符串
		jstring param = jenv->NewStringUTF(apn);
		
		// 调用类ClassLoader的实例方法loadClass加载指定的名称的类
		jclass tClazz = (jclass)jenv->CallObjectMethod(classLoader, loadClass, param);
		// 异常清除
		if (ClearException(jenv)) { 
		
			ALOG("Exception", "No");
			continue;
		}
		
		// 返回加载后的类的信息结构体jclass
		return tClazz;
	}
	
    ALOG("Exception", "No");
    
    // 上面的代码意思是:调用ClassLoader.loadClass("需要加载的类名称") 加载指定的类
    /*** ClassLoader类的成员方法
	 public Class<?> loadClass(String className) throws ClassNotFoundException {
        return loadClass(className, false);
    }
    ***/
	
    return NULL;
}

/***根据调用方法的参数和返回值重新计算jni方法调用的相关参数**/
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;
}

/***修改将被Hook的java方法的函数属性为native以及Method的相关参数的修改***/
bool HookDalvikMethod(jmethodID jmethod){
	
	// **************************************************
	// 类型转换,获取将被Hook的java方法的Method结构体指针
    Method *method = (Method*)jmethod;
    // **************************************************
	
    // 设置将被Hook的java方法的函数属性为native
    SET_METHOD_FLAG(method, ACC_NATIVE);

	// 获取被Hook的java方法的参数调用需要的寄存器的个数
    int argsSize = dvmComputeMethodArgsSize(method);
	// 判断方法是否是是静态方法,不是静态方法+1
    if (!dvmIsStaticMethod(method))
        argsSize++;
        
    /*****
    所谓的寄存器只存在于Dalvik虚拟机中,在Native的代码中并不存在这种虚拟寄存器的概念。
    因此,表示函数中用作调用别的函数传递参数的寄存器个数(outsSize)肯定是0。
    并且对于Native函数来说,其输入参数寄存器个数(insSize)和所有使用的寄存器个数(registersSize)是相等的(Native函数内部肯定没用到虚拟寄存器)。
    而insSize和registersSize的值要被设置成Native函数对应的在Java代码定义中,参数传递所需要的寄存器个数。
    ***/
	// 修改被Hook的java方法使用寄存器的数量,此时insSize = registersSize
    method->registersSize = method->insSize = argsSize;

	// 判断方法是否是nativ方法,既判断被Hook的函数是否修改为native属性成功
    if (dvmIsNativeMethod(method)) {
		
		/***
		 nativeFunc:如果这个方法是一个Dalvik虚拟机自带的Native函数(Internal Native)的话,则这里存放了指向JNI实际函数机器码的首地址。
		 如果这个方法是一个普通的Native函数的话,则这里将指向一个中间的跳转JNI桥(Bridge)代码;
		***/
		
		// 修改被Hook的java方法的本地方法指针指向jni bridge
        method->nativeFunc = dvmResolveNativeMethod;
		// 重新计算被Hook的java方法的jni调用的预设参数
        method->jniArgInfo = computeJniArgInfo(&method->prototype);
    }
    
    // @具体的这些操作代码原因可以参考Android源码的 dalvik/vm/oo/class.cpp里LoadMethodFromDex函数实现@
}

/**Hook目标进程的目标类class的目标方法*/
bool ClassMethodHook(HookInfo info){

    // 获取JNIEnv的指针
    JNIEnv *jenv = GetEnv();

	// 查找将被Hook的java方法所在的目标类
    jclass clazzTarget = jenv->FindClass(info.tClazz);
    
	// 异常的清除,判断将被Hook的java方法所在的类是否已经被加载到内存中
    if (ClearException(jenv)) {
		
        ALOG("Exception","ClassMethodHook[Can't find class:%s in bootclassloader", info.tClazz);

		/******** 被Hook的java方法所在的类没有被加载,主动加载到内存中 ******/ 
        clazzTarget = findAppClass(jenv, info.tClazz);
        
		// 判断获取到的被Hook的java方法所在的类是否为null
        if(clazzTarget == NULL){
			
            ALOG("Exception","%s", "Error in findAppClass");
            return false;
        }
    }
    
    /****
    相关的知识点:
    Jni反射调用java方法时要用到一个jmethodID指针,这个指针在Dalvik里其实就是Method类,通过修改这个类的一些属性就可以实现在运行时将一个方法修改成native方法.
	SET_METHOD_FLAG(method, ACC_NATIVE); 就是这么做的,其后面的代码就是设定native函数的参数占用内存大小统计.
	****/

	// 将被Hook的目标java方法所在的类已经被加载到内存中
	// 获取将Hook的目标java方法在内存中的结构体Method类的指针
    jmethodID method = jenv->GetMethodID(clazzTarget, info.tMethod, info.tMeihodSig);
	// 判断是否获取目标java方法的结构体Method类的指针是否正确
    if(method==NULL) {
		
        ALOG("Exception","ClassMethodHook[Can't find method:%s", info.tMethod);
        return false;
    }

	// 判断当前Hook是否是在ART模式下
    if(isArt()){
		
		// 运行ART模式下的Hook相关操作
        HookArtMethod(jenv, method);
		
    } else {
		
		// 运行dvm模式下的Hook相关操作
		// 修改目标java方法的函数属性为native而非java层方法
		// 即修改统函数getMacAddress为native属性以及修改Method相关的参数值
        HookDalvikMethod(method);
    }

    JNINativeMethod gMethod[] = {
		
		// info.tMethod为被Hook的java方法即getMacAddress的函数名称
		// info.tMeihodSig为被Hook的java方法的签名"()Ljava/lang/String;"
		// info.handleFunc为替换被Hook的java方法的实现即native层实现test函数
        {info.tMethod, info.tMeihodSig, info.handleFunc},
    };

    // 判断替换被Hook的java方法的实现即native层实现test函数是否为null
    if(info.handleFunc != NULL) {
		
        // 将目标类中修改为native后的getMacAddress函数的实现通过Jni桥替换为native层实现test函数
        if (jenv->RegisterNatives(clazzTarget, gMethod, 1) < 0) {
			
            ALOG("RegisterNatives","err");
            return false;
        }
    }	// 基于Android的so注入的java方法的Hook已经完成了,在目标进程中调用getMacAddress函数会执行test函数(native层)的实现

	// 分离jvm虚拟机的线程附加
    DetachCurrent();
	
    return true;
}


// 执行Hook目标进程的java方法的功能
int Hook(){
	
	// 获取Android目标进程的JavaVM虚拟机对象
    init();
	
	// 加载Android系统文件路径/data/local下的/data/local/libTest.so库文件
    void* handle = dlopen("/data/local/libTest.so", RTLD_NOW);
	// 获取dlopen函数调用返回的结果码
    const char *dlopen_error = dlerror();
	// 判断是否加载/data/local/libTest.so库文件成功
    if(!handle){
		
        ALOG("Error", "cannt load plugin :%s", dlopen_error);
		
        return -1;
    }
	
	// 获取加载的库文件/data/local/libTest.so的导出函数getpHookInfo的调用地址
    SetupFunc setup = (SetupFunc)dlsym(handle, "getpHookInfo");
    const char *dlsym_error = dlerror();
    // 判断函数dlsym是否调用成功
    if (dlsym_error) {
		
        ALOG("Error","Cannot load symbol 'getpHookInfo' :%s", dlsym_error);
        dlclose(handle);
		
        return 1;
    }

    HookInfo *hookInfo;
	// 调用加载的库文件/data/local/libTest.so中的导出函数getpHookInfo
	// 获取将被Hook的java方法getMacAddress的一些信息
    setup(&hookInfo);
    
    /***在头文件"MethodHooker.h"中定义的
	typedef struct{
		//被Hook的函数所在类
		const char *tClazz;
		//被Hook的函数方法的名称
		const char *tMethod;
		//被Hook的函数的方法签名
		const char *tMeihodSig;
		//被Hook函数的Native层实现的函数方法指针
		void *handleFunc;
	} HookInfo;
	****/

	// 打印将被Hook的java方法getMacAddress所在的类的信息
    ALOG("LOG", "Target Class:%s", hookInfo[0].tClazz);
    // 打印将被Hook的java方法的名称
    ALOG("LOG", "Target Method:%s", hookInfo[0].tMethod);

	// Hook目标进程的目标类class的目标方法
	// 具体就是Hook文件MainActivity.java所在进程的系统函数getMacAddress
    ClassMethodHook(hookInfo[0]);
	
}


//想要的被Hook目标进程pid_apk的java方法--getMacAddress执行我们自定义的代码的文件:

Test.cpp文件

/**以下是我们想要的目标进程java世界执行的我们自定义的代码

代码:   libTest.so*/

#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); // LOG ?:info
#define log_(b) __android_log_print(ANDROID_LOG_VERBOSE,"JNI_LOG_INFO",b); // LOG ?:info

//函数指针的声明
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");
}


/**被Hook的函数getMacAddress被修改函数属性后的实现--即原来方法的实现替换成了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 ");
}

/**被Hook的函数的相关的信息*/
HookInfo hookInfos[] = {
	
		// 注意:Hook和被Hook的函数的参数和返回值的类型要一致
        {"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},
		
};

/**返回被Hook的函数的信息*/
int getpHookInfo(HookInfo** pInfo){
	
	// 获取将要被Hook的函数的相关信息
    *pInfo = hookInfos;
	
	// 返回被Hook的函数的个数
    return sizeof(hookInfos) / sizeof(hookInfos[0]);
}

// 编译需要的配置文件Android.mk和Application.mk:

Android.mk文件

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

# 编译生成注入的libso.so库文件
LOCAL_MODULE:= so

LOCAL_SRC_FILES := so.cpp MethodHooker.cpp

LOCAL_LDLIBS+= 

LOCAL_CFLAGS    := -I./jni/include/ -I./jni/dalvik/vm/ -I./jni/dalvik -DHAVE_LITTLE_ENDIAN

LOCAL_LDFLAGS	:=	-L./jni/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:= hookart

LOCAL_SRC_FILES :=  art.cpp

LOCAL_CFLAGS    :=  -I./jni/include/ -I./jni/art-kitkat-mr1.1-release/runtime/ 
					-I./jni/gtest/include/
					
LOCAL_LDFLAGS	:=	-L./jni/lib/  -L$(SYSROOT)/usr/lib -lart

LOCAL_LDLIBS+= -llog

LOCAL_CPPFLAGS := 	-std=c++1y

LOCAL_SHARED_LIBRARIES := 

include $(BUILD_STATIC_LIBRARY)

#------------------------------------------------------------------------

include $(CLEAR_VARS)

# 编译生成libTest.so文件
LOCAL_MODULE:= Test

LOCAL_SRC_FILES := Test.c

LOCAL_LDLIBS+= -L./jni/lib -llog

LOCAL_CFLAGS    := -I./jni/include/ -I./jni/dalvik/vm/ -I./jni/dalvik -fPIC -shared

LOCAL_SHARED_LIBRARIES := 

include $(BUILD_SHARED_LIBRARY)

#------------------------------------------------------------------------

include $(CLEAR_VARS)

#编译生成注入工具inject文件
LOCAL_MODULE:= inject

LOCAL_SRC_FILES := inject.c shellcode.s

LOCAL_LDLIBS := 

LOCAL_CFLAGS :=  

include $(BUILD_EXECUTABLE)

Application.mk文件

APP_STL := gnustl_static
# 编译的Ndk的连接工具的版本
NDK_TOOLCHAIN_VERSION = 4.8

四、基于Android进程so注入Hook java方法的代码的编译和使用

1.)作者malokch提供的工程代码的下载地址为http://pan.baidu.com/s/1nt9GBsX,考虑到Android的ART模式的复杂性,作者给出的代码中art模式的函数Hook跑不起来,因此这里编译和使用,只考虑dalvik模式下java方法的Hook的实现。说明下,前面的代码分析是作者给出的主要的代码,最终能编译的工程的代码以从刚才这个链接下载工程代码为准。将作者malokch提供的工程代码导入到eclipse中,效果如下图。由于这种方法的Hook中设计到很多的系统dalvik函数,因此为了编译顺利通过,需要导入很多的系统的源码文件或者系统so库文件参与编译才能成功。具体的需要哪些系统源码文件和系统so库文件可以参考工程编译的配置文件Android.mk文件和Application.mk文件



2.)将作者malokch提供的工程TestAR导入eclipse以后,打开c++文件可能会出现常见的类型不能识别的错误如:Function 'XXX' could not be resolved,Type 'XXX' could not be resolved等,极有可能是头文件缺失的问题,具体的可以参考《Eclipse之NDK编译——常见错误的解决方法记录》中的方法,将需要的头文件按照下面的方法添加进来,在添加库文件时候要根据自己的编译生成的elf文件的需求,来具体的添加。

添加方法(windows环境)
右击项目 --> Properties --> 左侧C/C++ General --> Paths and Symbols --> 右侧Includes --> GNU C++(.cpp) --> Add

${NDKROOT}platformsandroid-19arch-armusrinclude
${NDKROOT}sourcescxx-stlgnu-libstdc++4.8include 
${NDKROOT}sourcescxx-stlgnu-libstdc++4.8libsarmeabiinclude
${NDKROOT} oolchainsarm-Linux-androideabi-4.8prebuiltwindowslibgccarm-linux-androideabi4.8include



3.)作者malokch提供的工程TestAR已经写好了用gdb和gdbserver调试的配置文件在 TestARlibsarmeabigdb.setup,同样根据自己的NDK的版本配置相应的gdb.setup文件就可以使用gdb调试作者给的代码了。



4.)关于工程TestAR中进行Android进程so注入的java方法Hook的实际案例的如下图:



5.)作者malokch提供的工程TestAR的编译是使用的android-ndk-r9c版本的具体是32bit还是64bit的不知道了,这里采用https://dl.google.com/Android/ndk/android-ndk-r9c-Linux-x86_64.tar.bz2版本的,编译链接工具使用 toolchain 4.8 的,前面用toolchain 4.9版本的编译链接工具各种报错,改都改不过来。后面发现根本就不是编译连接工具的问题,而是作者的工程TestAR原本就是编译不通过的,需要修改几个地方,由于作者malokch自己都讲ART模式下的Hook是不成功,因此这里只考虑DVM模式下的情况,对作者的工程进行3个地方的修改再编译,如下图:

Android.mk文件的修改


so.cpp文件的修改


MethodHooker.cpp文件的修改


在ubuntu下,配置好Android的开发环境,重新编译TestAR工程,目标apk文件com.example.testar.apk自行eclispe编译



6.)工程TestAR编译成功后文件的使用,作者已经在工程目录下文件 TestARjni un.bat中写好了。具体需要改的地方可以参照作者的文件信息,结合自己的编译环境进行修改。

先 adb install 安装目标进程com.example.testar.apk应用到真机或者模拟器上,然后执行在windows下执行下面功能的脚本,进行Hook目标函数的测试。

adb push xxxxlibTest.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

需要说明的是,作者malokch给出的脚本run.bat只能在Andorid模拟器里使用并且能够Hook函数成功不能在Android真机上使用,因为在Android真机的环境下是不能直接 adb push 文件到Android系统的文件/datata/local 目录下,会提示权限拒绝;如果要用Android真机测试,需要修改工程TestAR中的文件路径/datata/local为 /data/local/tmp 进行编译后测试,否则无法进行测试。下面是我使用Android api 19的模拟器进行Hook测试的结果,并且Hook目标函数成功。


工程TestAR能编译成功的源码的下载地址:http://download.csdn.net/detail/qq1084283172/9719976

工程TestAR编译成功后,生成的 TestAR.apk、inject、libso.so、libTest.so文件的下载地址:http://download.csdn.net/detail/qq1084283172/9719973


感谢链接

http://bbs.pediy.com/showthread.php?t=186054

http://bbs.pediy.com/showthread.php?t=192803

http://blog.csdn.net/qq1084283172/article/details/53668109

http://blog.csdn.net/roland_sun/article/details/38640297 

http://www.jianshu.com/p/052b6dd45659

http://www.cnblogs.com/vendanner/p/5021890.html

http://blog.csdn.net/ysuiboli/article/details/43526887


原文地址:https://www.cnblogs.com/csnd/p/11800653.html