Android設(shè)備上非root的抓包實(shí)現(xiàn)方法(Tcpdump方法)
通常我們?cè)贏(yíng)ndroid應(yīng)用中執(zhí)行某個(gè)命令時(shí)會(huì)使用“Runtime.getRuntime().exec("命令路徑")”這種方式,但是當(dāng)我們執(zhí)行抓包操作時(shí),使用這條命令無(wú)論如何都不行,通過(guò)下面代碼打印結(jié)果發(fā)現(xiàn),該命令一定要在root權(quán)限下才能執(zhí)行。
BufferedReader brW = new BufferedReader(new InputStreamReader(p.getErrorStream())); while((str = brW.readLine()) != null) Log.d("cwmp", "w:"+str);
但是我們的Android設(shè)備(包括機(jī)頂盒、手機(jī)等)通常并沒(méi)有root過(guò),apk的最高權(quán)限也只是system權(quán)限,這該怎么解決?首先我們要知道,方法總比問(wèn)題多,在A(yíng)ndroid設(shè)備的/system/bin路徑下,我們會(huì)看到很多二進(jìn)制文件,這些二進(jìn)制文件可以獲得root權(quán)限。因此,我們可以通過(guò)C語(yǔ)言來(lái)實(shí)現(xiàn)抓包功能,通過(guò)NDK把該C代碼交叉編譯成二進(jìn)制文件置于/system/bin路徑下,并賦予其root權(quán)限,此時(shí),這個(gè)二進(jìn)制文件就具備了抓包能力了?,F(xiàn)在問(wèn)題又來(lái)了,我們現(xiàn)在是想通過(guò)apk去調(diào)用這個(gè)抓包指定,抓包完成后又該怎么通知apk呢?其實(shí),Android可以通過(guò)socket使底層與framework層進(jìn)行通信,具體請(qǐng)參考Android中使用socket使底層和framework通信的實(shí)現(xiàn)方法。
接下來(lái)我們將貼出關(guān)鍵實(shí)現(xiàn)代碼。
1、編寫(xiě)socket服務(wù)端代碼fstiService.cpp,生成可執(zhí)行腳本fstiService
#define SOCKET_NAME "fstiService" #define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, "itv_assistance", __VA_ARGS__) #include <jni.h> #include <stdio.h> #include <stdlib.h> #include <errno.h> #include <string.h> #include <sys/types.h> #include <netinet/in.h> #include <sys/socket.h> #include <sys/wait.h> #include <sys/un.h> #include <cutils/sockets.h> #include <android/log.h> #include <unistd.h> #include <time.h> #include <sys/time.h> #include <pthread.h> pthread_t thread[2]; char s_time[10]; //抓包時(shí)間子串 char s_command[256]; //抓包指令子串 //抓包指令:system("/system/bin/tcpdump -v -w /sdcard/te.pcap"); //獲取進(jìn)程tcpdump的進(jìn)程號(hào) int getPid() { //for Linux C //FILE *fp = popen("ps -e | grep \'tcpdump\' | awk \'{print $1}\'", "r"); //for Android(ARM) //FILE *fp = popen("ps | grep \'tcpdump\'", "r"); FILE *fp = popen("ps | grep \'tcpdump\'", "r"); char buff[1024] = { 0 }; while (NULL != fgets(buff, sizeof(buff), fp)) ; //取消換行符(10) buff[strlen(buff) - 1] = '\0'; pclose(fp); char dst[5] = { 0 }; char *p = buff; char *q = dst; //每一行進(jìn)程信息的第二個(gè)字符串為進(jìn)程號(hào) while (*p != ' ') p++; while (*p == ' ') p++; while (*p != ' ') *(q++) = *(p++); *(q++) = '\0'; return atoi(dst); } //截取子串(抓包時(shí)間(秒):抓包命令) void substring(char *time, char *command, char *src) { char *p = src; char *q = time; char *s = command; while (*p != '/') *(q++) = *(p++); *(q++) = '\0'; //如果Tcpdump命令已添加環(huán)境變量,則添加下行代碼 //否則刪除下一行代碼,client傳遞的參數(shù)格式必須為: num/tcpdump所在路徑 p++; while (*p) *(s++) = *(p++); *(s++) = '\0'; } //抓包線(xiàn)程 void *thread1(void *arg) { system(s_command); } void *thread2(void *arg) { int i_time = atoi(s_time); int begin = time((time_t*) NULL); while (1) { if (time((time_t*) NULL) - begin < i_time) { //printf("當(dāng)前時(shí)間(s):%ld\n", time((time_t*)NULL)); continue; } else { int n = kill(getPid(), SIGKILL); LOGD("the kill process result is n=%d", n); break; } } return 0; } //創(chuàng)建子線(xiàn)程 void thread_create() { int temp; memset(&thread, 0, sizeof(thread)); if ((temp = pthread_create(&thread[0], NULL, thread1, NULL)) != 0) LOGD("create tcpdump thread failure"); else LOGD("create tcpdump thread success"); if ((temp = pthread_create(&thread[1], NULL, thread2, NULL)) != 0) LOGD("create count thread failure"); else LOGD("create count thread success"); } void thread_wait() { if (thread[0] != 0) { pthread_join(thread[0], NULL); LOGD("tcpdump thread has terminated"); } if (thread[1] != 0) { //pthread_join(thread[1], NULL); printf("counter thread has terminated"); } } /** * Native層Socket服務(wù)端 */ int main() { int connect_number = 6; int fdListen = -1, new_fd = -1; int ret; struct sockaddr_un peeraddr; socklen_t socklen = sizeof(peeraddr); int numbytes; char buff[256]; //這一步很關(guān)鍵,就是獲取init.rc中配置的名為 "fstiService" 的socket //獲取已綁定的socket,返回-1為錯(cuò)誤情況 fdListen = android_get_control_socket(SOCKET_NAME); if (fdListen < 0) { LOGD("failed to get socket '" SOCKET_NAME "' errno %d", errno); exit(-1); } /** * 方法說(shuō)明:開(kāi)始監(jiān)聽(tīng)(等待參數(shù)fdListen的socket連接,參數(shù)connect_number指定同時(shí)能處理的最大連接要求) * 如果連接數(shù)目達(dá)此上限則client端將收到ECONNREFUSED的錯(cuò)誤。listen函數(shù)并未開(kāi)始連接,只是設(shè)置 * socket為listen模式,真正接收client端連接的是accept()。通常listen()會(huì)在socket() * bind()之后調(diào)用,接著才調(diào)用accept(). * 返回值:成功返回0,失敗返回-1,錯(cuò)誤原因存在errno中 */ ret = listen(fdListen, connect_number); LOGD("listen result %d", ret); if (ret < 0) { /** * perror(s)將一個(gè)函數(shù)發(fā)生錯(cuò)誤的原因輸出到標(biāo)準(zhǔn)設(shè)備(stderr) * 參數(shù)s所指的字符串會(huì)先打印出,后面再加上錯(cuò)誤原因字符串 */ perror("listen"); exit(-1); } /** * 方法說(shuō)明:accept(int s, struct sockaddr * addr, socklen_t * addrlen)用來(lái)接受參數(shù)s的socket連接。 * socket必須先經(jīng)bind()、listen()函數(shù)處理過(guò),當(dāng)有socket客戶(hù)端連接進(jìn)來(lái)時(shí)會(huì)返回一個(gè)新的socket處理 * 代碼,往后的數(shù)據(jù)傳送與讀取就是經(jīng)由新的socket處理,而原來(lái)參數(shù)s的socket能繼續(xù)使用accept()來(lái)接受新的 * 連接請(qǐng)求。連線(xiàn)成功時(shí), 參數(shù)addr 所指的結(jié)構(gòu)會(huì)被系統(tǒng)填入遠(yuǎn)程主機(jī)的地址數(shù)據(jù),參數(shù)addrlen為sockaddr的 * 結(jié)構(gòu)長(zhǎng)度。 * 返回值:成功返回新的socket處理代碼,失敗返回-1,錯(cuò)誤原因存在于errno中。 */ new_fd = accept(fdListen, (struct sockaddr *) &peeraddr, &socklen); LOGD("accept_fd %d", new_fd); if (new_fd < 0) { LOGD("%d", errno); perror("accept error"); exit(-1); } //循環(huán)等待Socket客戶(hù)端發(fā)來(lái)消息 while (1) { /** * 方法說(shuō)明:recv(int s, void *buf, size_t len, unsigned int flags)用來(lái)接收 * 客戶(hù)端socket傳來(lái)的數(shù)據(jù),并把數(shù)據(jù)存到由參數(shù)buf指向的內(nèi)存空間,參數(shù)len為可接收數(shù)據(jù)的最大長(zhǎng)度。 * 參數(shù)flags一般設(shè)0 * 返回值:失敗返回-1 */ if ((numbytes = recv(new_fd, buff, sizeof(buff), 0)) == -1) { LOGD("%d", errno); perror("recv"); continue; } LOGD("the parameter received from socket client is %s", buff); if(strcmp(buff, "exit") != 0){ substring(s_time, s_command, buff); thread_create(); thread_wait(); } char result[10] = "successp"; /** * 方法說(shuō)明:send(int s, const void *msg, size_t len, unsigned int flags) * 參數(shù)s為已建立好連接的socket,參數(shù)msg指向欲發(fā)送的數(shù)據(jù)內(nèi)容,參數(shù)len為數(shù)據(jù)長(zhǎng)度,flags一般置0. * 返回值:失敗返回-1,錯(cuò)誤原因存在errno中 */ int sendR = send(new_fd, result, strlen(result), 0); //apk退出后,buff中仍然緩存之前的調(diào)用命令,此時(shí)會(huì)額外再執(zhí)行一次抓包,固下面代用重寫(xiě)buff中數(shù)據(jù) strcpy(buff, "exit"); if (sendR == -1) { perror("send"); close(new_fd); exit(0); } } close(new_fd); close(fdListen); return 0; }
2、配置init.rc文件,添加如下配置
service fstiService /system/bin/fstiService socket fstiService stream 777 system system class main
此處配置了一個(gè)名為“fstiService”的服務(wù),Android設(shè)備開(kāi)機(jī)會(huì)自動(dòng)啟動(dòng)并運(yùn)行/system/bin/fstiService這個(gè)腳本文件。服務(wù)端代碼完成后,我們需要將其編譯成可執(zhí)行腳本fstiService,Android.mk內(nèi)容如下:
LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) #指定該模塊在所有版本下都編譯 LOCAL_MODULE_TAGS :=optional LOCAL_MODULE := fstiService LOCAL_SRC_FILES := fstiService.cpp LOCAL_LDLIBS := -llog #編譯成動(dòng)態(tài)庫(kù) #include $(BUILD_SHARED_LIBRARY) #編譯成可執(zhí)行文件 include $(BUILD_EXECUTABLE)
3、Android客戶(hù)端代碼
public class SocketClient { private final String SOCKET_NAME = "fstiService"; private LocalSocket client = null; private LocalSocketAddress address = null; private boolean isConnected = false; private int connectTime = 1; public SocketClient(){ client = new LocalSocket(); //A socket in the Android reserved namespace in /dev/socket. //Only the init process may create a socket here address = new LocalSocketAddress(SOCKET_NAME, LocalSocketAddress.Namespace.RESERVED); new ConnectSocketThread().start(); } /** * 發(fā)送消息 * @param msg * @return 返回Socket服務(wù)端消息回執(zhí) */ public String sendMsg(String msg){ if(!isConnected) return "Connect failure"; try{ BufferedReader in = new BufferedReader(new InputStreamReader(client.getInputStream())); PrintWriter out = new PrintWriter(client.getOutputStream()); out.println(msg); out.flush(); return in.readLine(); }catch(IOException e){ e.printStackTrace(); } return "Nothing Return"; } /** * Socket連接線(xiàn)程,若連接失敗會(huì)嘗試重連3次 * @author Administrator * */ private class ConnectSocketThread extends Thread{ @Override public void run() { while(!isConnected && connectTime <= 3){ try{ sleep(1000); Log.d("itv_assistance", "Try to connect socket; ConnectTime: "+connectTime); client.connect(address); isConnected = true; }catch(Exception e){ connectTime++; isConnected = false; Log.d("itv_assistance", "Connect Failure"); } } } } /** * 關(guān)閉Socket */ public void closeSocket(){ try{ client.close(); }catch(IOException e){ e.printStackTrace(); } } }
至此,基于非root的Android設(shè)備上的抓包實(shí)現(xiàn)方法就完成了,接下來(lái)就是編譯系統(tǒng)進(jìn)行測(cè)試了,這步我沒(méi)有親自去做,而是把fstiService腳本及init.rc配置文件的操作交給合作廠(chǎng)商來(lái)做了,apk是我們自己做的,經(jīng)測(cè)試一切OK。
點(diǎn)擊下載源碼:http://xiazai.jb51.net/201611/yuanma/AndroidFstiService(jb51.net)
以上所述是小編給大家介紹的Android設(shè)備上非root的抓包實(shí)現(xiàn)方法(Tcpdump方法),實(shí)現(xiàn)一個(gè)模擬后臺(tái)數(shù)據(jù)登入的效果,希望對(duì)大家有所幫助,如果大家有任何疑問(wèn)請(qǐng)給我留言,小編會(huì)及時(shí)回復(fù)大家的。在此也非常感謝大家對(duì)腳本之家網(wǎng)站的支持!
相關(guān)文章
Android仿QQ個(gè)人標(biāo)簽添加與刪除功能
這篇文章主要為大家詳細(xì)介紹了Android仿QQ個(gè)人標(biāo)簽添加與刪除功能,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-12-12C#之Android手機(jī)App開(kāi)發(fā)
這篇文章主要為大家詳細(xì)介紹了C#之Android手機(jī)App開(kāi)發(fā),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-06-06Android編程實(shí)現(xiàn)Gallery中每次滑動(dòng)只顯示一頁(yè)的方法
這篇文章主要介紹了Android編程實(shí)現(xiàn)Gallery中每次滑動(dòng)只顯示一頁(yè)的方法,涉及Android擴(kuò)展Gallery控件實(shí)現(xiàn)翻頁(yè)效果控制的功能,涉及Android事件響應(yīng)及屬性控制的相關(guān)技巧,需要的朋友可以參考下2015-11-11關(guān)于A(yíng)ndroid短信驗(yàn)證碼的獲取的示例
本篇文章主要介紹了關(guān)于A(yíng)ndroid短信驗(yàn)證碼的獲取的示例,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-08-08Android基于訊飛語(yǔ)音SDK實(shí)現(xiàn)語(yǔ)音識(shí)別
本例子是一個(gè)調(diào)用訊飛語(yǔ)音識(shí)別SDK的例子源碼是一個(gè)最純凈的Demo比較容易看懂。實(shí)現(xiàn)的是點(diǎn)擊按鈕開(kāi)始語(yǔ)音監(jiān)聽(tīng),手機(jī)需要聯(lián)網(wǎng),2/3G的均可,希望本文對(duì)大家學(xué)習(xí)Android有所幫助2016-06-06Android自定義控件實(shí)現(xiàn)顏色選擇器
這篇文章主要為大家詳細(xì)介紹了Android自定義控件實(shí)現(xiàn)顏色選擇器,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-06-06android編程開(kāi)發(fā)之全屏和退出全屏的實(shí)現(xiàn)方法
這篇文章主要介紹了android編程開(kāi)發(fā)之全屏和退出全屏的實(shí)現(xiàn)方法,以實(shí)例形式較為詳細(xì)的分析了Android全屏及退出全屏的頁(yè)面布局與功能實(shí)現(xiàn)技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-11-11Android生存指南之:開(kāi)發(fā)中的注意事項(xiàng)
本篇文章是對(duì)在A(yíng)ndroid開(kāi)發(fā)中的一些注意事項(xiàng),需要的朋友可以參考下2013-05-05