蛙蛙推荐:自己做个抓包工具(半成品)
用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
声明:类库是我从一篇文章里整理出来的,版权规原作者所有,但实在找不出出处了。