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







1 0