技术准备
下面来看一下这个技术需要哪些知识点
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("%s\n",pn); printf("%s\n",is); pid_t target_pid; target_pid = find_pid_of(pn); printf("pid: %d\n",target_pid); int ret = inject_remote_process(target_pid, is, "InjectInterface", (void*)"I'm parameter!", strlen("I'm parameter!") ); printf("result: %d\n",ret); }
就是他的主函数代码
我们看到有一个重要的函数:
inject_remote_process
第一个参数:注入进程的id
第二个参数:需要注入到目标进程的so文件
第三个参数:so文件中需要执行的函数名
第四个参数:执行函数的参数
第五个参数:执行函数的参数的长度
主要还是前面三个参数。
这里我们通过find_pid_of(pn)函数来获取进程id
传递进去的是进程名
Android中应用的进程名就是包名
char *pn = "com.example.testar";
看到上面注入到目标进程的so文件
char *is = "/data/local/libso.so";
下面再来看一下这个so文件的源代码
so.cpp
#include "jni.h" #include <android_runtime/AndroidRuntime.h> #include "android/log.h" #include "stdio.h" #include "stdlib.h" #include "MethodHooker.h" #include <utils/CallStack.h> #define log(a,b) __android_log_write(ANDROID_LOG_INFO,a,b); // LOGÀàÐÍ:info #define log_(b) __android_log_write(ANDROID_LOG_INFO,"JNI_LOG_INFO",b); // LOGÀàÐÍ:info extern "C" void InjectInterface(char*arg){ log_("*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*"); log_("*-*-*-*-*-* Injected so *-*-*-*-*-*-*-*"); log_("*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*"); Hook(); log_("*-*-*-*-*-*-*- End -*-*-*-*-*-*-*-*-*-*"); } extern "C" JNIEXPORT jstring JNICALL Java_com_example_testar_InjectApplication_test(JNIEnv *env, jclass clazz) { return env->NewStringUTF("haha "); }
在这个文件中,我们看到了函数InjectInterface了,因为so是C++程序,但是inject是C程序,为了兼容,就有这种方式了
extern "C" 函数{
//do something
}
这个代码没什么难度和复杂性
这个函数中调用了Hook函数,下面在来看一下Hook函数的定义
MethodHooker.cpp的实现
#include "MethodHooker.h" #include "jni.h" #include "android_runtime/AndroidRuntime.h" #include "android/log.h" #include "stdio.h" #include "stdlib.h" #include "native.h" #include <dlfcn.h> #define ANDROID_SMP 0 #include "Dalvik.h" #include "alloc/Alloc.h" #define ALOG(...) __android_log_print(ANDROID_LOG_VERBOSE, __VA_ARGS__) static bool g_bAttatedT; static JavaVM *g_JavaVM; void init() { g_bAttatedT = false; g_JavaVM = android::AndroidRuntime::getJavaVM(); } static JNIEnv *GetEnv() { int status; JNIEnv *envnow = NULL; status = g_JavaVM->GetEnv((void **)&envnow, JNI_VERSION_1_4); if(status < 0) { status = g_JavaVM->AttachCurrentThread(&envnow, NULL); if(status < 0) { return NULL; } g_bAttatedT = true; } return envnow; } static void DetachCurrent() { if(g_bAttatedT) { g_JavaVM->DetachCurrentThread(); } } static int computeJniArgInfo(const DexProto* proto) { const char* sig = dexProtoGetShorty(proto); int returnType, jniArgInfo; u4 hints; /* The first shorty character is the return type. */ switch (*(sig++)) { case 'V': returnType = DALVIK_JNI_RETURN_VOID; break; case 'F': returnType = DALVIK_JNI_RETURN_FLOAT; break; case 'D': returnType = DALVIK_JNI_RETURN_DOUBLE; break; case 'J': returnType = DALVIK_JNI_RETURN_S8; break; case 'Z': case 'B': returnType = DALVIK_JNI_RETURN_S1; break; case 'C': returnType = DALVIK_JNI_RETURN_U2; break; case 'S': returnType = DALVIK_JNI_RETURN_S2; break; default: returnType = DALVIK_JNI_RETURN_S4; break; } jniArgInfo = returnType << DALVIK_JNI_RETURN_SHIFT; hints = dvmPlatformInvokeHints(proto); if (hints & DALVIK_JNI_NO_ARG_INFO) { jniArgInfo |= DALVIK_JNI_NO_ARG_INFO; } else { assert((hints & DALVIK_JNI_RETURN_MASK) == 0); jniArgInfo |= hints; } return jniArgInfo; } int ClearException(JNIEnv *jenv){ jthrowable exception = jenv->ExceptionOccurred(); if (exception != NULL) { jenv->ExceptionDescribe(); jenv->ExceptionClear(); return true; } return false; } bool isArt(){ return true; } static jclass findAppClass(JNIEnv *jenv,const char *apn){ jclass clazzApplicationLoaders = jenv->FindClass("android/app/ApplicationLoaders"); jthrowable exception = jenv->ExceptionOccurred(); if (ClearException(jenv)) { ALOG("Exception","No class : %s", "android/app/ApplicationLoaders"); return NULL; } jfieldID fieldApplicationLoaders = jenv->GetStaticFieldID(clazzApplicationLoaders,"gApplicationLoaders","Landroid/app/ApplicationLoaders;"); if (ClearException(jenv)) { ALOG("Exception","No Static Field :%s","gApplicationLoaders"); return NULL; } jobject objApplicationLoaders = jenv->GetStaticObjectField(clazzApplicationLoaders,fieldApplicationLoaders); if (ClearException(jenv)) { ALOG("Exception","GetStaticObjectField is failed [%s","gApplicationLoaders"); return NULL; } jfieldID fieldLoaders = jenv->GetFieldID(clazzApplicationLoaders,"mLoaders","Ljava/util/Map;"); if (ClearException(jenv)) { ALOG("Exception","No Field :%s","mLoaders"); return NULL; } jobject objLoaders = jenv->GetObjectField(objApplicationLoaders,fieldLoaders); if (ClearException(jenv)) { ALOG("Exception","No object :%s","mLoaders"); return NULL; } jclass clazzHashMap = jenv->GetObjectClass(objLoaders); jmethodID methodValues = jenv->GetMethodID(clazzHashMap,"values","()Ljava/util/Collection;"); jobject values = jenv->CallObjectMethod(objLoaders,methodValues); jclass clazzValues = jenv->GetObjectClass(values); jmethodID methodToArray = jenv->GetMethodID(clazzValues,"toArray","()[Ljava/lang/Object;"); if (ClearException(jenv)) { ALOG("Exception","No Method:%s","toArray"); return NULL; } jobjectArray classLoaders = (jobjectArray)jenv->CallObjectMethod(values,methodToArray); if (ClearException(jenv)) { ALOG("Exception","CallObjectMethod failed :%s","toArray"); return NULL; } int size = jenv->GetArrayLength(classLoaders); for(int i = 0 ; i < size ; i ++){ jobject classLoader = jenv->GetObjectArrayElement(classLoaders,i); jclass clazzCL = jenv->GetObjectClass(classLoader); jmethodID loadClass = jenv->GetMethodID(clazzCL,"loadClass","(Ljava/lang/String;)Ljava/lang/Class;"); jstring param = jenv->NewStringUTF(apn); jclass tClazz = (jclass)jenv->CallObjectMethod(classLoader,loadClass,param); if (ClearException(jenv)) { ALOG("Exception","No"); continue; } return tClazz; } ALOG("Exception","No"); return NULL; } bool HookDalvikMethod(jmethodID jmethod){ Method *method = (Method*)jmethod; SET_METHOD_FLAG(method, ACC_NATIVE); int argsSize = dvmComputeMethodArgsSize(method); if (!dvmIsStaticMethod(method)) argsSize++; method->registersSize = method->insSize = argsSize; if (dvmIsNativeMethod(method)) { method->nativeFunc = dvmResolveNativeMethod; method->jniArgInfo = computeJniArgInfo(&method->prototype); } } bool ClassMethodHook(HookInfo info){ JNIEnv *jenv = GetEnv(); jclass clazzTarget = jenv->FindClass(info.tClazz); if (ClearException(jenv)) { ALOG("Exception","ClassMethodHook[Can't find class:%s in bootclassloader",info.tClazz); clazzTarget = findAppClass(jenv,info.tClazz); if(clazzTarget == NULL){ ALOG("Exception","%s","Error in findAppClass"); return false; } } jmethodID method = jenv->GetMethodID(clazzTarget,info.tMethod,info.tMeihodSig); if(method==NULL){ ALOG("Exception","ClassMethodHook[Can't find method:%s",info.tMethod); return false; } /* if(isArt()){ HookArtMethod(jenv,method); }else{ HookDalvikMethod(method); } */ HookDalvikMethod(method); JNINativeMethod gMethod[] = { {info.tMethod, info.tMeihodSig, info.handleFunc}, }; if(info.handleFunc != NULL){ if (jenv->RegisterNatives(clazzTarget, gMethod, 1) < 0) { ALOG("RegisterNatives","err"); return false; } } DetachCurrent(); return true; } int Hook(){ init(); void* handle = dlopen("/data/local/libTest.so",RTLD_NOW); const char *dlopen_error = dlerror(); if(!handle){ ALOG("Error","cannt load plugin :%s",dlopen_error); return -1; } SetupFunc setup = (SetupFunc)dlsym(handle,"getpHookInfo"); const char *dlsym_error = dlerror(); if (dlsym_error) { ALOG("Error","Cannot load symbol 'getpHookInfo' :%s" , dlsym_error); dlclose(handle); return 1; } HookInfo *hookInfo; setup(&hookInfo); ALOG("LOG","Target Class:%s",hookInfo[0].tClazz); ALOG("LOG","Target Method:%s",hookInfo[0].tMethod); ClassMethodHook(hookInfo[0]); }
这个代码就有点多了,而且核心功能的代码都是在这里实现的。
首先来看一下Hook函数:
int Hook(){ init(); void* handle = dlopen("/data/local/libTest.so",RTLD_NOW); const char *dlopen_error = dlerror(); if(!handle){ ALOG("Error","cannt load plugin :%s",dlopen_error); return -1; } SetupFunc setup = (SetupFunc)dlsym(handle,"getpHookInfo"); const char *dlsym_error = dlerror(); if (dlsym_error) { ALOG("Error","Cannot load symbol 'getpHookInfo' :%s" , dlsym_error); dlclose(handle); return 1; } HookInfo *hookInfo; setup(&hookInfo); ALOG("LOG","Target Class:%s",hookInfo[0].tClazz); ALOG("LOG","Target Method:%s",hookInfo[0].tMethod); ClassMethodHook(hookInfo[0]); }
这个函数中,我们看到使用了dlopen系列的函数,主要是用来打开so文件,然后执行文件中的指定函数
我们看到主要还是执行getpHookInfo函数,我们就去看一下这个函数的定义
Test.c
#include "native.h" #include <android/log.h> #include "stdio.h" #include "stdlib.h" #include "MethodHooker.h" #define log(a,b) __android_log_print(ANDROID_LOG_VERBOSE,a,b); #define log_(b) __android_log_print(ANDROID_LOG_VERBOSE,"JNI_LOG_INFO",b); int getpHookInfo(HookInfo** pInfo); JNIEXPORT void JNICALL Java_com_example_testar_InjectClassloader_hookMethodNative (JNIEnv * jenv, jobject jboj, jobject jobj, jclass jclazz, jint slot) { //log("TestAE","start Inject other process"); } JNIEXPORT jstring JNICALL test(JNIEnv *env, jclass clazz) { //__android_log_print(ANDROID_LOG_VERBOSE, "tag", "call <native_printf> in java"); return (*env)->NewStringUTF(env,"haha ");; } HookInfo hookInfos[] = { {"android/net/wifi/WifiInfo","getMacAddress","()Ljava/lang/String;",(void*)test}, //{"com/example/testar/MainActivity","test","()Ljava/lang/String;",(void*)test}, //{"android/app/ApplicationLoaders","getText","()Ljava/lang/CharSequence;",(void*)test}, }; int getpHookInfo(HookInfo** pInfo){ *pInfo = hookInfos; return sizeof(hookInfos) / sizeof(hookInfos[0]); }
看一下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函数
看一下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
这里我把模拟器中的整个lib文件夹都拷贝过来了
LOCAL_LDFLAGS := -L./lib/ -L$(SYSROOT)/usr/lib -llog -ldvm -landroid_runtime -lart
这样就可以编译so.cpp了
后面的Test.c和inject.c编译方法类似,这里就不解释了。
注:这里其实说到了一种引入系统头文件的编译方式,之前在我的另外一篇文章中:
http://blog.csdn.net/jiangwei0910410003/article/details/40949475
在这篇文章中,我用的方式是将so文件拷贝到NDK的目录中的。
但是这个方式貌似更方便点,而且移植性比较好。本身就是一个项目了,不需要额外的工作就可以编译这个项目了。
编译:
项目的下载地址: http://download.csdn.net/detail/jiangwei0910410003/8263113
编译工作完成之后,我们应该有三个文件:
inject
libTest.so
libso.so
下面我们需要将这三个文件拷贝到设备的/data/local/目录下,为什么要拷贝到这个目录呢?因为上面代码中写的是这个目录呀。不记得的同学在回过头去看一下代码:inject.c中的main函数中以及so.cpp中的Hook函数中
我们先将这三个文件拷贝到指定的磁盘中(这里我是Q盘)
开始拷贝:
adb push inject /data/local/
adb push libso.so /data/local/
adb push libTest.so /data/local/
在修改一下他们的权限
chmod 777 inject
chmod 777 libso.so
chmod 777 libTest.so
当然我们还可以写一个简单的脚本文件一步到位
adb push ..\libs\armeabi\libTest.so /data/local/ adb push ..\libs\armeabi\libso.so /data/local/ adb push ..\libs\armeabi\inject /data/local/ adb shell chmod 777 /data/local/inject adb shell chmod 777 /data/local/libso.so adb shell chmod 777 /data/local/libTest.so adb shell su -c /data/local/inject pause
保存.bat文件,然后放到编译项目的目录下,直接运行即可。
拷贝工作完成了,下面来运行一下Android项目
注意应用的包名为:com.example.testar
这个在inject.c中的main函数中我们写死了这个,因为这个包名就是进程名,我们需要通过进程名来获取进程id的。
运行结果:
这时候我们开启两个终端:
第一个终端:执行inject程序进行注入
./inject
第二个终端:监听log信息
adb logcat -s LOG
拓展
上面我们将系统调用的getMacAddress()方法执行的过程转化成执行test函数了,但是这个test是在底层实现的,现在假如我们想在上层去修改这个具体的返回值,那不能修改一次,就去重新编译底层项目,然后还有拷贝工作,同时还需要重新注入。这个操作就太复杂了,所以我们需要将这些工作移动到Java层来,我们可以这么做:在Java 层代码中实现要修改的逻辑,然后在C 层通过反射机制去获取Java层的方法来获取值。