編輯:關於Android編程
之前的一篇概要文章中主要說了我這次研究的一些具體情況,這裡就不在多說了,但是這裡還需要指出的是,感謝一下三位大神願意分享的知識(在我看來,懂得分享和細致的人才算是大神,不一定是技術牛奧~~)
第一篇:http://blog.csdn.net/jinzhuojun/article/details/9900105
第二篇:http://bbs.pediy.com/showthread.php?t=186880
第三篇:http://bbs.pediy.com/showthread.php?t=157419
最重要的還是第一篇,所以這裡我就不多介紹了,當然如果要看這篇blog的話,最好是先仔細閱讀一下上面的三篇文章。
當然我對第一篇文章做了修改了詳細的描述了:http://blog.csdn.net/jiangwei0910410003/article/details/39293635
這篇文章最好是看懂了,而且是必須真的懂了,同時將demo自己執行一邊,流程走通了,不然下面也是會遇到問題的。
當然這種攔截方式的前提是:手機必須root,同時需要獲取su權限
下面開始進入正題
當然在之前的文章中的摘要中我們可以看到我這次主要攔截的是可以獲取系統信息的進程,所以我們要搞清楚攔截的對象,這裡就不做介紹了,我們攔截的進程是system_server(關於這個進程可以google一下,當然我們可以使用adb shell以及ps命令查看這個進程的相關信息)
關於inject源碼這裡就不做太多的解釋了,主要來看一下他的main函數:
int main(int argc, char** argv) { pid_t target_pid; target_pid = find_pid_of("system_server"); if (-1 == target_pid) { printf("Can't find the process\n"); return -1; } printf("target_id:%d\n",target_pid); inject_remote_process(target_pid, "/data/libsys.so", "hook_entry", "I'm parameter!", strlen("I'm parameter!")); return 0; }這裡的一個主要的方法就是inject_remote_process(...)
第一個參數:需要注入的進程id
第二個參數:需要注入的動態庫(其實這個庫中就是包含我們需要替換的函數地址)
第三個參數:動態庫入口的函數名稱
第四個參數:動態庫入口的函數所需要的參數
第五個參數:動態庫入口的函數所需要的參數的長度
當然這裡我們還有一個是通過進程名稱獲取到進程的id的函數find_pid_of(...)
既然現在我們需要注入system_server進程中,那麼我們就要需要將我們的代碼注入到libbiner.so文件中
在sys.c代碼中修改基礎地方:
第一個修改的地方就是so文件路徑:
#define LIBSF_PATH "/system/lib/libbinder.so"然後就是注入的函數:我們記得注入surfaceflinger進程的時候,攔截的是eglSwapBuffers函數,我們注入到system_server的話,就是攔截ioctl函數,因為我們知道想使用一些系統服務都是調用這個方法的,下面就對這個函數進行替換:
int (*old_ioctl) (int __fd, unsigned long int __request, void * arg) = 0; // 欲接替ioctl的新函數地址,其中內部調用了老的ioctl int new_ioctl (int __fd, unsigned long int __request, void * arg) { if ( __request == BINDER_WRITE_READ ) { call_count++; LOGD("call_count:%d",call_count); } int res = (*old_ioctl)(__fd, __request, arg); return res; }
在這個函數中,我們會判斷一下請求狀態_request,如果是BINDER_WRITE_READ,說明上層有請求服務了,這裡就是做一個簡單的判斷,通過一個int值,然後用log將其值打印出來。
int hook_eglSwapBuffers() { old_ioctl = ioctl; void * base_addr = get_module_base(getpid(), LIBSF_PATH); LOGD("libsurfaceflinger.so address = %p\n", base_addr); int fd; fd = open(LIBSF_PATH, O_RDONLY); if (-1 == fd) { LOGD("error\n"); return -1; } Elf32_Ehdr ehdr; read(fd, &ehdr, sizeof(Elf32_Ehdr)); unsigned long shdr_addr = ehdr.e_shoff; int shnum = ehdr.e_shnum; int shent_size = ehdr.e_shentsize; unsigned long stridx = ehdr.e_shstrndx; Elf32_Shdr shdr; lseek(fd, shdr_addr + stridx * shent_size, SEEK_SET); read(fd, &shdr, shent_size); char * string_table = (char *)malloc(shdr.sh_size); lseek(fd, shdr.sh_offset, SEEK_SET); read(fd, string_table, shdr.sh_size); lseek(fd, shdr_addr, SEEK_SET); int i; uint32_t out_addr = 0; uint32_t out_size = 0; uint32_t got_item = 0; int32_t got_found = 0; for (i = 0; i < shnum; i++) { read(fd, &shdr, shent_size); if (shdr.sh_type == SHT_PROGBITS) { int name_idx = shdr.sh_name; if (strcmp(&(string_table[name_idx]), ".got.plt") == 0 || strcmp(&(string_table[name_idx]), ".got") == 0) { out_addr = base_addr + shdr.sh_addr; out_size = shdr.sh_size; LOGD("out_addr = %lx, out_size = %lx\n", out_addr, out_size); for (i = 0; i < out_size; i += 4) { got_item = *(uint32_t *)(out_addr + i); if (got_item == old_ioctl) { LOGD("Found eglSwapBuffers in got\n"); got_found = 1; uint32_t page_size = getpagesize(); uint32_t entry_page_start = (out_addr + i) & (~(page_size - 1)); mprotect((uint32_t *)entry_page_start, page_size, PROT_READ | PROT_WRITE); *(uint32_t *)(out_addr + i) = new_ioctl; break; } else if (got_item == new_ioctl) { LOGD("Already hooked\n"); break; } } if (got_found) break; } } } free(string_table); close(fd); }
好的,修改差不多了,下面我們來編譯吧~~
編譯會出錯的,因為會提示找不到ioctl的定義以及一些常量值,所以我們得找到這個函數的定義,百度一下之後會發現這個函數的定義是在binder.h中,當然這個頭文件我們是可以在Android源碼中找到的
(關於源碼下載和編譯的問題:http://blog.csdn.net/jiangwei0910410003/article/details/37988637)。
然後將這個binder.h拷貝到我們編譯的目錄中,然後再代碼中引用一下即可。
再次編譯,擦,還是提示錯誤,說這個函數沒有定義。
原因很簡單,我們只是引用了頭文件,並沒有將函數的具體實現引用進來,所以還需要去找到這個函數的具體定義了。
這個過程就是有點麻煩了,糾結了很長時間呀~~,幸好最後搞定了
具體步驟:
從設備的system/lib/ 目錄中找到libbinder.so文件,將其拷貝出來,這是一個動態庫文件
然後將其放到我們之前的NDK配置目錄中的:具體目錄如下:
最後我們在Android.mk文件進行引用:
LOCAL_LDLIBS := -llog -lbinder -lutils -landroid_runtime
當然,這裡我們會看到-lXXX是通用的格式,同樣的,我們如果要用到JVM中的函數的話,會用到libandroid_runtime.so文件,頭文件:android_runtime.h,也是可以在源碼中找到的(後面會提及到)
(注:這裡就介紹了我們如何在使用Android系統中底層的一些函數,同時編譯的時候引用到了動態鏈接庫文件)
擴展:
這裡在擴展一下:還有另外的一種方式引用so文件:
操作步驟:
首先在我們編譯目錄中新建一個文件夾:prebuilt
在這個文件夾中存放兩個文件:
1.Android.mk:
LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE := binder LOCAL_SRC_FILES := libbinder.so include $(PREBUILT_SHARED_LIBRARY)
2. libbinder.so(這個動態鏈接庫文件就是我們需要用到的)
然後在回到我們編譯的目錄中,修改一下我們的Android.mk文件:
LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_LDLIBS := -llog LOCAL_MODULE := sys LOCAL_SRC_FILES := sys.c include $(BUILD_SHARED_LIBRARY) include $(LOCAL_PATH)/prebuilt/Android.mk最重要的一行就是在最後。所以這也是一種方法。當然我們會看到這種方法有一個弊端,就是一次只能引用到一個so文件。所以使用范圍比較窄~~
在回到主題上來,我們通過引用libbinder.so文件之後,編譯可以通過了,然後將編譯之後的libsys.so文件拷貝到/data/目錄中,這個和之前的libsuf.so步驟差不多。
然後進入到設備的data目錄中,執行inject
=>adb shell
=>cd data
=>./inject
同時檢測一下log信息:
adb logcat -s PERMISSIONINTERCEPTER
隨便動動手機,call_count就刷刷的變~~
至此,我們看到了,hook進程和攔截ioctl函數成功~~
Demo下載地址:
http://download.csdn.net/detail/jiangwei0910410003/7930603
<喎?/kf/ware/vc/" target="_blank" class="keylink">vcD48cD48YnIgLz48L3A+PHA+PHN0cm9uZz7PwsPm1NnAtMnuyOu/tNK7z8KjrMjnus7Aub3YxMTQqdOm08PU2sq508PPtc2zt/7O8cTYo788L3N0cm9uZz48L3A+PHA+ytfPyKOsztLDx7u5ysfQ6NKqttTJz8PmztLDx9fUtqjS5bXEPHN0cm9uZz5uZXdfaW9jdGw8L3N0cm9uZz66r8r91tC1xMLfvK29+NDQ0N64xNK7z8KhozwvcD48cD7G5Mq11eLA78Pmvs3T0NK7uPa63LTztcTE0bbIwcso1eK49tKy1f3Kx7XXsuNDvfjQ0MC5vdi1xNK7uPax17bLKaOs0OjSqrfWzvbK/b7duPHKvaOsyLu687LFxNzV/ci3tcS9+NDQwLm92KGjPC9wPjxwPs/Cw+bAtL+00rvPwr7fzOW1xLT6wuuwyaO6PC9wPjxwPjwvcD48cHJlIGNsYXNzPQ=="brush:java;">// 欲接替ioctl的新函數地址,其中內部調用了老的ioctl
int new_ioctl (int __fd, unsigned long int __request, void * arg)
{
if ( __request == BINDER_WRITE_READ )
{
int dir = _IOC_DIR(__request); //根據命令獲取傳輸方向
int type = _IOC_TYPE(__request); //根據命令獲取類型
int nr = _IOC_NR(__request); //根據命令獲取類型命令
int size = _IOC_SIZE(__request); //根據命令獲取傳輸數據大小
struct binder_write_read* tmp = (struct binder_write_read*) arg;
signed long write_size = tmp->write_size;
signed long read_size = tmp->read_size;
if(write_size > 0)//該命令將write_buffer中的數據寫入到binder
{
//LOGD("binder_write_read----->write size: %d,write_consumed :%d", tmp->write_size, tmp->write_consumed);
int already_got_size = 0;
unsigned long *pcmd = 0;
//LOGD("=================write_buffer process start!");
while(already_got_size < write_size)//循環處理buffer中的每一個命令
{
pcmd = (unsigned long *)(tmp->write_buffer + already_got_size); //指針後移
int code = pcmd[0];
//LOGD("pcmd: %x, already_got_size: %d", pcmd, already_got_size);
int dir = _IOC_DIR(code); //根據命令獲取傳輸方向
int type = _IOC_TYPE(code); //根據命令獲取類型
int nr = _IOC_NR(code); //根據命令獲取類型命令
int size = _IOC_SIZE(code); //根據命令獲取傳輸數據大小
//LOGD("cmdcode:%d, dir:%d, type:%c, nr:%d, size:%d\n", code, dir, type, nr, size);
struct binder_transaction_data* pdata = (struct binder_transaction_data*)(&pcmd[1]);
switch (code)
{
case BC_TRANSACTION:
if(pdata->sender_pid>5000){
//LOGD("code:%d",pdata->code);
//LOGD("name:%x",pdata->data.ptr.buffer);
//LOGD("pid: %d",pdata->sender_pid);
//char *pname = (char*)malloc(50*sizeof(char));
//cfgmng_get_taskname(pdata->sender_pid,pname);
//LOGD("pname: %s",pname);
//free(pname);
//hexdump(pdata->data.ptr.buffer, pdata->data_size);
}
parse_binder(pdata, 1);
break;
case BC_REPLY:
//LOGD("pid: %d, BC_REPLY, dir:%d, type:%c, nr:%d, size:%d\n", pdata->sender_pid, dir, type, nr, size);
parse_binder(pdata, 1);
break;
default:
break;
}
already_got_size += (size+4);
}
//LOGD("=================write_buffer process end!");
}
if(read_size > 0)//從binder中讀取數據寫入到read_buffer
{
//LOGD("binder_write_read----->read size: %d, read_consumed: %d", tmp->read_size, tmp->read_consumed);
int already_got_size = 0;
unsigned long *pret = 0;
//LOGD("=================read_buffer process start!");
while(already_got_size < read_size)//循環處理buffer中的每一個命令
{
pret = (unsigned long *)(tmp->read_buffer + already_got_size); //指針後移
int code = pret[0];
//LOGD("pret: %x, already_got_size: %d", pret, already_got_size);
int dir = _IOC_DIR(code); //根據命令獲取傳輸方向
int type = _IOC_TYPE(code); //根據命令獲取類型
int nr = _IOC_NR(code); //根據命令獲取類型命令
int size = _IOC_SIZE(code); //根據命令獲取傳輸數據大小
//LOGD("retcode:%d, dir:%d, type:%c, nr:%d, size:%d\n", code, dir, type, nr, size);
struct binder_transaction_data* pdata = (struct binder_transaction_data*)(&pret[1]);
switch (code)
{
case BR_TRANSACTION:
if(pdata->sender_pid>5000){
//LOGD("code:%d",pdata->code);
//LOGD("name:%s",pdata->data.ptr.buffer->flag);
//LOGD("pid: %d",pdata->sender_pid);
//char *pname = (char*)malloc(50*sizeof(char));
//getNameByPid(pdata->sender_pid,pname);
//LOGD("pname: %s",pname);
//free(pname);
char * pname = hexdump(pdata->data.ptr.buffer, pdata->data_size);
if(isStub(pname,STUB) == 1 && pid!=pdata->sender_pid)
{
pid = pdata->sender_pid;
//指定一下log的輸出格式:服務的名稱&&應用的進程id
LOGD("%s&&%d",pname,pdata->sender_pid);
//在一個線程中開啟socket
/*pthread_t tid;
int status = pthread_create(&tid,NULL,&new_socket,(void*)&data);
if(status != 0){
LOGD("can't create thread");
}else{
LOGD("create thread success");
}*/
//開一個socket
//new_socket(pid,pname);
//采用動態調用so文件中的函數f()
/*void * dp = dlopen("libmiddle.so",RTLD_NOW);
LOGD("dp is %p",dp);
int (*f)() = dlsym(dp,"f");
LOGD("func pointer==%p",f);
if(f == NULL)
{
LOGD("not find func");
}
else
{
(*f)();
}*/
}
free(pname);
}
parse_binder(pdata, 2);
break;
case BR_REPLY:
//LOGD("pid: %d, BR_REPLY, dir:%d, type:%c, nr:%d, size:%d\n", pdata->sender_pid, dir, type, nr, size);
parse_binder(pdata, 2);
break;
default:
break;
}
already_got_size += (size+4);//數據內容加上命令碼
}
//LOGD("=================read_buffer process end!");
}
}
if (old_ioctl == -1)
{
//LOGD("error\n");
return;
}
int res = (*old_ioctl)(__fd, __request, arg);
return res;
}
這個函數修改的邏輯就比較多了~~
首先來了解一下Binder在傳輸數據中比較關鍵的一個數據結構:binder_transaction_data
了解了這個數據結構之後,我們就可以從這個數據結構中提取出我們想要的服務的名稱(這一步真的很重要,也是網上很多人咨詢的一個問題,關於這個問題,我是閱讀了一本比較好的資料《Android框架揭秘》是棒子寫的一本書,裡面有一些錯誤,但是不影響大體的知識方向)、同時也是可以獲取到進程id,Android中一個應用一般就是一個進程,所以我們可以粗略的通過進程id來來獲取應用的一些詳細信息(可以獲取最近正在運行的應用信息列表中進行過濾)
獲取服務的名稱的主要操作:
char * pname = hexdump(pdata->data.ptr.buffer, pdata->data_size);
然後就是data.ptr.buffer的數據結構:
那麼現在問題就是如何能將這個服務的名稱正確的取出來呢?那麼這個就要看hexdump函數了
從字面意義上看應該是將hex文本轉化一下:
char* hexdump(void *_data, unsigned len) { unsigned char *data = _data; char *dataAry = (char*)malloc(len*(sizeof(char))); char *dataTmp = dataAry; unsigned count; for (count = 0; count < len; count++) { if ((count & 15) == 0) LOGD(stderr,"%04x:", count); //only show charset and '.' if(((*data >= 65) && (*data <= 90)) || ((*data >= 97) && (*data <= 122)) || (*data == 46)) { *dataAry = *data; dataAry++; } data++; if ((count & 15) == 15) LOGD(stderr,"\n"); } *dataAry = '\0'; return dataTmp; if ((count & 15) != 0) LOGD(stderr,"\n"); }
看到中間的一個核心的if判斷,那個就是用來過濾服務的包名的:大小寫字母+點號,這樣就能從buffer中提取出服務的包名了,然後返回即可(說實話,這個問題也是糾結了我好長時間,解決了還是很開心的)
在函數new_ioctl中最主要的核心代碼:
switch (code) { case BR_TRANSACTION: if(pdata->sender_pid>5000){ //LOGD("pid: %d, BR_TRANSACTION, dir:%d, type:%c, nr:%d, size:%d\n", pdata->sender_pid, dir, type, nr, size); //LOGD("code:%d",pdata->code); //LOGD("name:%s",pdata->data.ptr.buffer->flag); //LOGD("pid: %d",pdata->sender_pid); //char *pname = (char*)malloc(50*sizeof(char)); //getNameByPid(pdata->sender_pid,pname); //LOGD("pname: %s",pname); //free(pname); char * pname = hexdump(pdata->data.ptr.buffer, pdata->data_size); if(isStub(pname,STUB) == 1 && pid!=pdata->sender_pid) { pid = pdata->sender_pid; //指定一下log的輸出格式:服務的名稱&&應用的進程id LOGD("%s&&%d",pname,pdata->sender_pid); //在一個線程中開啟socket /*pthread_t tid; int status = pthread_create(&tid,NULL,&new_socket,(void*)&data); if(status != 0){ LOGD("can't create thread"); }else{ LOGD("create thread success"); }*/ //開一個socket //new_socket(pid,pname); //采用動態調用so文件中的函數f() /*void * dp = dlopen("libmiddle.so",RTLD_NOW); LOGD("dp is %p",dp); int (*f)() = dlsym(dp,"f"); LOGD("func pointer==%p",f); if(f == NULL) { LOGD("not find func"); } else { (*f)(); }*/ } free(pname); } parse_binder(pdata, 2); break; case BR_REPLY: //LOGD("pid: %d, BR_REPLY, dir:%d, type:%c, nr:%d, size:%d\n", pdata->sender_pid, dir, type, nr, size); parse_binder(pdata, 2); break; default: break; }
這個函數中就是判斷請求的方式
當code等於BR_TRANSACTION
表示開始讀取數據,所以我們這時候就可以在這裡進行數據的攔截分析,這裡在獲取進程id的時候,我做了一次判斷就是判斷進程id大於5000的,這個5000只是我隨便取的一個值,因為如果這裡不做判斷的話,在後面打印log信息的時候很不方便,因為系統有很多應用都可能在獲取服務,log信息有點多吧,這裡就相當於做個過濾。同時這裡還有一個過濾條件,就是服務名稱中有ILocationManager的,因為開始的時候只是想驗證一下這個不做能否成功,所以先用經緯度信息來做實驗。
上面的攔截操作算是完成了,那麼下面我們還得來做一件事(這件事也是網上好多同學糾結的一個問題)
就是如何將我們這裡攔截到的信息包括是哪個應用獲取服務的進程id以及獲取服務的名稱,如何將其這些信息傳遞到上層然後進行顯示(比如360彈出的對話框,或者是在通知欄中:XXX應用正在獲取你的XXX信息,拒絕還是允許)
我也花了一個禮拜的時間去解決的,找到三種方式:
第一種方式:通過上層(就是我們的app,建立一個SockeServer),然後在底層攔截到的詳細信息通過socket傳遞到server中。
底層的代碼如下:
void new_socket(int pid,char *pname){ int sockfd,sendbytes; char buf[MAXDATASIZE]; struct hostent *host = gethostbyname(SERVIP); struct sockaddr_in serv_addr; if((sockfd=socket(AF_INET,SOCK_STREAM,0))==-1){ LOGD("socket fail"); exit(1); } serv_addr.sin_family=AF_INET; serv_addr.sin_port=htons(SERVPORT); serv_addr.sin_addr.s_addr = inet_addr(SERVIP); bzero(&(serv_addr.sin_zero),8); if(connect(sockfd,(struct sockaddr *)&serv_addr,sizeof(struct sockaddr))==-1){ LOGD("connect fail"); //exit(1); } if((sendbytes=send(sockfd,pname,strlen(pname),0))==-1){ LOGD("send fail"); //exit(1); } free(pname); close(sockfd); }這個是在Linux中建立一個Socket,具體的操操作這裡就不做解釋了,自行可以去搜索一下Linux中如何建立socket。
下面在來看一下上層的app中建立一個SocketServer:
new Thread(){ @Override public void run(){ java.net.ServerSocket sc = null; try { sc = new java.net.ServerSocket(3333); Log.i("TTT","socket create success"); while (true) { java.net.Socket client = sc.accept(); String content = streamToString(client.getInputStream()); Log.i("TTT", "accept ==> "+content); } } catch (Exception ef) { Log.i("TTT", "exception===>"+ef.getMessage()); }finally{ Log.i("TTT", "stop connection"); if (sc != null){ try { sc.close(); } catch (IOException e) { Log.i("TTT", "exception===>"+e.getMessage()); } sc = null; } } } }.start();
這段代碼一般是放在Service中的,因為要始終監聽,同時這裡開啟的端口是:3333.為了防止和其他應用的端口沖突,一邊最好起個比較有個性的端口,當然這裡沒有做更好的優化,比如當建立端口的時候發現這個端口已經被占用了怎麼辦?做的好的應該做一下端口的判斷,然後找到沒被占用的端口(可以用Socket探針技術)。
第二種方式:有點復雜了就是使用JNI技術了,我們需要在底層中調用上層的Java代碼。這個技術就是可以在C++代碼中使用類加載機制+反射技術調用Java中的指定方法。這裡有一個問題就是如何獲取JVM對象以及JNIEnv對象。
眾所周知,Android的應用進程,都是由Zygote孵化的子進程,每個進程都運行在獨立的JVM中(具體的知識可以查看老羅的blog)。
那麼JVM對象可以獲取到,那麼JNIEnv對象呢?我們知道,在JVM進程中,JavaVM是全局唯一的,而JNIEnv則是按線程分配。另外,Dalvik的線程跟Linux線程是一一對應的,因此我們可以把自身所在的線程Attatch到JavaVM,JavaVM就會為我們分配JNIEnv對象了。通過閱讀Dalvik源碼,從AndroidRuntime類中我們可以得到JavaVm的地址,再通過JavaVm所提供的AttachCurrentThead和DetachCurrentThread兩個函數,即可完成JNIEnv的獲取。
具體代碼如下:
importdex.cpp代碼
#include#include #include #include #include "log.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]; //ClassLoader.getSystemClassLoader() static jobject getSystemClassLoader(){ jclass class_loader_claxx = jni_env->FindClass("java/lang/ClassLoader"); snprintf(sig_buffer, 512, "()%s", JCLASS_LOADER); jmethodID getSystemClassLoader_method = jni_env->GetStaticMethodID(class_loader_claxx, "getSystemClassLoader", sig_buffer); return jni_env->CallStaticObjectMethod(class_loader_claxx, getSystemClassLoader_method); } void Main() { JavaVM* jvm = AndroidRuntime::getJavaVM(); jvm->AttachCurrentThread(&jni_env, NULL); //TODO 使用JNIEnv //jvm->DetachCurrentThread(); jstring apk_path = jni_env->NewStringUTF("/data/local/tmp/DemoInject2.apk"); jstring dex_out_path = jni_env->NewStringUTF("/data/data/"); jclass dexloader_claxx = jni_env->FindClass("dalvik/system/DexClassLoader"); snprintf(sig_buffer, 512, "(%s%s%s%s)V", JSTRING, JSTRING, JSTRING, JCLASS_LOADER); jmethodID dexloader_init_method = jni_env->GetMethodID(dexloader_claxx, " ", sig_buffer); snprintf(sig_buffer, 512, "(%s)%s", JSTRING, JCLASS); jmethodID loadClass_method = jni_env->GetMethodID(dexloader_claxx, "loadClass", sig_buffer); jobject class_loader = getSystemClassLoader(); check_value(class_loader); jobject dex_loader_obj = jni_env->NewObject( dexloader_claxx, dexloader_init_method, apk_path, dex_out_path, NULL, class_loader); jstring class_name = jni_env->NewStringUTF("com.demo.inject2.EntryClass"); jclass entry_class = static_cast (jni_env->CallObjectMethod(dex_loader_obj, loadClass_method, class_name)); jmethodID invoke_method = jni_env->GetStaticMethodID(entry_class, "invoke", "(I)[Ljava/lang/Object;"); check_value(invoke_method); jobjectArray objectarray = (jobjectArray) jni_env->CallStaticObjectMethod(entry_class, invoke_method, 0); jvm->DetachCurrentThread(); }
這裡會遇到一個問題就是AndroidRuntime類找不到的問題,可以google一下,他的定義在android_runtime.h頭文件中,這個同樣也是可以從Android源碼中找到的,同時這個AndroidRuntime類的實現可以從設備的/system/lib/目錄中找到動態庫文件libandroid_runtime.so。和之前使用libbinder.so差不多的步驟,這裡就不做說明了。
編譯項目下載地址:
http://download.csdn.net/detail/jiangwei0910410003/7932355
編譯之後得到importdex.so文件,這個在後面會用到。
下面來具體看一下代碼。在看代碼之前需要先了解一下Android中的類加載機制,請看下面的一篇文章:
http://blog.csdn.net/jiangwei0910410003/article/details/17679823
如果這篇文章看懂了,其實上面的代碼就沒有任何難度了,其實就是C++中實現這種方式,和Java中的反射機制很相似,
在代碼中還可以看到DexClassLoader這個類的一個參數是dex文件的輸出目錄。這裡直接放到了/data/data目錄中。
同時我們需要將開發一個DemoInject2.apk,這裡面要有一個類:com.demo.inject2.EntryClass,在這個類中需要定義一個靜態的invoke方法:
package com.demo.inject2; import android.content.Context; import android.util.Log; import android.widget.Toast; 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(); Toast.makeText(context, "Success", Toast.LENGTH_LONG).show(); /*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; } }然後我們就可以通過底層調用這個方法,將進程id和服務的名稱作為參數傳遞過去,但是在使用這種方法的時候遇到一個問題:
那就是我們看到sys.c是C程序,但是上面的那個程序是C++的,所以這裡面會牽扯到一個問題就是怎麼在C程序中調用C++的代碼,這個可以看一下下面的一篇文章是怎麼操作:
http://blog.csdn.net/jiangwei0910410003/article/details/39312947
下面看看我們這裡是如何做到的
這裡會用到一個libimportdex.so文件(上面編譯之後的文件),還需要在弄一個編譯項目middle.middle文件夾:
middle.c:
#include "importdex.h" extern "C"{ int f() { callback(); return 0; } }
Android.mk:
LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_LDLIBS := -llog -lbinder -lutils -limportdex LOCAL_MODULE := middle LOCAL_SRC_FILES := middle.cpp include $(BUILD_SHARED_LIBRARY)這裡我們還需要將之前的編譯得到的libimportdex.so文件拷貝到NDK的配置目錄中
http://download.csdn.net/detail/jiangwei0910410003/7932369
底層的執行so文件中指定的函數的核心代碼:
void * dp = dlopen("libmiddle.so",RTLD_NOW); LOGD("dp is %p",dp); int (*f)() = dlsym(dp,"f"); LOGD("func pointer==%p",f); if(f == NULL) { LOGD("not find func"); } else { (*f)(); }
最後如果想成功的話,還需要將importdex.so文件以及libmiddle.so文件拷貝到設備的/system/lib/目錄下面,因為你需要引用。
第三種方式:使用Log日志來傳遞數據(這個是最終的解決方法)
這個實現原理很簡單,就是在底層C中,我們拿到的了進程的id和服務的名稱然後使用特定的規則將其進行拼接,然後在上層的app進行log的攔截,然後獲取指定的值,這裡會遇到一個問題就是如何能在本應用中獲取其他進程中的log信息,如果我們在本應用中獲取log信息很簡單的,但是獲取其他進程的log信息遇到點問題,但是最後靠著自己摸索,發現在su權限的情況下,可以獲取到所有進程的log信息,所以我們要獲取su權限。
在上層代碼中取指定log信息代碼:
package com.isoft.log; import java.io.DataInputStream; import android.annotation.SuppressLint; import android.app.Service; import android.content.Intent; import android.os.Handler; import android.os.IBinder; import android.os.Message; import android.util.Log; import android.widget.Toast; public class LogObserverService extends Service implements Runnable{ private String TAG = "LOG"; private StringBuffer logContent = null; private final static String[] CMDS = new String[]{"/system/bin/su","-c","logcat -s PERMISSIONINTERCEPTER"}; @SuppressLint("HandlerLeak") private Handler handler = new Handler(){ @Override public void dispatchMessage(Message msg) { if(msg.what == 0){ String content = (String)msg.obj; Toast.makeText(getApplicationContext(), "conent:"+content, Toast.LENGTH_LONG).show(); } super.dispatchMessage(msg); } }; @Override public void onCreate() { super.onCreate(); Log.i(TAG,"onCreate"); logContent = new StringBuffer(); new Thread(this).start(); } @Override public void run() { Process pro = null; try { pro = Runtime.getRuntime().exec(CMDS); }catch (Exception e) { Log.i(TAG, "異常:"+e.getMessage()); e.printStackTrace(); } DataInputStream dis = new DataInputStream(pro.getInputStream()); String line = null; while (true) { try { while ((line = dis.readLine()) != null) { if(line != null){ String[] lineAry = line.split("&&"); if(lineAry.length == 2){ //檢測一下獲取有位置信息的log信息 if(lineAry[0].contains("ILocationManager")){ Message msg = new Message(); msg.what = 0; msg.obj = "進程id:"+lineAry[1] + "\n獲取位置信息"; handler.sendMessage(msg); } } } String temp = logContent.toString(); logContent.delete(0, logContent.length()); logContent.append(line); logContent.append("\n"); logContent.append(temp); Thread.yield(); } } catch (Exception e) { e.printStackTrace(); } } } @Override public IBinder onBind(Intent intent) { return null; } }
主要的執行命令:
private final static String[] CMDS = new String[]{"/system/bin/su","-c","logcat -s PERMISSIONINTERCEPTER"};
這樣就可以獲取到指定的Tag的log信息,然後對其進行解析,獲取進程id和服務的名稱。
這三種方式的實現的代碼的Demo下載地址(前兩種方式我在代碼中注釋了,如果想用的話,可以打開注釋)
總結一下,為什麼會有這三種方式的產生呢?首先在有這種需求,想到的解決方式就是第一種。但是我在實現第一種方式的時候,出現了一個問題,就是運行一段時間總是會死機,總是需要reboot命令重啟或者扣電池了~~,現在還沒找到解決方案。所以就延伸出第二種方式了,但是第二種方式可以是可以,沒先到也會死機和第一種方式的問題一樣。現在還沒有找到解決方案。所以就延伸出第三種方式了,因為第三種方式真的很簡單。但是第三種方式有一個很大的問題,就是不能和底層進行交互,因為當我們在上層app中會點擊拒絕的話,就是不給某個應用獲取此服務,所以我們需要得到用戶的選擇狀態值,這樣才能在底層做具體的操作。回想一下,第一種和第二種方式都是可以實現的。但是會出現死機的情況。所以這個在後續是一定要解決的。
最後在來感受一下效果吧:
(ps:關於這個注入和攔截的問題,網上有很多同學都在研究,可能有的同學已經成功了,但是有的同學沒有,所以我就將這篇文章分享了一下,因為在這個過程中,我懂得那種遇到問題無助的感觸,所以如果你看完了這個文章,如果遇到任何問題,請給我留言,我能幫你解決的盡量解決一下)
第一次編譯時,設定android SDK:F:\RAD Studio XE6\PlatformSDKs\adt-bundle-windows-x86-20131030\s
本篇使用到的Android Studio版本為1.0, Eclipse ADT版本22.3.0。 主要介紹兩種導入方式: 先用Eclipse導出為Gradle build
相關文章Android View體系(一)視圖坐標系Android View體系(二)實現View滑動的六種方法Android View體系(三)屬性動畫Android
一、內存洩露內存洩漏會因為減少可用內存的數量從而降低計算機的性能。最終,在最糟糕的情況下,過多的可用內存被分配掉導致全部或部分設備停止正常工作,或者應用程序崩潰。內存洩漏