【轉載】注入安卓進程,並hook java世界的方法

作 者: malokch
鏈 接: http://bbs.pediy.com/showthread.php?t=186054

說明:
安卓系統的可執行對象有兩個世界,一個是Linux Native世界,一個是Java世界.兩個世界能夠通過jvm產生交互,具體來說就是通過jni技術進行互相干涉.但是在正常情況下,只能在Java世界通過jni調用native方法,二native不能在沒有任何java上的支持下干涉java世界.
在一些應用中,我們需要對一個app的java世界進行干涉.再說到linux上的進程注入技術,已不用我多講,但是傳統的linux進程注入技術在安卓上只能進入目標進程的native世界.
於是本教程是要注入別的進程,並hook java世界的java 方法!
文章長,詳情見附件

注入安卓進程,並hook java世界的方法

說明:
安卓系統的可執行對象有兩個世界,一個是Linux Native世界,一個是Java世界.兩個世界能夠通過jvm產生交互,具體來說就是通過jni技術進行互相干涉.但是在正常情況下,只能在Java世界通過jni調用native方法,二native不能在沒有任何java上的支持下干涉java世界.

在一些應用中,我們需要對一個app的java世界進行干涉.再說到linux上的進程注入技術,已不用我多講,但是傳統的linux進程注入技術在安卓上只能進入目標進程的native世界.

於是本教程是要注入別的進程,並hook java世界的java 方法!

條件:

1) 手機已root
2) 佈置好了的ndk環境
3) 網友貢獻的inject代碼

由於安卓上的進程注入網上已經有很多方案了,這裏就不列舉了,這裏就假設讀者已經能夠將so注入到別的進程並順利運行了.

首先貼一下這次的目標
代碼:
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) {
                    // TODO Auto-generated method stub
                    WifiManager wifi = (WifiManager) getSystemService(Context.WIFI_SERVICE);
                    WifiInfo info = wifi.getConnectionInfo();
                    System.out.println("Wifi mac :" + info.getMacAddress());
                    System.out.println("return " + 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";
        }
    }
我們的目標是上面的test()方法,我們要改變其返回值.
接下來看看我們要注入到目標進程的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"
#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
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)
{
    Abort_();
    return env->NewStringUTF("haha ");;
}


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__)

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){
    //������oaders
    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;
    }
    //̡ȡmap֐Ķalues
    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;
    //ؼ!!Ŀ귽ОĎnative
    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);
    }

    JNINativeMethod gMethod[] = {
        {info.tMethod, info.tMeihodSig, info.handleFunc},
    };

    //funcΪNULLʱהА������������չɍ
    if(info.handleFunc != NULL){
        //ؼ!!Ŀ귽؁הҥĮative
        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]);
}

以下是我們想要的目標進程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");
    }

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

程序大致的流程是這樣的,首先將so.so注入到目標進程,執行裏面的Hook()函數,然後Hook()加載libTest.so,獲取裏面定義的Hook信息.接着用ClassMethodHook掛鉤java世界的方法.

關鍵一:

從native世界進入java世界.熟悉jni編程的都知道,java到native的橋樑是JNIEnv,我們只要獲得一個JNIEnv就能進入到java世界了.突破點就在AndroidRuntime,android::AndroidRuntime::getJavaVM();這個靜態方法能夠獲取一個JavaVM, JavaVM->GetEnv方法能夠獲得一個JNIEnv了.JNIEnv是和線程相關的,使用前一定記得將其附加到當前進程,也要在適當的時候將其銷燬.

關鍵二:

怎麼影響內存裏的java代碼,這個情況替換內存是不現實的,但是可以取巧.我們知道java代碼裏將一個方法聲明爲native方法時,對此函數的調用就會到native世界裏找.我們何不在運行時將一個不是native的方法修改成native方法呢?這是可以做到的,看着段代碼
代碼:

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

Jni反射調用java方法時要用到一個jmethodID指針,這個指針在Dalvik裏其實就是Method類,通過修改這個類的一些屬性就可以實現在運行時將一個方法修改成native方法.

SET_METHOD_FLAG(method, ACC_NATIVE);

就是這麼做的,其後面的代碼就是設定native函數的參數佔用內存大小統計.

也許你發現了,雖然將其修改成一個native方法了,但是這個方法對應的native代碼在那裏呢?這樣做
代碼:

    if (jenv->RegisterNatives(clazzTarget, gMethod, 1) < 0) {
        ALOG("RegisterNatives","err");
        return false;
    }

可以將一個native函數綁定到一個java的native方法

這樣就能夠實現開始的目標了!

我這裏講得是有點粗略了,但是這個技術牽涉到的知識太多了,主要是給老鳥參考的,小菜們拿來用用就好,要是要講得小菜們都能明白,就不知要講到何年何月了.還有就是上面的art環境的代碼是跑不起來的,等我後面有空完善了再發一次吧!

本教程僅供學習交流用途,請勿用於非法用途!
測試代碼猛擊這裏:http://pan.baidu.com/s/1nt9GBsX

//############### 14.16.17更新 ##########################
之前沒有提到調用原來的方法的方法,方法是這樣,直接內存拷貝存起來
代碼:

  uint mlen = sizeof(Method);
  Method *oldMeth = (Method*)malloc(mlen);
  memcpy(oldMeth,method,mlen);
  info->odlMethod = oldMeth;
  info->curMethod = method;

調用原來的方法就把內存拷回去,調用後再hook一次
代碼:

  memcpy(hi->curMethod,hi->odlMethod,mlen);

  jmethodID om = (jmethodID)hi->curMethod;
  jenv->CallVoidMethod(me,om,gDevice_Sensors);

  ClassMethodHook(jenv,&baiduhookInfos[0]);
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章