配色: 字号:
非root Android设备上Tcpdump的实现
2016-11-09 | 阅:  转:  |  分享 
  
非rootAndroid设备上Tcpdump的实现

通常我们在Android应用中执行某个命令时会使用“Runtime.getRuntime().exec("命令路径")”这种方式,但是当我们执行抓包操作时,使用这条命令无论如何都不行,通过下面代码打印结果发现,该命令一定要在root权限下才能执行。



BufferedReaderbrW=newBufferedReader(newInputStreamReader(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







复制代码

#defineSOCKET_NAME"fstiService"

#defineLOGD(...)__android_log_print(ANDROID_LOG_DEBUG,"itv_assistance",__VA_ARGS__)

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

pthread_tthread[2];

chars_time[10];//抓包时间子串

chars_command[256];//抓包指令子串

//抓包指令:system("/system/bin/tcpdump-v-w/sdcard/te.pcap");

//获取进程tcpdump的进程号

intgetPid(){

//forLinuxC

//FILEfp=popen("ps-e|grep\''tcpdump\''|awk\''{print$1}\''","r");

//forAndroid(ARM)

//FILEfp=popen("ps|grep\''tcpdump\''","r");

FILEfp=popen("ps|grep\''tcpdump\''","r");

charbuff[1024]={0};

while(NULL!=fgets(buff,sizeof(buff),fp))

;

//取消换行符(10)

buff[strlen(buff)-1]=''\0'';

pclose(fp);

chardst[5]={0};

charp=buff;

charq=dst;

//每一行进程信息的第二个字符串为进程号

while(p!='''')

p++;

while(p=='''')

p++;

while(p!='''')

(q++)=(p++);

(q++)=''\0'';

returnatoi(dst);

}

//截取子串(抓包时间(秒):抓包命令)

voidsubstring(chartime,charcommand,charsrc){

charp=src;

charq=time;

chars=command;

while(p!=''/'')

(q++)=(p++);

(q++)=''\0'';

//如果Tcpdump命令已添加环境变量,则添加下行代码

//否则删除下一行代码,client传递的参数格式必须为:num/tcpdump所在路径

p++;

while(p)

(s++)=(p++);

(s++)=''\0'';

}



//抓包线程

voidthread1(voidarg){

system(s_command);

}

voidthread2(voidarg){

inti_time=atoi(s_time);

intbegin=time((time_t)NULL);

while(1){

if(time((time_t)NULL)-begin
//printf("当前时间(s):%ld\n",time((time_t)NULL));

continue;

}else{

intn=kill(getPid(),SIGKILL);

LOGD("thekillprocessresultisn=%d",n);

break;

}

}

return0;

}

//创建子线程

voidthread_create(){

inttemp;

memset(&thread,0,sizeof(thread));

if((temp=pthread_create(&thread[0],NULL,thread1,NULL))!=0)

LOGD("createtcpdumpthreadfailure");

else

LOGD("createtcpdumpthreadsuccess");

if((temp=pthread_create(&thread[1],NULL,thread2,NULL))!=0)

LOGD("createcountthreadfailure");

else

LOGD("createcountthreadsuccess");

}



voidthread_wait(){

if(thread[0]!=0){

pthread_join(thread[0],NULL);

LOGD("tcpdumpthreadhasterminated");

}

if(thread[1]!=0){

//pthread_join(thread[1],NULL);

printf("counterthreadhasterminated");

}

}



/

Native层Socket服务端

/

intmain(){

intconnect_number=6;

intfdListen=-1,new_fd=-1;

intret;

structsockaddr_unpeeraddr;

socklen_tsocklen=sizeof(peeraddr);

intnumbytes;

charbuff[256];



//这一步很关键,就是获取init.rc中配置的名为"fstiService"的socket

//获取已绑定的socket,返回-1为错误情况

fdListen=android_get_control_socket(SOCKET_NAME);

if(fdListen<0){

LOGD("failedtogetsocket''"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("listenresult%d",ret);

if(ret<0){

/

perror(s)将一个函数发生错误的原因输出到标准设备(stderr)

参数s所指的字符串会先打印出,后面再加上错误原因字符串

/

perror("listen");

exit(-1);

}

/

方法说明:accept(ints,structsockaddraddr,socklen_taddrlen)用来接受参数s的socket连接。

socket必须先经bind()、listen()函数处理过,当有socket客户端连接进来时会返回一个新的socket处理

代码,往后的数据传送与读取就是经由新的socket处理,而原来参数s的socket能继续使用accept()来接受新的

连接请求。连线成功时,参数addr所指的结构会被系统填入远程主机的地址数据,参数addrlen为sockaddr的

结构长度。

返回值:成功返回新的socket处理代码,失败返回-1,错误原因存在于errno中。

/

new_fd=accept(fdListen,(structsockaddr)&peeraddr,&socklen);

LOGD("accept_fd%d",new_fd);

if(new_fd<0){

LOGD("%d",errno);

perror("accepterror");

exit(-1);

}



//循环等待Socket客户端发来消息

while(1){

/

方法说明:recv(ints,voidbuf,size_tlen,unsignedintflags)用来接收

客户端socket传来的数据,并把数据存到由参数buf指向的内存空间,参数len为可接收数据的最大长度。

参数flags一般设0

返回值:失败返回-1

/

if((numbytes=recv(new_fd,buff,sizeof(buff),0))==-1){

LOGD("%d",errno);

perror("recv");

continue;

}

LOGD("theparameterreceivedfromsocketclientis%s",buff);

if(strcmp(buff,"exit")!=0){

substring(s_time,s_command,buff);

thread_create();

thread_wait();

}

charresult[10]="successp";

/

方法说明:send(ints,constvoidmsg,size_tlen,unsignedintflags)

参数s为已建立好连接的socket,参数msg指向欲发送的数据内容,参数len为数据长度,flags一般置0.

返回值:失败返回-1,错误原因存在errno中

/

intsendR=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);

return0;

}

复制代码





2、配置init.rc文件,添加如下配置







servicefstiService/system/bin/fstiService

socketfstiServicestream777systemsystem

classmain





此处配置了一个名为“fstiService”的服务,Android设备开机会自动启动并运行/system/bin/fstiService这个脚本文件。服务端代码完成后,我们需要将其编译成可执行脚本fstiService,Android.mk内容如下:







复制代码

LOCAL_PATH:=$(callmy-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客户端代码







复制代码

publicclassSocketClient{

privatefinalStringSOCKET_NAME="fstiService";

privateLocalSocketclient=null;

privateLocalSocketAddressaddress=null;

privatebooleanisConnected=false;

privateintconnectTime=1;

publicSocketCwww.baiyuewang.netlient(){

client=newLocalSocket();

//AsocketintheAndroidreservednamespacein/dev/socket.

//Onlytheinitprocessmaycreateasockethere

address=newLocalSocketAddress(SOCKET_NAME,LocalSocketAddress.Namespace.RESERVED);

newConnectSocketThread().start();

}

/

发送消息

@parammsg

@return返回Socket服务端消息回执

/

publicStringsendMsg(Stringmsg){

if(!isConnected)

return"Connectfailure";

try{

BufferedReaderin=newBufferedReader(newInputStreamReader(client.getInputStream()));

PrintWriterout=newPrintWriter(client.getOutputStream());

out.println(msg);

out.flush();

returnin.readLine();

}catch(IOExceptione){

e.printStackTrace();

}

return"NothingReturn";

}

/

Socket连接线程,若连接失败会尝试重连3次

@authorAdministrator



/

privateclassConnectSocketThreadextendsThread{

@Override

publicvoidrun(){

while(!isConnected&&connectTime<=3){

try{

sleep(1000);

Log.d("itv_assistance","Trytoconnectsocket;ConnectTime:"+connectTime);

client.connect(address);

isConnected=true;

}catch(Exceptione){

connectTime++;

isConnected=false;

Log.d("itv_assistance","ConnectFailure");

}

}

}

}

/

关闭Socket

/

publicvoidcloseSocket(){

try{

client.close();

}catch(IOExceptione){

e.printStackTrace();

}

}

}

复制代码





至此,基于非root的Android设备上的抓包实现方法就完成了,接下来就是编译系统进行测试了,这步我没有亲自去做,而是把fstiService脚本及init.rc配置文件的操作交给合作厂商来做了,apk是我们自己做的,经测试一切OK。

献花(0)
+1
(本文系thedust79首藏)