libpcap 在Android 上抓包,实时分析的思路及实现
来源:互联网 发布:ubuntu常用软件推荐 编辑:程序博客网 时间:2024/05/20 17:41
这是我的第一篇博客,问题可能会有很多,希望大家多多指教
先给大家看效果图,这个程序,用作测试没问题,大家可以试一下
运行主界面,点击菜单
菜单界面,点击开始抓包,然后切换出去,用浏览器上网,再切换回来
上图是抓到的包,显示在界面上
主要思路
1 使用库libpcap,写个简单的c程序main.c,实现抓包,在控制台输出所抓取包的内容,可以先在linux用gcc编译,以root权限运行一次,确保程序正确
2 用ndk编译main.c,得到可执行文件pcap(名字自己取的),放到/system/bin下,修改权限,用手机连接电脑,用adb shell以root权限 试一下,确保程序正确
3 在Android程序中以root权限执行pcap,并获得输出的数据
4 将输出的数据解析成Packet(jpcap中的一个类)类的对象
5 然后在java中,你就可以做你想做的了
具体实现
以前看了一些关于libpcap移植到Android上的文章,自己动手做了一两次,由于权限问题,一直获取不到网卡设备,本人对于Android权限不太懂,没有继续尝试了
最近才将自己的手机root,发现手机里面的/system/xbin/里有tcpdump,就用tcpdum 试了下抓包,以下是抓取包的数据截图
从上图,我们可以看到,这些抓取出来的数据,不知道他的格式是什么(去网上搜了比较久,还是不知道),我决定去看了一下tcpdump的输出部分的源码,自己c语言太差了,看不下去,所以这个数据格式,就暂时没法知道了。后面想到既然tcpdump可以抓到包,为什么自己不能写一个简单一点的抓包程序,输出的数据格式,自己可以灵活处理。
然后在网上搜了一些关于Android ndk编译C语言为可执行二进制的文件教程,最后写了一个使用了libpcap简单的c语言抓包程序文件名为main.c,如果你了解一点libpcap这个就很简单了,
#include <stdio.h>#include <pcap.h>#include <stdlib.h>#include <string.h> void myCallback(u_char * uchar, const struct pcap_pkthdr * packet,const u_char * data) { bpf_u_int32 length= packet->caplen; bpf_u_int32 plen= packet->len;struct timeval time= packet->ts; printf("the cap len: %d,the pcaket len:%d\n ",length,plen); printf("the timeinfo:tv_sec:%ld, tv_usec:%ld\n",time.tv_sec,time.tv_usec); int i; for(i=0;i<packet->len;i++) printf("%02x",data[i]); printf("\n"); }int main(){ char error_buffer[100]; char *result= pcap_lookupdev(error_buffer); char error_buffer2[100]; pcap_t* pPcap_t= pcap_open_live(result,BUFSIZ,0,-1,error_buffer2); unsigned char error_buffer1[100]; pcap_loop(pPcap_t,-1,myCallback,error_buffer1); return 0;}将这个文件和Android系统源代码中的libpcap的源代码文件放在一起,将其中的Android.mk文件修改一下
LOCAL_PATH:= $(call my-dir)include $(CLEAR_VARS)LOCAL_SRC_FILES:=\bpf_dump.c\bpf/net/bpf_filter.c\bpf_image.c\etherent.c\fad-gifc.c\gencode.c\grammar.c\inet.c\nametoaddr.c\optimize.c\pcap.c\pcap-linux.c\savefile.c\scanner.c\version.c<span style="color:#FF6666;">\main.c</span>LOCAL_CFLAGS:=-O2 -gLOCAL_CFLAGS+=-DHAVE_CONFIG_H -D_U_="__attribute__((unused))" -Dlinux -D__GLIBC__ -D_GNU_SOURCELOCAL_MODULE:= pcap<span style="color:#FF0000;">include $(BUILD_EXECUTABLE)</span>红色部分是修改的地方,作用是将自己的main.c文件加进去,将编译出来的文件为可执行。如果编译没有报错的话,最后编译得到了一个pcap可执行文件,将这个文件放到Android system/bin的目录下,修改权限,
我的做法是,将这个文件先放到手机的sdcard的根目录,然后用一款叫做Root Explorer的文件管理器,将这个文件复制到/system/bin目录下,
修改它的权限
将手机连上电脑,然后可以用adb shell试下我们写的程序是否可以抓包
这是程序运行的结果,对于以上的输出结果,就是下面这段代码输出来的,数据分为3行,每一行的数据我们都知道它属于数据包的那一部分,
void myCallback(u_char * uchar, const struct pcap_pkthdr * packet,const u_char * data) { bpf_u_int32 length= packet->caplen; bpf_u_int32 plen= packet->len;struct timeval time= packet->ts; <span style="color:#FF0000;"> printf("the cap len: %d,the pcaket len:%d\n ",length,plen); printf("the timeinfo:tv_sec:%ld, tv_usec:%ld\n",time.tv_sec,time.tv_usec); int i; for(i=0;i<packet->len;i++) printf("%02x",data[i]); printf("\n");</span> }现在我们已经知道了数据的输出的格式了,这些数据输出在控制台,我们接下来要解决如何在Android程序中以root权限执行这个pcap并且获得其输出的数据
我的解决思路是,采用了一个叫做RootTools的Android工具库,用它来执行pcap,并且获得其输出在控制台的数据。下图是Android中的log输出
上图的输出,跟在电脑上用adb shell 执行pcap在控制台的输出数据是一致的,我们已经在Android应用中获得了pcap在控制台的输出数据,
接下来我们就是要将这些数据解析成Java对象了。jpcap,jnetpcap中都应该含有解析byte数据,转为Java对象的代码,阅读了jpcap,jnetpcap部分源代码,最终采用jpcap中的部分源代码(这部分纯java)来实现byte数据的解析,(其中有过自己去写解析的函数的想法,感觉不可能写出来,只有继续去看源代码),数据解析这一关就攻克了。下面是Android程序中的主要代码
package com.example.jpcapforandroid; import java.io.File;import java.io.FileNotFoundException;import java.io.FileOutputStream;import java.io.IOException;import java.io.InputStream;import net.sourceforge.jpcap.net.LinkLayer;import net.sourceforge.jpcap.net.Packet;import net.sourceforge.jpcap.net.PacketFactory;import com.stericson.RootTools.RootTools;import com.stericson.RootTools.execution.Command;import com.stericson.RootTools.execution.Shell;import com.tqd.utils.IPSeeker;import android.app.Activity;import android.content.Context;import android.content.res.AssetManager;import android.graphics.Typeface;import android.os.Bundle;import android.os.Environment;import android.os.Handler;import android.os.UserHandle;import android.util.Log;import android.view.Menu;import android.view.MenuItem;import android.view.View;import android.view.Window;import android.widget.AdapterView;import android.widget.AdapterView.OnItemClickListener;import android.widget.ListView;import android.widget.TextView;import android.widget.Toast;public class MainActivity extends Activity {public static final String DIR="ipdatabase";public static final String FILE_NAME="qqwry.dat";private static final String TAG = "MainActivity"; UserHandle userHandle=android.os.Process.myUserHandle();ListView mListViewPacket;PacketListAdapter pla;public static int tip=0;public IPSeeker mIPSeeker;public static long mPacketCount=0;public TextView mTVPacketcount;/** * 将ip数据库文件数据流写入到sdcard指定文件 * @param is */public boolean writeToSDCard(InputStream is){String root=Environment.getExternalStorageDirectory().getAbsolutePath();File dir=new File(root+File.separator+DIR);Log.i(TAG, "dir path: "+dir.getAbsolutePath());//不存在目录,则创建目录if(!dir.exists())dir.mkdir();Log.i(TAG, "file path: "+dir.getAbsolutePath()+File.separator+FILE_NAME);File file=new File(dir.getAbsolutePath()+File.separator+FILE_NAME);//不存在文件,则创建文件if(!file.exists())try {file.createNewFile();} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();}FileOutputStream fos = null;try {fos = new FileOutputStream(file);} catch (FileNotFoundException e1) {// TODO Auto-generated catch blocke1.printStackTrace();}int temp=-1;byte[] buffer=new byte[1024];try {while((temp=is.read(buffer))!=-1){fos.write(buffer, 0, temp);}} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();}try {fos.flush();fos.close();is.close();return true;} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();}return false;}@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);loadIpDatabase();getWindow().requestFeature(Window.FEATURE_NO_TITLE);setContentView(R.layout.activity_main);mTVPacketcount=(TextView) findViewById(R.id.packet_count);//加载,设置字体Typeface tf=Typeface.createFromAsset(getAssets(), "hkww.ttf");mTVPacketcount.setTypeface(tf);mTVPacketcount.setText("已抓取"+mPacketCount+"个packet");mListViewPacket=(ListView) this.findViewById(R.id.packet_list);pla=new PacketListAdapter(this);mListViewPacket.setAdapter(pla);//Log.i("userHandle", userHandle.toString());RootTools rt=new RootTools();RootTools.default_Command_Timeout=1000*1000; //设置执行命令超时的值,pcap命令是个死循环,这个设置大一点好些, RootTools.debugMode=true;mListViewPacket.setOnItemClickListener(new OnItemClickListener() {@Overridepublic void onItemClick(AdapterView<?> parent, View view,int position, long id) {// TODO Auto-generated method stubPacket packet=(Packet) pla.getItem(position);Toast.makeText(MainActivity.this, packet.toColoredString(false), Toast.LENGTH_LONG).show();}});}Handler mHandler=new Handler(){public void handleMessage(android.os.Message msg) {if(msg.what==0 && msg.obj instanceof Packet){Packet packet=(Packet) msg.obj;pla.addPacket(packet);mPacketCount++;mTVPacketcount.setText("已抓取"+mPacketCount+"个packet");}if(msg.what==1 && msg.obj instanceof String){Toast.makeText(MainActivity.this, msg.obj.toString(), Toast.LENGTH_LONG).show();mIPSeeker=new IPSeeker(FILE_NAME, Environment.getExternalStorageDirectory()+File.separator+DIR);pla.setIPSeeker(mIPSeeker);}if(msg.what==2 && msg.obj instanceof String){Toast.makeText(MainActivity.this, msg.obj.toString(), Toast.LENGTH_LONG).show();}};};/** * 加载本地ip数据库 */private void loadIpDatabase(){new Thread(){@Overridepublic void run() {boolean isSuccess=true;// TODO Auto-generated method stubLog.i(TAG,"ip数据库默认路径"+Environment.getExternalStorageDirectory()+DIR+File.separator+FILE_NAME);//查看sdcard指定目录中的本地ip数据库是否存在File file=new File(Environment.getExternalStorageDirectory()+DIR+File.separator+FILE_NAME);Log.i(TAG, Environment.getExternalStorageDirectory()+DIR+File.separator+FILE_NAME+file.getAbsolutePath());//不存在就从asset中复制到sdcard指定目录中if(!file.exists()){Log.i(TAG, "不存在ip数据库,从asset复制到sdcard中");AssetManager am=MainActivity.this.getAssets();InputStream is = null;try { is=am.open("qqwry.dat");} catch (IOException e) {// TODO Auto-generated catch blockLog.i(TAG, "从asset读取ip数据库失败");isSuccess=false;e.printStackTrace();}if(is!=null)isSuccess=writeToSDCard(is);elseLog.i(TAG, "从asset复制ip数据库到sdcard中失败");}//将事件发送到ui线程if(isSuccess)mHandler.obtainMessage(1, "本地ip数据库加载完毕").sendToTarget();elsemHandler.obtainMessage(2, "本地ip数据库加载失败").sendToTarget();}}.start();}/** * 启动抓包 */public void start(){//用RootTools帮我们查询到pcap的路径,其实我们可以自己指定为/system/bin/pcap的,RootTools.findBinary("pcap");//MyRunner是我自己写的一个继承于Runner的类,用来执行命令的一个线程类//本来使用这个的RootTools.runBinary(context, binaryName, parameter);但是不知道命令执行的输出数据,如何获取, MyRunner mr=new MyRunner(this, RootTools.lastFoundBinaryPaths.get(0)+"pcap", "");mr.start();}/** * @author Administrator * 这个类包含了命令执行时,数据输出的回调函数 commandOutput 我们主要在这个函数里做我们想要的事 * */class MyCommandCapture extends Command{ private StringBuilder sb = new StringBuilder(); public MyCommandCapture(int id, String... command) { super(id, command); } public MyCommandCapture(int id, boolean handlerEnabled, String... command) { super(id, handlerEnabled, command); } public MyCommandCapture(int id, int timeout, String... command) { super(id, timeout, command); } /* (non-Javadoc) * @see com.stericson.RootTools.execution.Command#commandOutput(int, java.lang.String) */ @Override public void commandOutput(int id, String line) { sb.append(line).append('\n'); tip++; /* * 在main.c中,我们每抓到一个包,数据输出三行, * * printf("the cap len: %d,the pcaket len:%d\n ",length,plen); printf("the timeinfo:tv_sec:%ld, tv_usec:%ld\n",time.tv_sec,time.tv_usec); int i; for(i=0;i<packet->len;i++) printf("%02x",data[i]); printf("\n"); * * 第一,二行是 const struct pcap_pkthdr * 第三行 是包的数据 byte[]的String形式 */ switch(tip) { case 1: RootTools.log("Command", "ID: " + 1 + ", " + line); break; case 2: RootTools.log("Command", "ID: " + 2 + ", " + line); break; case 3: RootTools.log("Command", "ID: " + 3 + ", " + line); Log.i("data length", ""+line.length()); byte []data=hexStringToBytes(line); //将byte数组,解析成具体的包,第一个参数是数据链路层类型,我的手机是:wifi下是LinkLayer.EN10MB 移动的2G网是LinkLayer.LINUX_SLL //我们可以在main.c里面获得这个具体的值,int pcap_datalink(pcap_t *p) 返回,例如DLT_EN10MB //这里写死了, //数据包的解析用的是jpcap的库,我这里只是用了一部分java代码,没有涉及native层的,纯java, Packet packet= PacketFactory.dataToPacket(LinkLayer.EN10MB, data) ; // ByteBuffer bb=ByteBuffer.wrap(data); mHandler.obtainMessage(0, packet).sendToTarget(); tip=0; break; default: break; } } public String bytesToHexString(byte[] src){ StringBuilder stringBuilder = new StringBuilder(""); if (src == null || src.length <= 0) { return null; } for (int i = 0; i < src.length; i++) { int v = src[i] & 0xFF; String hv = Integer.toHexString(v); if (hv.length() < 2) { stringBuilder.append(0); } stringBuilder.append(hv); } return stringBuilder.toString(); } /** * 十六进制的字符串转化为byte[] * @param hexString * @return */ public byte[] hexStringToBytes(String hexString) { if (hexString == null || hexString.equals("")) { return null; } hexString = hexString.toUpperCase(); int length = hexString.length() / 2; char[] hexChars = hexString.toCharArray(); byte[] d = new byte[length]; for (int i = 0; i < length; i++) { int pos = i * 2; d[i] = (byte) (charToByte(hexChars[pos]) << 4 | charToByte(hexChars[pos + 1])); } return d; } /** * Convert char to byte * @param c char * @return byte */ private byte charToByte(char c) { return (byte) "0123456789ABCDEF".indexOf(c); } @Override public void commandTerminated(int id, String reason) { //pass } @Override public void commandCompleted(int id, int exitcode) { //pass } @Override public String toString() { return sb.toString(); }}class MyRunner extends Thread{ private static final String LOG_TAG = "RootTools::Runner"; Context context; String binaryName; String parameter; /** * @param context 这个参数, * @param binaryName 可执行二进制文件的路径 * @param parameter 命令的参数 例如 ls -l 这个parameter就是指后面的-l */ public MyRunner(Context context, String binaryName, String parameter) { this.context = context; this.binaryName = binaryName; this.parameter = parameter; } public void run() { try { //这个类里面包含了执行命令输出的数据的回调函数, MyCommandCapture command = new MyCommandCapture(0, false, binaryName + " " + parameter); //命令执行,参数是个超时的值,不太懂,设置的尽量大,不小了,找不到几个包,就结束了 Shell.startRootShell(10000*10000).add(command); // Shell.startRootShell().add(command); commandWait(command); } catch (Exception e) {} } private void commandWait(Command cmd) { synchronized (cmd) { try { if (!cmd.isFinished()) { cmd.wait(2000); } } catch (InterruptedException e) { e.printStackTrace(); } } }}@Overridepublic boolean onCreateOptionsMenu(Menu menu) {// TODO Auto-generated method stubmenu.add("开始抓包");menu.add("停止抓包");return super.onCreateOptionsMenu(menu);}@Overridepublic boolean onMenuItemSelected(int featureId, MenuItem item) {// TODO Auto-generated method stubString title=(String) item.getTitle();Log.i(TAG, "the select menuitem is "+title);if("开始抓包".endsWith(title)){start();}if("停止抓包".endsWith(title)){stop();}return super.onMenuItemSelected(featureId, item);}/** * 终止抓包 */private void stop() {// TODO Auto-generated method stubtry {RootTools.closeAllShells();} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();}}}
最后,将ip地址转化为地理地址,这个部分的代码,都是从http://blog.csdn.net/swazn_yj/article/details/1611020 拷贝过来的
感谢swazn_yj的源代码
我看过的教程,感谢这些教程的作者
不使用Cygwin,在eclipse中快速开发JNI,一键生成C头文件.h,以及一键使用NDK交叉编译
纯真ip数据库的解析读取: http://blog.csdn.net/swazn_yj/article/details/1611020
libpcap介绍 http://www.cppblog.com/flyonok/articles/49143.html
libpcap,jnetpcap 移植到Android编译 http://aswang.iteye.com/blog/1038284
jpcap项目地址:https://github.com/jpcap/jpcap
RootTools项目地址在:https://github.com/Stericson/RootTools
libpcap 大家都有吧,
本程序所用到的代码,都会上传
用的编译环境为Android sdk 4.4 ,android-ndk的版本是r9的,测试用的手机的平台为Android 4.2.2,Eclipse+ADT的Android开发环境, libpcap的源码是从Android 4.0的源码中提取出来的
源代码地址:http://download.csdn.net/detail/tanqidong1992/7944833
- libpcap 在Android 上抓包,实时分析的思路及实现
- libpcap底层实现变化的分析
- libpcap底层实现变化的分析
- libpcap底层实现变化的分析
- 自制简易BootLoader思路分析及实现
- Libpcap的安装及使用
- WebService实时接口实现思路?
- CFAN-实时人脸配准实现思路
- Android轮播图原理思路分析+实现方案
- Android轮播图原理思路分析+实现方案
- Android 插件框架实现思路及原理
- Android ButterKnife 的实现思路
- 实现邮箱找回的思路分析
- PHP实现自动登陆的思路分析
- PHP实现自动登陆的思路分析
- 实时改变配置文件的思路
- android 自定义ScrollView实现背景图片伸缩的实现代码及思路
- android 自定义ScrollView实现背景图片伸缩的实现代码及思路
- 动态规划中的钢条切割的介绍与理解(自顶向下递归和自底向上递归四种包含C源代码)
- 给大家推荐一个不错的学习论坛-炼数成金 大家可以使用我的邀请码S309
- python中的装饰器
- Wing IDE 5.0快捷键
- 社会情商的一个角度
- libpcap 在Android 上抓包,实时分析的思路及实现
- [戏剧] [锡剧] 《玉蜻蜓》 庵堂认母 唱词
- JVM调优工具集 -- jps, jstat, jinfo, jmap, jstack
- 开始学习之路
- eclipse 使用Eclipse查看Android源码
- 80端口被System占用的解决办法
- 【互动问答分享】第12期决胜云计算大数据时代Spark亚太研究院公益大讲堂
- Blog居然被封了
- Java开发过程中生成chm格式帮助文档