蛙蛙推荐:自己做个抓包工具(半成品)
用wireshark的命令行模式和windump抓包有时候很难满足抓包的需求,比如我们在一台http服务器上抓http的某个头是指定值的包及iis给其的响应,而其它的包都不要,用现有工具好像就不好实现了,winddump的规则也顶多指定协议、端口之类,具体包的内容过滤上好像就束手无策了,于是想自己做一个,找了一些wincap开发的资料,貌似c#相关的资料不多,找到一个却不能调试,于是又找了一篇讲c#监控网络流量的文章,改造了一下,做了一个命令行抓包工具,因为遇到一些问题,所以还是半成品。
先贴代码,这是类库
using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;
using System.Net.Sockets;
using System.Net;
namespace WawaSoft.WawaCapturer
{
[StructLayout(LayoutKind.Explicit)]
public struct IPHeader
{
[FieldOffset(0)]
public byte ip_verlen; //I4位首部长度+4位IP版本号
[FieldOffset(1)]
public byte ip_tos; //8位服务类型TOS
[FieldOffset(2)]
public ushort ip_totallength; //16位数据包总长度(字节)
[FieldOffset(4)]
public ushort ip_id; //16位标识
[FieldOffset(6)]
public ushort ip_offset; //3位标志位
[FieldOffset(8)]
public byte ip_ttl; //8位生存时间 TTL
[FieldOffset(9)]
public byte ip_protocol; //8位协议(TCP, UDP, ICMP, Etc.)
[FieldOffset(10)]
public ushort ip_checksum; //16位IP首部校验和
[FieldOffset(12)]
public uint ip_srcaddr; //32位源IP地址
[FieldOffset(16)]
public uint ip_destaddr; //32位目的IP地址
}
public class RawSocket
{
private bool error_occurred; //套接字在接收包时是否产生错误
public bool KeepRunning; //是否继续进行
private static int len_receive_buf; //得到的数据流的长度
byte[] receive_buf_bytes; //收到的字节
private Socket socket = null; //声明套接字
const int SIO_RCVALL = unchecked((int)0x98000001);//监听所有的数据包
public RawSocket() //构造函数
{
error_occurred = false;
len_receive_buf = 4096;
receive_buf_bytes = new byte[len_receive_buf];
}
public void CreateAndBindSocket(string IP) //建立并绑定套接字
{
socket = new Socket(AddressFamily.InterNetwork, SocketType.Raw, ProtocolType.IP);
socket.Blocking = false; //置socket非阻塞状态
socket.Bind(new IPEndPoint(IPAddress.Parse(IP), 0)); //绑定套接字
if (SetSocketOption() == false) error_occurred = true;
}
private bool SetSocketOption() //设置raw socket
{
bool ret_value = true;
try
{
socket.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.HeaderIncluded, 1);
byte[] IN = new byte[4] { 1, 0, 0, 0 };
byte[] OUT = new byte[4];
//低级别操作模式,接受所有的数据包,这一步是关键,必须把socket设成raw和IP Level才可用SIO_RCVALL
int ret_code = socket.IOControl(SIO_RCVALL, IN, OUT);
ret_code = OUT[0] + OUT[1] + OUT[2] + OUT[3];//把4个8位字节合成一个32位整数
if (ret_code != 0) ret_value = false;
}
catch (SocketException)
{
ret_value = false;
}
return ret_value;
}
public bool ErrorOccurred
{
get
{
return error_occurred;
}
}
//解析接收的数据包,形成PacketArrivedEventArgs事件数据类对象,并引发PacketArrival事件
unsafe private void Receive(byte[] buf, int len)
{
byte temp_protocol = 0;
uint temp_version = 0;
uint temp_ip_srcaddr = 0;
uint temp_ip_destaddr = 0;
short temp_srcport = 0;
short temp_dstport = 0;
IPAddress temp_ip;
PacketArrivedEventArgs e = new PacketArrivedEventArgs();//新网络数据包信息事件
fixed (byte* fixed_buf = buf)
{
IPHeader* head = (IPHeader*)fixed_buf;//把数据流整和为IPHeader结构
e.HeaderLength = (uint)(head->ip_verlen & 0x0F) << 2;
e.IPHeaderBuffer = new byte[e.HeaderLength];
temp_protocol = head->ip_protocol;
switch (temp_protocol)//提取协议类型
{
case 1: e.Protocol = “ICMP“; break;
case 2: e.Protocol = “IGMP“; break;
case 6: e.Protocol = “TCP“; break;
case 17: e.Protocol = “UDP“; break;
default: e.Protocol = “UNKNOWN“; break;
}
temp_version = (uint)(head->ip_verlen & 0xF0) >> 4;//提取IP协议版本
e.IPVersion = temp_version.ToString();
//以下语句提取出了PacketArrivedEventArgs对象中的其他参数
temp_ip_srcaddr = head->ip_srcaddr;
temp_ip_destaddr = head->ip_destaddr;
temp_ip = new IPAddress(temp_ip_srcaddr);
e.OriginationAddress = temp_ip.ToString();
temp_ip = new IPAddress(temp_ip_destaddr);
e.DestinationAddress = temp_ip.ToString();
temp_srcport = *(short*)&fixed_buf[e.HeaderLength];
temp_dstport = *(short*)&fixed_buf[e.HeaderLength + 2];
e.OriginationPort = IPAddress.NetworkToHostOrder(temp_srcport).ToString();
e.DestinationPort = IPAddress.NetworkToHostOrder(temp_dstport).ToString();
e.PacketLength = (uint)len;
e.MessageLength = (uint)len – e.HeaderLength;
e.MessageBuffer = new byte[e.MessageLength];
e.ReceiveBuffer = buf;
//把buf中的IP头赋给PacketArrivedEventArgs中的IPHeaderBuffer
Array.Copy(buf, 0, e.IPHeaderBuffer, 0, (int)e.HeaderLength);
//把buf中的包中内容赋给PacketArrivedEventArgs中的MessageBuffer
Array.Copy(buf, (int)e.HeaderLength, e.MessageBuffer, 0, (int)e.MessageLength);
}
//引发PacketArrival事件
OnPacketArrival(e);
}
public void Run() //开始监听
{
IAsyncResult ar = socket.BeginReceive(receive_buf_bytes, 0, len_receive_buf, SocketFlags.None, new AsyncCallback(CallReceive), this);
}
private void CallReceive(IAsyncResult ar)//异步回调
{
int received_bytes;
received_bytes = socket.EndReceive(ar);
Receive(receive_buf_bytes, received_bytes);
if (KeepRunning) Run();
}
public void Shutdown() //关闭raw socket
{
if (socket != null)
{
socket.Shutdown(SocketShutdown.Both);
socket.Close();
}
}
public delegate void PacketArrivedEventHandler(Object sender, PacketArrivedEventArgs args);
//事件句柄:包到达时引发事件
public event PacketArrivedEventHandler PacketArrival;//声明时间句柄函数
private void OnPacketArrival(PacketArrivedEventArgs e)
{
PacketArrivedEventHandler temp = PacketArrival;
if (temp != null)
temp(this, e);
}
public class PacketArrivedEventArgs : EventArgs
{
public uint HeaderLength;
public string Protocol;
public string IPVersion;
public string OriginationAddress;
public string DestinationAddress;
public string OriginationPort;
public string DestinationPort;
public uint PacketLength;
public uint MessageLength;
public byte[] ReceiveBuffer;
public byte[] IPHeaderBuffer;
public byte[] MessageBuffer;
public PacketArrivedEventArgs()
{
}
public override string ToString()
{
StringBuilder sb = new StringBuilder();
sb.Append(“”r“n—————-“r“n“);
sb.AppendFormat(“src = {0}:{1}, dst= {2}:{3}“r“n“,OriginationAddress,OriginationPort,
DestinationAddress, DestinationPort);
sb.AppendFormat(“protocol = {0}, ipVersion={1}“r“n“, Protocol, IPVersion);
sb.AppendFormat(“PacketLength={0},MessageLength={1}“,PacketLength,MessageLength);
sb.Append(“”r“n—————-“r“n“);
return sb.ToString();
}
}
}
}
具体原理不说了,详情请看这篇文章
http://www.cnblogs.com/onlytiancai/archive/2007/10/14/924075.html
这是控制台代码,愿意是想加上一个tcp分析类和http分析类,然后可以实现http头和内容的过滤。
using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
namespace WawaSoft.WawaCapturer
{
class Program
{
static void Main(string[] args)
{
RawSocket socket = null;
try
{
socket = new RawSocket();
socket.CreateAndBindSocket(“192.168.1.100“);
if (socket.ErrorOccurred)
{
Console.WriteLine(“监听出错了“);
return;
}
socket.KeepRunning = true;
socket.PacketArrival += socket_PacketArrival;
socket.Run();
}
catch (Exception ex)
{
Console.WriteLine(ex);
throw;
}
finally
{
Console.Read();
socket.Shutdown();
}
}
static void socket_PacketArrival(object sender, RawSocket.PacketArrivedEventArgs args)
{
if (args.Protocol == “TCP“ && (args.OriginationPort == “80“ || args.DestinationPort == “80“))
{
Console.WriteLine(args);
byte[] httpData = new byte[args.MessageLength–20];
Buffer.BlockCopy(args.MessageBuffer, 20, httpData, 0, httpData.Length);
using (BinaryWriter bw = new BinaryWriter(File.Open(string.Format(“{0}.cap“,
DateTime.Now.ToString(“yyyyMMdd hh“)),
FileMode.OpenOrCreate)))
{
bw.Write(DateTime.Now.ToString());
bw.Write(httpData, 0, httpData.Length);
bw.Write(new byte[] { 0, 0, 0, 0 }, 0, 4); //结束
}
return;
//System.IO.MemoryStream s = new System.IO.MemoryStream(httpData);
//System.IO.BinaryReader r = new System.IO.BinaryReader(s);
//byte temp = new byte();
//byte temp1;
//int i;
//bool find = false;
//for (i = 0; i < s.Length; i++)
//{
// temp1 = r.ReadByte();
// if (temp == 0x0d && temp1 == 0x0a)
// {
// find = true;
// break;
// }
// temp = temp1;
//}
//if (!find) return;
//byte[] httpHeaderData = new byte[httpData.Length – i -1];
//Buffer.BlockCopy(httpData, i, httpHeaderData, 0, httpHeaderData.Length);
//Console.WriteLine(“http头内容如下”r”n{0}”,Encoding.UTF8.GetString(httpHeaderData));
//byte[] body = new byte[httpData.Length – httpHeaderData.Length];
//Buffer.BlockCopy(httpData, httpHeaderData.Length, body, 0, body.Length);
//Console.WriteLine(“http内容如下”r”n{0}”, Encoding.UTF8.GetString(body));
}
}
}
}
简单说明:ip的头是20个字节,在RawSocket里已经提取出来了,tcp的头也是20字节,我在console里也取了出来,但没有分析(要分析的话看相关链接的第二篇文章),就是说args.MessageBuffer的20字节以后就是http协议的内容了(假如是抓的http的包),然后http的内容基本上是文本的(当然http也传输图片啥的,取决于contenttype头),在第一个回车换行(”r”n,也就是0x0d,0x0a)前面是http头部分,我们根据这个标志位可以取出http头,然后”r”n下面就是http的正文消息了,我们根据http头的ContentType来决定把正文的数据解析成一个图片还是一段html。如果我们只抓取html正文里包含”蛙蛙池塘”的数据包,我们就可以用响应编码类的getstring方法获取html正文字符串后用indexof方法来判断是否抓取该包。
以上纯属个人想法,结果编码的时候死活编不出来,问题如下,请高手指教。
1、我用Encoding.UTF8.GetString所有http的数据,结果中文死活出不来,我把byte[]保存到硬盘上用UE打开,转换编码,还是不能识别中文,我就抓的google的包,铁定是utf-8编码的,我就纳闷了,怎么能取出中文的数据呀。
2、每次触发PacketArrival事件,是不是有可能是收到半截儿的数据呀,比方是tcp的一次传输数据大于MTU的1500的值,一个数据传输了5次,会不会触发5次PacketArrival事件呀。难道还得自己根据ip协议的序号来拼合多次接受的数据不成?
3、在一个byte[]里怎么查找”r”n的值呀,我的算法是用一个循环加两个临时变量做的,貌似复杂了点儿,而且算法根本就不对好像,用Array.IndexOf<T>()方法和Array.FindAll方法貌似也不行,大家看看怎么弄比较好。
最后我就想着,做成一个命令行工具,参数里可以设置要抓取包的协议,来源地址,目的地址,来源端口,目的端口,数据里包含的字符串,有这几个就够了,因为我们一般在服务器出错,或者怀疑有人攻击服务器的时候会抓取某些包含特殊攻击字符或者来源于某些特定访问者(不一定是IP,IP的话用windump的规则就可以指定,而可能是一个http头或者cookie标识的一个应用层的用户)的数据包,要把所有包都抓回来,分析起来会很费力。
有了这个小工具,会很方便做网络应用的朋友,大家谁有兴趣,一起完善哦,哥们是积穷了,弄了三四个小时了还没弄好
相关链接:
用协议分析工具学习TCP/IP
http://www.cnpaf.net/class/OtherAnalysis/0532918532942694.html
TCP/IP协议数据报结构详解
http://www.itvue.com/Article/CISCO/CIROUTE/200607/1683.html
Raw Socket Capturing Using C#
http://www.codeproject.com/csharp/pktcap.asp
声明:类库是我从一篇文章里整理出来的,版权规原作者所有,但实在找不出出处了。