通常我们在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");elseLOGD("create tcpdump thread success");if ((temp = pthread_create(&thread[1], NULL, thread2, NULL)) != 0)LOGD("create count thread failure");elseLOGD("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/fstiServicesocket fstiService stream 777 system systemclass main
此处配置了一个名为“fstiService”的服务,Android设备开机会自动启动并运行/system/bin/fstiService这个脚本文件。服务端代码完成后,我们需要将其编译成可执行脚本fstiService,Android.mk内容如下:
LOCAL_PATH := $(call my-dir)include $(CLEAR_VARS)#指定该模块在所有版本下都编译LOCAL_MODULE_TAGS :=optionalLOCAL_MODULE := fstiServiceLOCAL_SRC_FILES := fstiService.cppLOCAL_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 hereaddress = 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{@Overridepublic 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.VeVB.COm/201611/yuanma/AndroidFstiService(VeVB.COm)
以上所述是小编给大家介绍的Android设备上非root的抓包实现方法(Tcpdump方法),实现一个模拟后台数据登入的效果,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对武林网网站的支持!
新闻热点
疑难解答