致謝:html
感謝 簡行之旅的這篇blog:http://blog.csdn.net/l173864930/article/details/38455951,這篇文章是參考這篇blog的進行一步一步操做的,假設沒有這篇好文章的話,貌似我這篇文章的誕生可能就有點難度了。java
今天週日,昨天花了一天的時間總算是搞定了。問題仍是想相應用程序的行爲進行攔截的操做,就是像小米手機同樣,哪些應用在獲取你什麼權限的信息。在以前寫過相應用程序的行爲進行攔截的方式(C層)實現的博客,在寫完這篇以後。原本想是儘快的把Java層攔截的文章結束的,但是因爲各類緣由吧。因此一直沒有時間去弄這些了。今天算是有空。就總結一下吧。如下進入正題:android
咱們知道現在一些安全軟件都會有一個功能就是可以攔截應用的行爲(比方地理位置信息。通信錄等),因此這裏就來實現如下這種功能,固然實現這種功能有兩種方式,一種是從底層進行攔截。這個我在以前的博客中已經解說過了。ios
沒看過的同窗可以轉戰:shell
http://blog.csdn.net/jiangwei0910410003/article/details/39346151
編程
另外一種方式就是從上層進行攔截,也就是咱們今天所要說的內容,這種方式都是可以的,固然很是多人不少其它的偏向上層。因爲底層攔截需要熟知Binder協議和Binder的數據格式的。上層攔截就簡單點了。安全
首先咱們需要了解一點知識就是不管是底層攔截仍是上層攔截。都需要一個技術支持:進程注入,關於這個知識點,這裏就不做解釋了,不瞭解的同窗可以轉戰:http://blog.csdn.net/jiangwei0910410003/article/details/39293635app
瞭解了進程注入以後,這篇文章主要解說三點知識:jvm
一、怎樣動態載入so。並且運行當中的函數ide
二、怎樣在C層運行Java方法(NDK一般是指Java中調用C層函數)
三、怎樣改動系統服務(Context.getSystemService(String...)事實上返回來的就是Binder對象)對象
固然咱們還需要一些預備知識:知道怎樣使用NDK進行編譯項目。不瞭解的同窗可以轉戰:
http://blog.csdn.net/jiangwei0910410003/article/details/17710243
這篇文章編譯環境是Window下的,我的感受仍是不方便,仍是在Ubuntu環境下操做比較方便
另外一點需要聲明:就是攔截行爲是需要root權限的
目的:但願將咱們本身的功能模塊(so文件)注入到目標進程中。而後改動目標進程中的某個函數的運行過程
文件:注入功能可運行文件poison、目標進程可運行文件demo一、需要注入的模塊libmyso.so
注入功能的可運行文件核心代碼poison.c,這個功能模塊在後面講到的樣例中也會用到,因此他是公用的
#include <unistd.h> #include <errno.h> #include <stdlib.h> #include <dlfcn.h> #include <sys/mman.h> #include <sys/ptrace.h> #include <sys/wait.h> #include "ptrace_utils.h" #include "elf_utils.h" #include "log.h" #include "tools.h" struct process_hook { pid_t pid; char *dso; } process_hook = {0, ""}; int main(int argc, char* argv[]) { LOGI("argv len:"+argc); if(argc < 2) exit(0); struct pt_regs regs; process_hook.dso = strdup(argv[1]); process_hook.pid = atoi(argv[2]); if (access(process_hook.dso, R_OK|X_OK) < 0) { LOGE("[-] so file must chmod rx\n"); return 1; } const char* process_name = get_process_name(process_hook.pid); ptrace_attach(process_hook.pid, strstr(process_name,"zygote")); LOGI("[+] ptrace attach to [%d] %s\n", process_hook.pid, get_process_name(process_hook.pid)); if (ptrace_getregs(process_hook.pid, ®s) < 0) { LOGE("[-] Can't get regs %d\n", errno); goto DETACH; } LOGI("[+] pc: %x, r7: %d", regs.ARM_pc, regs.ARM_r7); void* remote_dlsym_addr = get_remote_address(process_hook.pid, (void *)dlsym); void* remote_dlopen_addr = get_remote_address(process_hook.pid, (void *)dlopen); LOGI("[+] remote_dlopen address %p\n", remote_dlopen_addr); LOGI("[+] remote_dlsym address %p\n", remote_dlsym_addr); if(ptrace_dlopen(process_hook.pid, remote_dlopen_addr, process_hook.dso) == NULL){ LOGE("[-] Ptrace dlopen fail. %s\n", dlerror()); } if (regs.ARM_pc & 1 ) { regs.ARM_pc &= (~1u); regs.ARM_cpsr |= CPSR_T_MASK; } else { regs.ARM_cpsr &= ~CPSR_T_MASK; } if (ptrace_setregs(process_hook.pid, ®s) == -1) { LOGE("[-] Set regs fail. %s\n", strerror(errno)); goto DETACH; } LOGI("[+] Inject success!\n"); DETACH: ptrace_detach(process_hook.pid); LOGI("[+] Inject done!\n"); return 0; }咱們看到,這個注入功能的代碼和咱們以前說的從底層進行攔截的那篇文章中的注入代碼(inject.c)不太同樣呀?這個是有人在網上重新改寫了一下,事實上功能上沒什麼差異的。咱們從main函數可以看到,有兩個入口參數:
第一個是:需要注入so文件的全路徑
第二個是:需要注入進程的pid
也就是說,咱們在運行poison程序的時候需要傳遞這兩個值。在以前說道的注入代碼(inject.c)中,事實上這兩個參數是在代碼中寫死的,假設忘記的同窗可以回去看一下。就是前面提到的從底層進行攔截的那篇文章。
那麼這樣改動以後,貌似靈活性更高了。
固然注入功能的代碼不止這一個,事實上是一個project。這裏因爲篇幅的緣由就不作介紹了,project的下載地址:
http://download.csdn.net/detail/jiangwei0910410003/8138061
使用NDK編譯一下。生成可運行文件就OK了。
1)目標進程依賴的so文件inso.h和inso.c
__attribute__ ((visibility ("default"))) void setA(int i); __attribute__ ((visibility ("default"))) int getA();
inso.c代碼
#include <stdio.h> #include "inso.h" static int gA = 1; void setA(int i){ gA = i; } int getA(){ return gA; }
編譯成so文件就能夠,項目下載:http://download.csdn.net/detail/jiangwei0910410003/8138107
2)目標進程的可運行文件demo1.c
這個就簡單了,就是很是easy的代碼,起一個循環每個一段時間打印數值,這個項目需要引用上面編譯的inso.so文件
頭文件inso.h(和上面的頭文件是同樣的)
__attribute__ ((visibility ("default"))) void setA(int i); __attribute__ ((visibility ("default"))) int getA();
demo1.c文件
#include <stdio.h> #include <unistd.h> #include "inso.h" #include "log.h" int main(){ LOGI("DEMO1 start."); while(1){ LOGI("%d", getA()); setA(getA() + 1); sleep(2); } return 0; }代碼簡單吧。就是運行循環打印數值,這裏使用的是底層的log方法,在log.h文件裏定義了,篇幅緣由,這裏就不列舉了,項目下載地址:http://download.csdn.net/detail/jiangwei0910410003/8138071
3)注入的模塊功能源文件myso.c
#include <stdio.h> #include <stddef.h> #include <dlfcn.h> #include <pthread.h> #include <stddef.h> #include "log.h" __attribute__ ((__constructor__)) void Main() { LOGI(">>>>>>>>>>>>>Inject Success!!!!<<<<<<<<<<<<<<"); void (*setA_func)(int); void* handle = dlopen("libinso.so", RTLD_NOW); LOGI("Handle:%p",handle); //void (*setA_func)(int) = (void (*)(int))dlsym(handle, "setA"); setA_func = (void (*)(int))dlsym(handle,"setA"); LOGI("Func:%p",setA_func); if (setA_func) { LOGI("setA is Executing!!!"); (*setA_func)(999); } dlclose(handle); }
說明:
這段代碼需要解釋一下,首先來看一下:
__attribute__ ((__constructor__))
gcc爲函數提供了幾種類型的屬性。當中包括:構造函數(constructors)和析構函數(destructors)。
程序猿應當使用類似如下的方式來指定這些屬性:
static void start(void) __attribute__ ((constructor)); static void stop(void) __attribute__ ((destructor));帶有"構造函數"屬性的函數將在main()函數以前被運行,而聲明爲"析構函數"屬性的函數則將在main()退出時運行。
#include <iostream> void breforemain() __attribute__((constructor)); void aftermain() __attribute__((destructor)); class AAA{ public: AAA(){std::cout << "before main function AAA" << std::endl;} ~AAA(){std::cout << "after main function AAA" << std::endl;} }; AAA aaa; void breforemain() { std::cout << "before main function" << std::endl; } void aftermain() { std::cout << "after main function" << std::endl; } int main(int argc,char** argv) { std::cout << "in main function" << std::endl; return 0; }輸出結果:
有點類似於Spring的AOP編程~~
另外一個就是咱們開始說的,怎樣載入so文件,並且運行當中的函數,原理很是easy,就是打開so文件。而後返回一個函數指針。
需要的頭文件:#include <dlfcn.h>
核心代碼:
打開so文件,返回一個句柄handle:
void* handle = dlopen("libinso.so", RTLD_NOW);
獲得指定的函數指針:
setA_func = (void (*)(int))dlsym(handle,"setA");
函數指針的定義:
void (*setA_func)(int);就是這麼簡單,有點類似Java中動態載入jar包,而後運行當中的方法。
項目下載地址:
http://download.csdn.net/detail/jiangwei0910410003/8138109
好了。到這裏離成功不遠了,咱們保證上面的project編譯都能經過,獲得如下文件:
demo一、poison、libmyso.so、libinso.so
而後咱們就可以實踐了
首先我將這些文件複製到手機中的/data/data/文件夾中
adb push demo1 /data/data/
adb push poison /data/data/
adb push libmyso.so /data/data/
拷貝完以後,還需要進入adb shell。改動他們的權限
chmod 777 demo1
chmod 777 poison
chmod 777 libmyso.so
這裏要注意的是,libinso.so文件要單獨複製到/system/lib中。否則在運行demo1的時候,會報錯(找不到libinso.so),固然在拷貝的時候會遇到一點問題
報錯:"Failed to push selection: Read-only file system"
這時候僅僅要改變system文件夾的掛載讀寫屬性就行了
mount -o remount rw /system/
而後進入adb shell在改動一下/system的屬性
chmod 777 /system
而後就可以拷貝了:
adb push libinso.so /system/lib/
而後進入到system/lib中,改動libinso.so的屬性
chmod 777 libinso.so
而後進入到data/data文件夾中。開始運行文件,這時候咱們需要開三個終端:一個是監聽log信息,一個是運行demo1,一個是運行poison,例如如下圖所看到的:
一、監聽log信息
adb logcat -s TTT
二、運行demo1
./demo1
三、運行poison
./poison /data/data/libmyso.so 1440
這裏咱們看到,運行poison有兩個入口參數:一個是so文件的路徑,一個是目標進程(demo1)的pid就是log信息中的顯示的pid
到這裏咱們就實現了咱們的第一個樣例了。
如下繼續:將目標進程改變成一個Android應用
這裏和上邊的惟一差異就是咱們需要將demo1變成一個Android應用
那麼來看一下這個Android應用的代碼:
package com.demo.host; import android.app.Activity; import android.content.Context; import android.os.Bundle; import android.util.Log; public final class MainActivity extends Activity { private static int sA = 1; public static void setA(int a) { sA = a; } public static int getA() { return sA; } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); new Thread() { public void run() { while (true) { Log.i("TTT", "" + getA()); setA(getA() + 1); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } }; }.start(); } }代碼和demo1的功能同樣的,寫個循環,打印數值,project下載地址:http://download.csdn.net/detail/jiangwei0910410003/8138227
這個應用就變成了咱們注入的目標進程,但是有一個問題,咱們怎麼才幹改動setA()方法的行爲呢?在第一個案例中。咱們是動態載入libinso.so,而後獲取到setA()函數。改動器返回值。
這裏咱們需要作的就是怎麼動態的去改動上面的MainActivity中的setA()方法的返回值,事實上這裏就到了我開始說的第二個知識點:怎樣在底層C中調用Java方法?
咱們知道在使用NDK的時候,Java層調用底層C的時候鏈接的紐帶就是那個JNIEnv變量,這個變量是做爲函數參數傳遞過來的。那麼假設咱們在底層C中獲取到這個變量的話,就可以調用Java方法了,但是這裏咱們又未定義本地方法,怎麼獲得JNIEnv變量呢?
答案就是#include <android_runtime/AndroidRuntime.h>這個頭文件,獲得JVM變量以後,而後獲得當前線程的JNIEnv變量:
JavaVM* jvm = AndroidRuntime::getJavaVM(); LOGI("jvm is %p",jvm); jvm->AttachCurrentThread(&jni_env, NULL); //TODO 使用JNIEnv jvm->DetachCurrentThread();
經過AndroidRuntime中的getJavaVM方法獲取jvm變量,而後在獲取當前線程的JNIEnv變量就能夠
關於AndroidRuntime這個類的定義和實現是在 AndroidRuntime源代碼文件夾/jni/AndroidRuntime.cpp中
好了。當咱們拿到JNIEnv變量以後。咱們就可以幹很是多事了,因爲咱們知道在弄NDK的時候。假設使用JNIEnv變量的時候都清楚,他比如Java中的反射機制,可以動態的載入Java中的類。而後獲取其方法。字段等信息,進行操做。
但是現在另外一個問題。就是咱們怎麼去動態載入MainActivity這個類呢?
但是當咱們嘗試使用PathClassLoader去載入MainActivity時,會拋ClassNotFoundException
惟一可行的方案是找到host(目標應用)的PathClassLoader,而後經過這個ClassLoader尋找MainActivity
因爲咱們是注入到MainActivity這個應用的進程中。那麼咱們的注入代碼和MainActivity是在一個進程中的。又因爲Android中一個進程相應一個全局Context對象,因此咱們僅僅要獲得這個進程Context對象的類載入器就可以了
(事實上Android中多個應用是可以跑在一個進程中的。他們會擁有一共同的全局Context變量,固然這個Context不是特定的Activity的。而是Application對象持有的Context)
要想獲得一個進程中的Context對象。
經過閱讀源代碼。發現可以經過如下的方式讀取到Context對象:
假設是System_Process,可以經過例如如下方式獲取
Context context = ActivityThread.mSystemContext假設是非System_Process(即普通的Android進程),可以經過例如如下方式獲取
Context context = ((ApplicationThread)RuntimeInit.getApplicationObject()).app_obj.this$0到這裏,咱們都知道該怎麼辦了,沒錯就是用反射機制,獲取到全局的Context變量
上面的思路是有了,如下在來整理一下吧:
首先咱們需要注入到MainActivity所在的進程,而後改動他的setA()方法。
但是咱們注入的時候是把so文件注入到一個進程中。因此需要在底層改動setA()方法的運行
假設底層想改動/運行Java層方法的話。必需要獲得JNIEnv變量
而後可以經過AndroidRuntime類先獲得jvm變量,而後在經過jvm變量獲得JNIEnv變量
獲得JNIEnv變量以後,用JNIEnv的一些方法去動態載入MainActivity類。而後改動他的方法,但是會出現異常找不到MainActivity類
找不到這個類的緣由是類載入器找的不正確。咱們需要找到全局的Context對象的類載入器。因爲咱們是注入到了MainActivity這個應用的進程中,一個進程有一個全局的Context對象。因此僅僅要獲得它的類載入器就可以了。
而後經過查看源代碼,咱們可以在Java層經過反射獲取到這個對象。
最後:經過上面的分析,咱們還需要一些項目的支持:
一、底層獲取JNIEnv對象的項目。也就是咱們需要注入的so。
二、在上層還需要一個模塊。去獲取到全局的Context對象,而後動態的載入MainActivity類,改動他的方法。
這樣分析以後,咱們可能制定的方案是:
1)上層模塊DemoInject2:
主要是獲取進程的Context變量,而後經過這個變量去載入MainActivity類,改動他的setA()方法的邏輯,核心代碼:
ContextHunter.java
package com.demo.inject2; import android.app.Application; import android.content.Context; public final class ContexHunter { private static Context sContext = null; public static Context getContext() { if (sContext == null) { synchronized (ContexHunter.class) { if (sContext == null) { sContext = searchContextForSystemServer(); if (sContext == null) { sContext = searchContextForZygoteProcess(); } } } } return sContext; } private static Context searchContextForSystemServer() { Context result = null; try { result = (Context) ActivityThread.getSystemContext(); } catch (Exception e) { e.printStackTrace(); return null; } return result; } private static Context searchContextForZygoteProcess() { Context result = null; try { Object obj = RuntimeInit.getApplicationObject(); if (obj != null) { obj = ApplicationThread.getActivityThreadObj(obj); if (obj != null) { result = (Application) ActivityThread.getInitialApplication(obj); } } } catch (Exception e) { e.printStackTrace(); } return result; } }動態載入MainActivity類。改動其setA()方法的邏輯功能的
EntryClass.java
package com.demo.inject2; import java.lang.reflect.Method; import android.content.Context; import android.util.Log; public final class EntryClass { public static Object[] invoke(int i) { try { Log.i("TTT", ">>>>>>>>>>>>>I am in, I am a bad boy 2!!!!<<<<<<<<<<<<<<"); Context context = ContexHunter.getContext(); Class<?> MainActivity_class = context.getClassLoader().loadClass("com.demo.host.MainActivity"); Method setA_method = MainActivity_class.getDeclaredMethod("setA", int.class); setA_method.invoke(null, 1); } catch (Exception e) { e.printStackTrace(); } return null; } }整個項目的下載地址:http://download.csdn.net/detail/jiangwei0910410003/8138735
注:這個項目沒有入口的Activity的,因此運行是沒有效果的。他的主要功能是一個功能模塊apk,如下會說道他的用途。
2)注入的so文件源代碼:importdex.cpp
他的功能是獲取到當前進程的JVM變量,而後獲取到JNIEnv變量,經過JNIEnv變量,載入上面獲得的DemoInject2.apk,而後運行EntryClass類中的invoke方法
#include <stdio.h> #include <stddef.h> #include <jni.h> #include <android_runtime/AndroidRuntime.h> #include "log.h" #include "importdex.h" using namespace android; static const char JSTRING[] = "Ljava/lang/String;"; static const char JCLASS_LOADER[] = "Ljava/lang/ClassLoader;"; static const char JCLASS[] = "Ljava/lang/Class;"; static JNIEnv* jni_env; static char sig_buffer[512]; //EntryClass entryClass; //ClassLoader.getSystemClassLoader() static jobject getSystemClassLoader(){ LOGI("getSystemClassLoader is Executing!!"); jclass class_loader_claxx = jni_env->FindClass("java/lang/ClassLoader"); snprintf(sig_buffer, 512, "()%s", JCLASS_LOADER); LOGI("sig_buffer is %s",sig_buffer); jmethodID getSystemClassLoader_method = jni_env->GetStaticMethodID(class_loader_claxx, "getSystemClassLoader", sig_buffer); LOGI("getSystemClassLoader is finished!!"); return jni_env->CallStaticObjectMethod(class_loader_claxx, getSystemClassLoader_method); } __attribute__ ((__constructor__)) void callback() { LOGI("Main is Executing!!"); JavaVM* jvm = AndroidRuntime::getJavaVM(); LOGI("jvm is %p",jvm); jvm->AttachCurrentThread(&jni_env, NULL); //TODO 使用JNIEnv jvm->DetachCurrentThread(); LOGI("jni_env is %p",jni_env); jstring apk_path = jni_env->NewStringUTF("/data/local/tmp/DemoInject2.apk"); jstring dex_out_path = jni_env->NewStringUTF("/data/data/com.demo.host/"); jclass dexloader_claxx = jni_env->FindClass("dalvik/system/DexClassLoader"); //LOGI("apk_path:%s",apk_path); //LOGI("dex_out_path:%s",dex_out_path); snprintf(sig_buffer, 512, "(%s%s%s%s)V", JSTRING, JSTRING, JSTRING, JCLASS_LOADER); LOGI("sig_buffer is %s",sig_buffer); jmethodID dexloader_init_method = jni_env->GetMethodID(dexloader_claxx, "<init>", sig_buffer); snprintf(sig_buffer, 512, "(%s)%s", JSTRING, JCLASS); LOGI("sig_buffer is %s",sig_buffer); jmethodID loadClass_method = jni_env->GetMethodID(dexloader_claxx, "loadClass", sig_buffer); jobject class_loader = getSystemClassLoader(); //check_value(class_loader); LOGI("GetClassLoader"); jobject dex_loader_obj = jni_env->NewObject(dexloader_claxx, dexloader_init_method, apk_path, dex_out_path, NULL, class_loader); LOGI("step---1"); LOGI("dex_loader_obj:%s",dex_loader_obj); jstring class_name = jni_env->NewStringUTF("com.demo.inject2.EntryClass"); jclass entry_class = static_cast<jclass>(jni_env->CallObjectMethod(dex_loader_obj, loadClass_method, class_name)); LOGI("step---2"); LOGI("jni_env:%p",jni_env); LOGI("step---2-1"); LOGI("entry_class:%s",entry_class); jmethodID invoke_method = jni_env->GetStaticMethodID(entry_class, "invoke", "(I)[Ljava/lang/Object;"); //check_value(invoke_method); LOGI("step---3"); jobjectArray objectarray = (jobjectArray) jni_env->CallStaticObjectMethod(entry_class, invoke_method, 0); LOGI("step---4"); jvm->DetachCurrentThread(); LOGI("Main is finished"); }這裏。咱們可以看到,一旦咱們拿到了JNIEnv變量以後,真的是什麼都可以幹,想調用Java世界中的不論什麼類中的方法,就連動態載入apk都是可以的。因此這一點真的很是重要,要切記。
這個就是底層中調用Java層的方法的最好實現方案了。超有用的。
這個項目的下載地址:http://download.csdn.net/detail/jiangwei0910410003/8138253
DemoHost.apk、DemoInject2.apk、libimportdex.so
首先需要將DemoHost.apk安裝到手機上
而後將DemoInject2.apk複製到手機的/data/local/tmp/文件夾中:adb push DemoInject2.apk /data/local/tmp/
因爲在importdex.cpp代碼中的路勁就是這個:
jstring apk_path = jni_env->NewStringUTF("/data/local/tmp/DemoInject2.apk");固然你可以改動的。
最後仍是需要將libimportdex.so文件複製到/data/data/文件夾中
adb push libimportdex.so /data/data/
改動權限:
chmod 777 libimportdex.so
文件拷貝好了。如下開始運行了,仍是需要開啓三個終端:監聽log信息。運行注入程序,啓動MainActivity(固然這個可以不需要。因爲你可以手動的打開)
一、監聽log信息
adb logcat -s TTT
二、運行注入程序
./poison /data/data/libimportdex.so 15427
三、開啓MainActivity
am start com.demo.host/.MainActivity
經過log信息咱們可以看到注入成功了。
注:在這個樣例中,編譯的過程當中會遇到一個問題:就是很是多頭文件,函數定義都找不到的。比方
#include <android_runtime/AndroidRuntime.h>這個頭文件,這個就需要咱們去Android的系統源代碼中查找了。關於Android系統源代碼下載和編譯的知識,可以轉戰:http://blog.csdn.net/jiangwei0910410003/article/details/37988637
當咱們加入這個頭文件的時候,在編譯仍是出錯,因爲找不到指定的函數定義,這時候咱們有兩種選擇:
第一種:將這個頭文件的實現源文件也拷貝過來,進行編譯(源代碼中有)
另一種:使用so文件進行編譯,關於這個so文件可以到手機設備的/system/lib/文件夾下找到:libandroid_runtime.so
這裏我就是採用這種方式進行編譯的,咱們將libandroid_runtime.so文件拷貝出來而後將其放到咱們以前的NDK配置文件夾中的:詳細文件夾例如如下:
最後咱們在Android.mk文件進行引用:
LOCAL_LDLIBS := -llog -lbinder -lutils -landroid_runtime
固然,這裏咱們會看到-lXXX是通用的格式,相同的,咱們假設要用到JVM中的函數的話,會用到libandroid_runtime.so文件,頭文件:android_runtime.h,也是可以在源代碼中找到的(後面會說起到)
(注:這裏就介紹了咱們怎樣在使用Android系統中底層的一些函數。同一時候編譯的時候引用到了動態連接庫文件)
在看一個樣例,第一個樣例是怎樣將目標模塊注入到目標進程中,而後運行它的函數,只是在這個樣例中咱們的目標進程是一個簡單的C程序編譯的可運行文件運行的,第二個樣例和第一個樣例的差異是將目標進程換成Android中的一個應用。在這個過程當中需要解決很是多問題的。並且這個樣例是很是重要的。因爲它介紹了怎樣在底層調用Java方法:可以將Java模塊打成jar/dex/apk,而後在底層使用類載入器進行動態載入,而後運行其指定的方法。
那麼到這裏算是結束了嗎?答案是否認的,因爲咱們的目的是攔截。因此最後一個樣例咱們就需要實現咱們的目的了
相應用行爲的攔截
需要的項目:注入的so文件proxybinder.cpp。上層模塊DemoInject3.apk
首先來看一下實現原理吧:
如下咱們以AMS爲例,作一個說明:
AMS繼承Binder(IBinder的子類。封裝了IPC通信公共部分的邏輯),Binder裏保存着一個類型爲int的mObject的字段。這個字段正是其C++對象JavaBBinder對象的地址,這個JavaBBinder纔是AMS終於跟內核通信的對象。
代碼例如如下:
public class Binder implements IBinder { //... /* mObject is used by native code, do not remove or rename */ private int mObject; //這個對象保存的就是JavaBBinder的指針 private IInterface mOwner; private String mDescriptor; //... }
class JavaBBinder : public BBinder { //... jobject object() const { return mObject; } //... private: JavaVM* const mVM; jobject const mObject; //這個保存的是AMS的引用 }; }
在實現這個代理,咱們需要獲取AMS和及相應用的JavaBBinder兩個對象。
獲取AMS引用
另外,也有一種比較簡單的方式,那就是經過defaultServiceManager的getService方法獲取到。
class DummyJavaBBinder : public BBinder{ public: virtual status_t onTransact(uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags = 0) { return NO_ERROR; } jobject object() const { return mObject; } JavaVM* javaVM() const { return mVM; } void changeObj(jobject newobj){ const jobject* p_old_obj = &mObject; jobject* p_old_obj_noconst = const_cast<jobject *>(p_old_obj); *p_old_obj_noconst = newobj; } private: JavaVM* const mVM; jobject const mObject; };
proxybinder.so的項目下載地址:http://download.csdn.net/detail/jiangwei0910410003/8138929
1)DemoInject3.apk的核心代碼
package com.demo.inject3; import android.os.Binder; import android.os.IBinder; import android.os.Parcel; import android.os.RemoteException; import android.util.Log; public final class EntryClass { private static final class ProxyActivityManagerServcie extends Binder { private IBinder mBinder; public ProxyActivityManagerServcie(IBinder binder) { mBinder = binder; } @Override protected boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException { //打印值。因爲data是請求者帶來的數據。reply是接受者返回的數據。這裏看data數據 Log.i("TTT", "code="+code); String callingPackage = data.readString(); Log.i("TTT", "callingPackage:"+callingPackage); String resolvedType = data.readString(); Log.i("TTT", "resolvedType:"+resolvedType); String resultWho = data.readString(); Log.i("TTT", "resultWho:"+resultWho); int requestCode = data.readInt(); Log.i("TTT", "requestCode:"+requestCode); int startFlags = data.readInt(); Log.i("TTT", "startFlags:"+startFlags); String profileFile = data.readString(); Log.i("TTT", "profileFile:"+profileFile); int v1 = data.readInt(); Log.i("TTT","v1:"+v1); int v2 = data.readInt(); Log.i("TTT","v2:"+v2); int userId = data.readInt(); Log.i("TTT","userId:"+userId); return mBinder.transact(code, data, reply, flags); } } public static Object[] invoke(int i) { IBinder activity_proxy = null; try { activity_proxy = new ProxyActivityManagerServcie(ServiceManager.getService("activity")); Log.i("TTT", ">>>>>>>>>>>>>I am in, I am a bad boy 3!!!!<<<<<<<<<<<<<<"); } catch (Exception e) { e.printStackTrace(); } //將activity_proxy傳遞給底層,進行Binder對象的改動 return new Object[] { "activity", activity_proxy }; } }ProxyActivityManagerService繼承了Binder對象,在onTransact中參數data是請求者攜帶的數據,因此咱們可以打印這個數據的值來看一下,裏面都是什麼樣的值?固然咱們確定不知道這些數據中包括什麼信息。因此要到源代碼中找到一個有這種方法的類,可以定位到ActivityManagerNative.java這個類。在 源代碼文件夾/java/android/app/ActivityManagerNative.java。
如下就是他的onTransact方法的一部分:
@Override public boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException { switch (code) { case START_ACTIVITY_TRANSACTION: { data.enforceInterface(IActivityManager.descriptor); IBinder b = data.readStrongBinder(); IApplicationThread app = ApplicationThreadNative.asInterface(b); String callingPackage = data.readString(); Intent intent = Intent.CREATOR.createFromParcel(data); String resolvedType = data.readString(); IBinder resultTo = data.readStrongBinder(); String resultWho = data.readString(); int requestCode = data.readInt(); int startFlags = data.readInt(); String profileFile = data.readString(); ParcelFileDescriptor profileFd = data.readInt() != 0 ? data.readFileDescriptor() : null; Bundle options = data.readInt() != 0 ? Bundle.CREATOR.createFromParcel(data) : null; int result = startActivity(app, callingPackage, intent, resolvedType, resultTo, resultWho, requestCode, startFlags, profileFile, profileFd, options); reply.writeNoException(); reply.writeInt(result); return true; } .........這個直截取了一小部分。因爲這種方法的內容太多了。主要是那個code的值有很是多值,可以看一下code的取值:
// Please keep these transaction codes the same -- they are also // sent by C++ code. int START_RUNNING_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION; int HANDLE_APPLICATION_CRASH_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+1; int START_ACTIVITY_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+2; int UNHANDLED_BACK_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+3; int OPEN_CONTENT_URI_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+4; // Remaining non-native transaction codes. int FINISH_ACTIVITY_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+10; int REGISTER_RECEIVER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+11; int UNREGISTER_RECEIVER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+12; int BROADCAST_INTENT_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+13; int UNBROADCAST_INTENT_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+14; int FINISH_RECEIVER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+15; int ATTACH_APPLICATION_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+16; int ACTIVITY_IDLE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+17; int ACTIVITY_PAUSED_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+18; int ACTIVITY_STOPPED_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+19; int GET_CALLING_PACKAGE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+20; int GET_CALLING_ACTIVITY_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+21; int GET_TASKS_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+22; int MOVE_TASK_TO_FRONT_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+23; int MOVE_TASK_TO_BACK_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+24; int MOVE_TASK_BACKWARDS_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+25; int GET_TASK_FOR_ACTIVITY_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+26; int REPORT_THUMBNAIL_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+27; int GET_CONTENT_PROVIDER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+28; int PUBLISH_CONTENT_PROVIDERS_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+29; int REF_CONTENT_PROVIDER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+30; int FINISH_SUB_ACTIVITY_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+31; int GET_RUNNING_SERVICE_CONTROL_PANEL_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+32; int START_SERVICE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+33; int STOP_SERVICE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+34; int BIND_SERVICE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+35; int UNBIND_SERVICE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+36; int PUBLISH_SERVICE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+37; int ACTIVITY_RESUMED_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+38; int GOING_TO_SLEEP_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+39; int WAKING_UP_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+40; int SET_DEBUG_APP_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+41; int SET_ALWAYS_FINISH_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+42; int START_INSTRUMENTATION_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+43; int FINISH_INSTRUMENTATION_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+44; int GET_CONFIGURATION_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+45; int UPDATE_CONFIGURATION_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+46; int STOP_SERVICE_TOKEN_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+47; int GET_ACTIVITY_CLASS_FOR_TOKEN_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+48; int GET_PACKAGE_FOR_TOKEN_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+49; int SET_PROCESS_LIMIT_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+50; int GET_PROCESS_LIMIT_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+51; int CHECK_PERMISSION_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+52; int CHECK_URI_PERMISSION_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+53; int GRANT_URI_PERMISSION_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+54; int REVOKE_URI_PERMISSION_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+55; int SET_ACTIVITY_CONTROLLER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+56; int SHOW_WAITING_FOR_DEBUGGER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+57; int SIGNAL_PERSISTENT_PROCESSES_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+58; int GET_RECENT_TASKS_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+59; int SERVICE_DONE_EXECUTING_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+60; int ACTIVITY_DESTROYED_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+61; int GET_INTENT_SENDER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+62; int CANCEL_INTENT_SENDER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+63; int GET_PACKAGE_FOR_INTENT_SENDER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+64; int ENTER_SAFE_MODE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+65; int START_NEXT_MATCHING_ACTIVITY_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+66; int NOTE_WAKEUP_ALARM_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+67; int REMOVE_CONTENT_PROVIDER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+68; int SET_REQUESTED_ORIENTATION_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+69; int GET_REQUESTED_ORIENTATION_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+70; int UNBIND_FINISHED_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+71; int SET_PROCESS_FOREGROUND_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+72; int SET_SERVICE_FOREGROUND_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+73; int MOVE_ACTIVITY_TASK_TO_BACK_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+74; int GET_MEMORY_INFO_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+75; int GET_PROCESSES_IN_ERROR_STATE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+76; int CLEAR_APP_DATA_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+77; int FORCE_STOP_PACKAGE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+78; int KILL_PIDS_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+79; int GET_SERVICES_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+80; int GET_TASK_THUMBNAILS_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+81; int GET_RUNNING_APP_PROCESSES_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+82; int GET_DEVICE_CONFIGURATION_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+83; int PEEK_SERVICE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+84; int PROFILE_CONTROL_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+85; int SHUTDOWN_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+86; int STOP_APP_SWITCHES_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+87; int RESUME_APP_SWITCHES_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+88; int START_BACKUP_AGENT_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+89; int BACKUP_AGENT_CREATED_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+90; int UNBIND_BACKUP_AGENT_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+91; int GET_UID_FOR_INTENT_SENDER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+92; int HANDLE_INCOMING_USER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+93; int GET_TASK_TOP_THUMBNAIL_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+94; int KILL_APPLICATION_WITH_APPID_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+95; int CLOSE_SYSTEM_DIALOGS_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+96; int GET_PROCESS_MEMORY_INFO_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+97; int KILL_APPLICATION_PROCESS_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+98; ...........這些值咱們相同可以在 源代碼文件夾/java/android/app/IActivityManager.java源代碼中看到,很是多值,上面僅僅是一部分。因此上面的onTransact方法中的switch分支結構是有很是多代碼的,這裏,咱們就從那個分支中選擇一種可能將他的代碼複製到咱們的onTransact方法中進行分析:
@Override protected boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException { //打印值。因爲data是請求者帶來的數據。reply是接受者返回的數據,這裏看data數據 Log.i("TTT", "code="+code); String callingPackage = data.readString(); Log.i("TTT", "callingPackage:"+callingPackage); String resolvedType = data.readString(); Log.i("TTT", "resolvedType:"+resolvedType); String resultWho = data.readString(); Log.i("TTT", "resultWho:"+resultWho); int requestCode = data.readInt(); Log.i("TTT", "requestCode:"+requestCode); int startFlags = data.readInt(); Log.i("TTT", "startFlags:"+startFlags); String profileFile = data.readString(); Log.i("TTT", "profileFile:"+profileFile); int v1 = data.readInt(); Log.i("TTT","v1:"+v1); int v2 = data.readInt(); Log.i("TTT","v2:"+v2); int userId = data.readInt(); Log.i("TTT","userId:"+userId); return mBinder.transact(code, data, reply, flags); }經過打印的值,來分析這些字段的含義。
DemoInject3.apk的項目下載地址:http://download.csdn.net/detail/jiangwei0910410003/8138233
好了。假設上面的項目都編譯成功的話,會獲得兩個文件:
libproxybinder.so、DemoInject3.apk
將libproxybinder.so文件複製到/data/data/文件夾下
adb push libproxybinder.so /data/data/
改動權限
chmod 777 libproxybinder.so
將DemoInject3.apk複製到/data/local/tmp/文件夾下
adb push DemoInject3.apk /data/local/tmp/
那麼如下開始運行吧。
但是想想,咱們應該注入到哪一個進程中呢?這個在開頭的時候也說過了,是system_process。但是當咱們使用ps命令去查看進程信息的時候,會發現找不到這個進程名。事實上google一下以後。會發現,事實上咱們真正要注入的進程名是:system_server,而system_process僅僅是這個進程的應用名稱。至關於咱們普通應用的名字和咱們的應用的包名(通常包名是進程的名稱)。因此咱們再次查找一下system_server進程的時候就找到了:
進程的pid是599。
好的如下就開始運行了,咱們需要開啓兩個終端就能夠:一個監聽log信息,一個運行注入程序
一、運行注入程序
./poison /data/data/libproxybinder.so 599
爲了能看到哪些應用在使用權限,這裏我打開了百度地圖app.
二、監聽log信息
adb logcat -s TTT
經過打印的結果。咱們看到兩個重要的信息,一個resultWho字段,他的值就是應用使用的權限,百度地圖使用了位置信息權限。
還有就是requestCode字段。他的值就是應用的進程pid,不信的話,咱們在使用ps查看一下進程列表:
看到了,進程pid爲5611,看到進程名爲:com.baidu.BaiduMap.pad:remote
因此咱們可以獲得兩個重要的字段:
resultWho:應用使用的權限
requestCode:應用的進程id
有了這兩個值,咱們就可以獲得哪一個應用正在使用什麼權限(可以經過pid獲得應用的名稱,這個很是easy。百度一下就可以了)
那麼到此咱們就實現了在Java層堆應用程序的行爲攔截,在第三個樣例中主要就是在上次咱們本身定義一個Binder,而後將其傳給底層,而後底層在替換JavaBBinder對象就能夠。在這個樣例中咱們可以看到我以前講到的第三個知識點:怎樣替換系統服務(Binder).
這篇文章總算是結束了。篇幅有點長,主要是經過三個樣例。徐徐漸進的解說一下怎樣攔截應用的行爲:
第一個樣例:怎樣將咱們的功能模塊libmyso.so注入到簡單的應用程序中demo1
技術點:動態載入so文件。而後運行當中的指定函數
第二個樣例:怎樣將咱們的功能模塊libimportdex.so注入到咱們的Android應用中
技術點:獲取JNIEnv對象,獲取進程中的Context對象
第三個樣例:怎樣將咱們的功能模塊注入到系統進程system_process(system_server)中
技術點:上層本身定義一個Binder,在onTransact方法中分析數據。底層替換JavaBBinder對象
固然這篇文章講的知識點可能比較多。但是主要就是我開頭講到的三個知識點:
一、怎樣動態載入so,並且運行當中的函數
二、怎樣在C層運行Java方法(NDK一般是指Java中調用C層函數)
三、怎樣改動系統服務(Context.getSystemService(String...)事實上返回來的就是Binder對象)對象
注:上面講到的三個樣例中的項目代碼都有對應的下載地址,假設download下來以後。運行有問題的話。可以給我留言,我會盡量的幫助解決一下。
最後:事實上這篇寫完這篇文章以後,發現了我還有兩個地方沒有理解清楚:
一、同一個進程中的Application對象以及Context對象的含義
二、Android中的Binder機制
這個我會在興許的文章中再次進行具體的總結。
(PS:總算是寫完了,真心寫了一下午還沒寫完。一大早上來接着寫的。這個文章的篇幅有點長,可能需要很是有耐心的去看。固然假設看到有錯誤的地方,還請各位大哥能指出來,謝謝~~)