欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

Android设备上非root的抓包实现方法(Tcpdump方法)

程序员文章站 2024-03-02 20:37:04
通常我们在android应用中执行某个命令时会使用“runtime.getruntime().exec("命令路径")”这种方式,但是当我们执行抓包操作时,使用这条命令无论...

通常我们在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方法),实现一个模拟后台数据登入的效果,希望对大家有所帮助