編輯:Android資訊
Android中通過注入技術修改系統返回的Mac地址
下面來看一下這個技術需要哪些知識點
1、如何將非native方法變成native方法
2、如何將native方法直接注冊(不需要jni這樣的頭文件了)
3、Android中的類加載器相關知識
4、如何編譯Android系統引用系統頭文件的NDK項目
雖然這裡有這四個知識點,但是其中有兩個我在之前的blog中已經介紹了:
Android中的類加載器:http://blog.csdn.net/jiangwei0910410003/article/details/41384667
如何編譯Android系統引用系統頭文件的NDK項目:http://blog.csdn.net/jiangwei0910410003/article/details/40949475
不過在這篇文章中,我們在介紹一種新的編譯方式
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函數
因為我們是通過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 ..\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
這個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、注入進程的相關知識
Android引入即用的便捷開發框架WelikeAndroid
WelikeAndroid 是什麼? WelikeAndroid 是一款引入即用的便捷開發框架,致力於為程序員打造最佳的編程體驗,使用WelikeAndroid,
Android ORM 框架 greenDAO 使用經驗總結
前言 我相信,在平時的開發過程中,大家一定會或多或少地接觸到 SQLite。然而在使用它時,我們往往需要做許多額外的工作,像編寫 SQL 語句與解析查詢結果等。所
Android 緩存工具 DiskLruCache 學習筆記
DiskLruCache是一個十分好用的android緩存工具,我們可以從GitHub上下載其源碼:https://github.com/JakeWharton/
捕獲Android文本中鏈接點擊事件
Android中的TTextView很強大,我們可以不僅可以設置純文本為其內容,還可以設置包含網址和電子郵件地址的內容,並且使得這些點擊可以點擊。但是我們可以捕獲