Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android設備上非root的抓包實現方法(Tcpdump方法)

Android設備上非root的抓包實現方法(Tcpdump方法)

編輯:關於Android編程

通常我們在Android應用中執行某個命令時會使用“Runtime.getRuntime().exec("命令路徑")”這種方式,但是當我們執行抓包操作時,使用這條命令無論如何都不行,通過下面代碼打印結果發現,該命令一定要在root權限下才能執行。

BufferedReader brW = new BufferedReader(new InputStreamReader(p.getErrorStream()));
while((str = brW.readLine()) != null)
Log.d("cwmp", "w:"+str);

但是我們的Android設備(包括機頂盒、手機等)通常並沒有root過,apk的最高權限也只是system權限,這該怎麼解決?首先我們要知道,方法總比問題多,在Android設備的/system/bin路徑下,我們會看到很多二進制文件,這些二進制文件可以獲得root權限。因此,我們可以通過C語言來實現抓包功能,通過NDK把該C代碼交叉編譯成二進制文件置於/system/bin路徑下,並賦予其root權限,此時,這個二進制文件就具備了抓包能力了。現在問題又來了,我們現在是想通過apk去調用這個抓包指定,抓包完成後又該怎麼通知apk呢?其實,Android可以通過socket使底層與framework層進行通信,具體請參考Android中使用socket使底層和framework通信的實現方法。

接下來我們將貼出關鍵實現代碼。

1、編寫socket服務端代碼fstiService.cpp,生成可執行腳本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]; //抓包時間子串
char s_command[256]; //抓包指令子串
//抓包指令:system("/system/bin/tcpdump -v -w /sdcard/te.pcap");
//獲取進程tcpdump的進程號
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;
//每一行進程信息的第二個字符串為進程號
while (*p != ' ')
p++;
while (*p == ' ')
p++;
while (*p != ' ')
*(q++) = *(p++);
*(q++) = '\0';
return atoi(dst);
}
//截取子串(抓包時間(秒):抓包命令)
void substring(char *time, char *command, char *src) {
char *p = src;
char *q = time;
char *s = command;
while (*p != '/')
*(q++) = *(p++);
*(q++) = '\0';
//如果Tcpdump命令已添加環境變量,則添加下行代碼
//否則刪除下一行代碼,client傳遞的參數格式必須為: num/tcpdump所在路徑
p++;
while (*p)
*(s++) = *(p++);
*(s++) = '\0';
}
//抓包線程
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("當前時間(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;
}
//創建子線程
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服務端
*/
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];
//這一步很關鍵,就是獲取init.rc中配置的名為 "fstiService" 的socket
//獲取已綁定的socket,返回-1為錯誤情況
fdListen = android_get_control_socket(SOCKET_NAME);
if (fdListen < 0) {
LOGD("failed to get socket '" SOCKET_NAME "' errno %d", errno);
exit(-1);
}
/**
* 方法說明:開始監聽(等待參數fdListen的socket連接,參數connect_number指定同時能處理的最大連接要求)
* 如果連接數目達此上限則client端將收到ECONNREFUSED的錯誤。listen函數並未開始連接,只是設置
* socket為listen模式,真正接收client端連接的是accept()。通常listen()會在socket()
* bind()之後調用,接著才調用accept().
* 返回值:成功返回0,失敗返回-1,錯誤原因存在errno中
*/
ret = listen(fdListen, connect_number);
LOGD("listen result %d", ret);
if (ret < 0) {
/**
* perror(s)將一個函數發生錯誤的原因輸出到標准設備(stderr)
* 參數s所指的字符串會先打印出,後面再加上錯誤原因字符串
*/
perror("listen");
exit(-1);
}
/**
* 方法說明:accept(int s, struct sockaddr * addr, socklen_t * addrlen)用來接受參數s的socket連接。
* socket必須先經bind()、listen()函數處理過,當有socket客戶端連接進來時會返回一個新的socket處理
* 代碼,往後的數據傳送與讀取就是經由新的socket處理,而原來參數s的socket能繼續使用accept()來接受新的
* 連接請求。連線成功時, 參數addr 所指的結構會被系統填入遠程主機的地址數據,參數addrlen為sockaddr的
* 結構長度。
* 返回值:成功返回新的socket處理代碼,失敗返回-1,錯誤原因存在於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);
}
//循環等待Socket客戶端發來消息
while (1) {
/**
* 方法說明:recv(int s, void *buf, size_t len, unsigned int flags)用來接收
* 客戶端socket傳來的數據,並把數據存到由參數buf指向的內存空間,參數len為可接收數據的最大長度。
* 參數flags一般設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";
/**
* 方法說明:send(int s, const void *msg, size_t len, unsigned int flags)
* 參數s為已建立好連接的socket,參數msg指向欲發送的數據內容,參數len為數據長度,flags一般置0.
* 返回值:失敗返回-1,錯誤原因存在errno中
*/
int sendR = send(new_fd, result, strlen(result), 0);
//apk退出後,buff中仍然緩存之前的調用命令,此時會額外再執行一次抓包,固下面代用重寫buff中數據
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

此處配置了一個名為“fstiService”的服務,Android設備開機會自動啟動並運行/system/bin/fstiService這個腳本文件。服務端代碼完成後,我們需要將其編譯成可執行腳本fstiService,Android.mk內容如下:

LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
#指定該模塊在所有版本下都編譯
LOCAL_MODULE_TAGS :=optional
LOCAL_MODULE := fstiService
LOCAL_SRC_FILES := fstiService.cpp
LOCAL_LDLIBS := -llog
#編譯成動態庫
#include $(BUILD_SHARED_LIBRARY)
#編譯成可執行文件
include $(BUILD_EXECUTABLE)

3、Android客戶端代碼

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();
}
/**
* 發送消息
* @param msg
* @return 返回Socket服務端消息回執
*/
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連接線程,若連接失敗會嘗試重連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");
}
}
}
}
/**
* 關閉Socket
*/
public void closeSocket(){
try{
client.close();
}catch(IOException e){
e.printStackTrace();
}
}
}

  至此,基於非root的Android設備上的抓包實現方法就完成了,接下來就是編譯系統進行測試了,這步我沒有親自去做,而是把fstiService腳本及init.rc配置文件的操作交給合作廠商來做了,apk是我們自己做的,經測試一切OK。

點擊下載源碼:http://xiazai.jb51.net/201611/yuanma/AndroidFstiService(jb51.net)

以上所述是小編給大家介紹的Android設備上非root的抓包實現方法(Tcpdump方法),實現一個模擬後台數據登入的效果,希望對大家有所幫助,如果大家有任何疑問請給我留言,小編會及時回復大家的。在此也非常感謝大家對本站網站的支持!

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved