Android设备上非root的抓包实现方法(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<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_tthread[2]; chars_time[10];//抓包时间子串 chars_command[256];//抓包指令子串 //抓包指令:system("/system/bin/tcpdump-v-w/sdcard/te.pcap"); //获取进程tcpdump的进程号 intgetPid(){ //forLinuxC //FILE*fp=popen("ps-e|grep\'tcpdump\'|awk\'{print$1}\'","r"); //forAndroid(ARM) //FILE*fp=popen("ps|grep\'tcpdump\'","r"); FILE*fp=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}; char*p=buff; char*q=dst; //每一行进程信息的第二个字符串为进程号 while(*p!='') p++; while(*p=='') p++; while(*p!='') *(q++)=*(p++); *(q++)='\0'; returnatoi(dst); } //截取子串(抓包时间(秒):抓包命令) voidsubstring(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){ inti_time=atoi(s_time); intbegin=time((time_t*)NULL); while(1){ if(time((time_t*)NULL)-begin<i_time){ //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,structsockaddr*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,(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,void*buf,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,constvoid*msg,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; publicSocketClient(){ 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。
点击下载源码:http://xiazai.jb51.net/201611/yuanma/AndroidFstiService(jb51.net)
以上所述是小编给大家介绍的Android设备上非root的抓包实现方法(Tcpdump方法),实现一个模拟后台数据登入的效果,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对毛票票网站的支持!