dex注入实现详解
最近在研究Android綠色安全這一塊,具體到上層的業(yè)務(wù)就是“去第三方APP的廣告”。如果既想使用第三方APP,又不想看到一些無(wú)良的廣告,那dex注入基本無(wú)法避免。本文針對(duì)網(wǎng)上一些大牛分享的文章,進(jìn)行了一些簡(jiǎn)單的實(shí)現(xiàn),總結(jié)和分享自是不能少的。Ps:感謝金山毒霸實(shí)現(xiàn)了該功能,感謝大牛們破解之后的無(wú)私分享。
參考文章
金山手機(jī)毒霸工作原理【引用1】
【原創(chuàng)】手機(jī)毒霸去廣告功能分析一:總體分析【引用2】
【原創(chuàng)】手機(jī)毒霸去廣告功能分析三:java代碼(dex)注入
Android中的so注入(inject)和掛鉤(hook) - For both x86 and arm 【引用3】
源碼相關(guān)
android.os.Handler 自行eclipse 關(guān)聯(lián)即可;
android.app.ActivityThread 在線(xiàn)代碼;
實(shí)現(xiàn)目標(biāo)
系統(tǒng):Android 4.2.2 平板
功能:將一段dex代碼注入到HelloWord APP中,dex對(duì)應(yīng)的java代碼要求能夠攔截目標(biāo)APP中的onPause與onResume 回調(diào),輸出打印。
基本原理
其實(shí)原理在各路大牛的文章里面已經(jīng)解釋的很清楚了,這里再不厭其煩的絮叨絮叨,主要是捋一捋思路,別整亂嘍。
1.獲得root權(quán)限后,通過(guò)ptrace()注入到指定pid的進(jìn)程中;
Android下的注入都是從Linux下ptrace()函數(shù)繼承下來(lái)的,具體原理不便深入。網(wǎng)上大牛已有相關(guān)的開(kāi)源工具,這里采用【引用3】篇幅中博主開(kāi)源出的代碼,注意修改對(duì)應(yīng)參數(shù)即可。【引用3】中是以注入系統(tǒng)進(jìn)程/system/bin/surfaceflinger為例的,我們這里需要修改成目標(biāo)APP的包名。這部分拿到開(kāi)源代碼之后使用ndk編譯生成注入工具文件inject;
2.注入代碼調(diào)用功能庫(kù).so中的接口,它的作用是利用反射注入dex文件、并調(diào)用相應(yīng)的java代碼;
這里對(duì)應(yīng)的就是【引用3】中hello.c部分了,作為注入的功能代碼關(guān)鍵部分,這部分不能打印兩句草草了事。這里采用【引用1】中對(duì)金山毒霸分析結(jié)果得出的代碼拿出來(lái)來(lái)實(shí)現(xiàn)dex注入與java層代碼調(diào)用,具體實(shí)現(xiàn)與分析見(jiàn)后文。這里也是C代碼通過(guò)ndk編譯生成注入功能庫(kù),呃,libhelloTool.so;
3.生成dex的java源碼通過(guò)反射置換 ActivityThread 中mH屬性中的的mCallback回調(diào),來(lái)實(shí)現(xiàn)攔截Activity生命周期回調(diào)的HOOK功能;
通過(guò)上一步程序會(huì)執(zhí)行到j(luò)ava層中,既然要下鉤子,就要弄清楚我們要鉤在哪里才有效。很明顯的,既然要攔截界面的onPause、onResume消息,那就必須要了解Activity的生命周期回調(diào)在底層是如何實(shí)現(xiàn)消息分發(fā)的,知其所以然之后才好下手。有了前面提到的幾篇大牛博客的文章,我們可以很清晰的定位到android.app.ActivityThread 類(lèi)中的mH變量:
public final class ActivityThread { … final H mH = new H(); … private void queueOrSendMessage(int what, Object obj, int arg1, int arg2) {synchronized (this) {if (DEBUG_MESSAGES) Slog.v(TAG, "SCHEDULE " + what + " " + mH.codeToString(what)+ ": " + arg1 + " / " + obj);Message msg = Message.obtain();msg.what = what;msg.obj = obj;msg.arg1 = arg1;msg.arg2 = arg2;mH.sendMessage(msg);} } … private class H extends Handler {public static final int LAUNCH_ACTIVITY = 100;public static final int PAUSE_ACTIVITY = 101;public static final int PAUSE_ACTIVITY_FINISHING= 102;public static final int STOP_ACTIVITY_SHOW = 103;public static final int STOP_ACTIVITY_HIDE = 104;public static final int SHOW_WINDOW = 105;public static final int HIDE_WINDOW = 106;public static final int RESUME_ACTIVITY = 107;public static final int SEND_RESULT = 108;…public void handleMessage(Message msg) {if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what));switch (msg.what) {case LAUNCH_ACTIVITY: {Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStart");ActivityClientRecord r = (ActivityClientRecord)msg.obj;r.packageInfo = getPackageInfoNoCheck(r.activityInfo.applicationInfo, r.compatInfo);handleLaunchActivity(r, null);Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);} break;…}}… }從源碼上就能看出來(lái),底層消息派發(fā)都在內(nèi)部類(lèi)H中實(shí)現(xiàn),而H實(shí)際上是Handler的子類(lèi)。對(duì)應(yīng)的H本身是個(gè)final類(lèi)型的內(nèi)部私有類(lèi),做手腳不甚方便,考慮到要攔截的實(shí)際情況,伸手到其父類(lèi)中的屬性的mCallback回調(diào)就是個(gè)很好的選擇了。從前面所引博客中對(duì)金山毒霸的反編譯情況來(lái)看即是這個(gè)思路。引用【引用2】中的一句話(huà)即:
f) 替換當(dāng)前ActivityThread中的mH(Handler類(lèi)型)的mCallback,用金山自定義的一個(gè)callback對(duì)象來(lái)包裹過(guò)原callback并且替換原callback,從而起到hook作用。
實(shí)現(xiàn)流程
必備工具
注意:為了快速調(diào)通,這里所有參數(shù)都是寫(xiě)死的,這就限制了后續(xù)驗(yàn)證流程必須要實(shí)現(xiàn)過(guò)程中的代碼編寫(xiě)的一致。如果只是作為預(yù)研、測(cè)試是否可行的階段,這種做法無(wú)可厚非;相對(duì)的如果是正規(guī)的開(kāi)發(fā)流程中,在迭代周期里面做好通用性的設(shè)計(jì)是必要的。
開(kāi)始實(shí)現(xiàn)
建立目標(biāo)APP
這一步最簡(jiǎn)單了,新建一個(gè)HelloWord Android工程,運(yùn)行安裝到測(cè)試機(jī)器中,我這里設(shè)置了包名為:com.inject.helloword 后面注入工具中需要用到;
注入工具
如【引用3】的方法,簡(jiǎn)歷文件夾填好配置文件。編輯injec.c文件更換參數(shù),主要在main函數(shù)中:
int main(int argc, char** argv) {pid_t target_pid;//更換為目標(biāo)應(yīng)用的包名target_pid = find_pid_of("com.inject.helloword");if (-1 == target_pid) {printf("Can't find the process\n");return -1;}//設(shè)置注入代碼庫(kù)位置、調(diào)用接口與參數(shù)inject_remote_process(target_pid, "/data/libhelloTool.so", "hook_entry", "I'm parameter!hehe", strlen("I'm parameter! hehe "));return 0; }如果想深入研究注入的原理,可以仔細(xì)分析相關(guān)函數(shù)的實(shí)現(xiàn)即可,NDK編譯后生成注入工具inject文件;
kf2lc@kf2lc-OptiPlex-3020:~/develop/inject/jni$ ndk-build
Compile x86 : inject <= jni inject.c
Executable : inject
Install : inject =libs/x86/inject
Compile thumb : inject <= jni inject.c
Executable : inject
Install : inject =libs/armeabi-v7a/inject
注入代碼庫(kù)
建立文件夾如【引用3】所述,修改注入接口方法hook_entry如【引用1】中的分析實(shí)現(xiàn),代碼并注釋如下:
#include <unistd.h> #include <stdio.h> #include <stdlib.h> #include <android/log.h> #include <elf.h> #include <fcntl.h> #include <jni.h> #include <dlfcn.h>#define LOG_TAG "DEBUG" #define LOGD(fmt, args...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, fmt, ##args) int invoke_dex_method(const char* dexPath, const char* dexOptDir, const char* className, const char* methodName, int argc, char *argv[]);int hook_entry(char * a){ LOGD("Hook success, pid = %d\n", getpid()); LOGD("Hello %s\n", a); //參數(shù)直接寫(xiě)死是個(gè)取巧的方法int ret = invoke_dex_method("/data/injects/DexInject.apk","/data/data/com.inject.helloword/cache","com/inject/dexinject/HookTool","dexInject",0,NULL);LOGD("Hello %d\n",ret);return 0; }JNIEnv* (*getJNIEnv)(); /** * PARAM: * dexPath要注入的apk/jar路徑 * dexOptDir 緩存路徑,注意需要目標(biāo)應(yīng)用進(jìn)程中可寫(xiě)的目錄 * className 執(zhí)行方法所在類(lèi)名 * methodName 執(zhí)行的方法名 * argc 參數(shù)之流這里沒(méi)有使用 * argv 參數(shù)之流這里沒(méi)有使用 */ int invoke_dex_method(const char* dexPath, const char* dexOptDir, const char* className, const char* methodName, int argc, char *argv[]) {//獲取JNIEnvvoid* handle = dlopen("/system/lib/libandroid_runtime.so", RTLD_NOW);getJNIEnv = dlsym(handle, "_ZN7android14AndroidRuntime9getJNIEnvEv");JNIEnv* env = getJNIEnv();//調(diào)用ClassLoader中的getSystemClassLoader方法獲取當(dāng)前進(jìn)程的ClassLoaderjclass classloaderClass = (*env)->FindClass(env,"java/lang/ClassLoader");jmethodID getsysloaderMethod = (*env)->GetStaticMethodID(env,classloaderClass, "getSystemClassLoader", "()Ljava/lang/ClassLoader;");jobject loader = (*env)->CallStaticObjectMethod(env, classloaderClass, getsysloaderMethod);//以進(jìn)程現(xiàn)有的ClassLoader、要注入的dex路徑為參數(shù)構(gòu)造注入后的DexClassLoaderjstring dexpath = (*env)->NewStringUTF(env, dexPath);jstring dex_odex_path = (*env)->NewStringUTF(env,dexOptDir);jclass dexLoaderClass = (*env)->FindClass(env,"dalvik/system/DexClassLoader");jmethodID initDexLoaderMethod = (*env)->GetMethodID(env, dexLoaderClass, "<init>", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/ClassLoader;)V");jobject dexLoader = (*env)->NewObject(env, dexLoaderClass, initDexLoaderMethod,dexpath,dex_odex_path,NULL,loader);//獲取新出爐的DexClassLoader中findClass方法加載dex中要執(zhí)行代碼所在類(lèi)jmethodID findclassMethod = (*env)->GetMethodID(env,dexLoaderClass,"findClass","(Ljava/lang/String;)Ljava/lang/Class;");jstring javaClassName = (*env)->NewStringUTF(env,className);jclass javaClientClass = (*env)->CallObjectMethod(env,dexLoader,findclassMethod,javaClassName);//獲取注入dex中要執(zhí)行的方法jmethodID start_inject_method = (*env)->GetStaticMethodID(env, javaClientClass, methodName, "()V");//執(zhí)行之注意目標(biāo)方法必須是靜態(tài)公有的(*env)->CallStaticVoidMethod(env,javaClientClass,start_inject_method); }原帖中倒數(shù)第二句GetStaticMethodID方法所給的參數(shù)有誤,這里修正一下,如此就完成了dex注入并調(diào)用了java代碼中的:com.inject.dexinject. HookTool. dexInject() 方法。同上采用NDK編譯,生成注入庫(kù):libhelloTool.so。
生成dex
說(shuō)是dex注入,實(shí)際上從上一段的代碼中可以知道最終采用的是DexClassLoader類(lèi)來(lái)實(shí)現(xiàn)注入。托之前研究過(guò)一段APK加殼的福,對(duì)這里還相對(duì)比較了解,DexClassLoader的主要參數(shù)路徑實(shí)際上應(yīng)該是一個(gè)apk/jar的路徑,具體可見(jiàn)相關(guān)的SDK文檔。這里直接編一個(gè)APK丟進(jìn)去就好。
建立Android應(yīng)用工程DexInject,干掉無(wú)關(guān)的界面配置。建立如下類(lèi):
1.自定義Callback,加入攔截操作代碼(打印);
2.工具類(lèi),攔截實(shí)現(xiàn)代碼;
public class HookTool {public static final String TAG = "Inject";public static void dexInject() {Log.d(TAG, "this is dex code,welcome to HookTool~");try { Object currentActivityThread = ReflectUtils.invokeStaticMethod("android.app.ActivityThread", "currentActivityThread",new Class[] {}, new Object[] {});Handler localHandler = (Handler) ReflectUtils.getFiled("android.app.ActivityThread","mH",currentActivityThread);HookCallback oriCallback = (HookCallback) ReflectUtils.getFieldObject(Handler.class, localHandler, "mCallback");HookCallback hookCallBack = new HookCallback(oriCallback);ReflectUtils.setFieldObject(Handler.class, localHandler, "mCallback", hookCallBack);} catch (IllegalArgumentException e) {e.printStackTrace();}}}3.反射工具類(lèi)
這部分就不貼了,反射工具到處都是。
最后編譯生成DexInject.apk
驗(yàn)證結(jié)果
Push各路工具和數(shù)據(jù)到測(cè)試機(jī)器:
adb push DexInject.apk /data/injects
adb push inject /data/
adb push libhelloTool.so /data/
運(yùn)行注入工具:
這是按home退出APP再進(jìn)入,通過(guò)日志過(guò)濾器可以得到:
原文地址: http://taoyuanxiaoqi.com/2015/03/16/dexinject/
總結(jié)
- 上一篇: Android apk动态加载机制的研究
- 下一篇: APK加壳【1】初步方案实现详解