通常我们在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
group root
user root

 

此处配置了一个名为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。

项目源码下载

参考:

Linux下C语言多线程编程:http://www.cnblogs.com/chenyadong/archive/2011/10/25/2223610.html

C语言socket send()数据缓存问题:http://www.tuicool.com/articles/NvIjma

posted on 2016-11-09 11:01  阳光沙滩  阅读(3129)  评论(0编辑  收藏  举报