libpcap 在Android 上抓包,实时分析的思路及实现

这是我的第一篇博客,问题可能会有很多,希望大家多多指教



先给大家看效果图,这个程序,用作测试没问题,大家可以试一下




运行主界面,点击菜单




菜单界面,点击开始抓包,然后切换出去,用浏览器上网,再切换回来


上图是抓到的包,显示在界面上

主要思路

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 -g
LOCAL_CFLAGS+=-DHAVE_CONFIG_H -D_U_="__attribute__((unused))" -Dlinux -D__GLIBC__ -D_GNU_SOURCE

LOCAL_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 block
				e.printStackTrace();
			}
		
		
		FileOutputStream fos = null;
		try {
			fos = new 	FileOutputStream(file);
		} catch (FileNotFoundException e1) {
			// TODO Auto-generated catch block
			e1.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 block
			e.printStackTrace();
		}
		
		try {
			fos.flush();
			fos.close();
			is.close();
			
			return true;
			
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		return false;

	}
	@Override
	protected 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() {

			@Override
			public void onItemClick(AdapterView<?> parent, View view,
					int position, long id) {
				// TODO Auto-generated method stub
				
				Packet 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()
		{
			
			
			@Override
			public void run() {
				boolean isSuccess=true;
				// TODO Auto-generated method stub
				Log.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 block
						Log.i(TAG, "从asset读取ip数据库失败");
						isSuccess=false;
						e.printStackTrace();
					}
					if(is!=null)
					
						isSuccess=	writeToSDCard(is);
					else
						Log.i(TAG, "从asset复制ip数据库到sdcard中失败");
				}
				//将事件发送到ui线程
				if(isSuccess)
					
				mHandler.obtainMessage(1, "本地ip数据库加载完毕").sendToTarget();
				else
					mHandler.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();
		            }
		        }
		    }
}

	@Override
	public boolean onCreateOptionsMenu(Menu menu) {
		// TODO Auto-generated method stub
		menu.add("开始抓包");
		menu.add("停止抓包");
		
		return super.onCreateOptionsMenu(menu);
	}
	@Override
	public boolean onMenuItemSelected(int featureId, MenuItem item) {
		// TODO Auto-generated method stub
		String 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 stub
		try {
			RootTools.closeAllShells();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.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







  • 4
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 55
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 55
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值