C# 中的网络嗅探器






4.87/5 (74投票s)
一个简单的网络嗅探器,可以解析 IP、TCP、UDP 和 DNS 数据包。

引言
在本文中,我将讨论一个简单的网络嗅探器的工作原理,它可以解析 IP、TCP、UDP 和 DNS 数据包。
捕获数据包
// For sniffing the socket to capture the packets
// has to be a raw socket, with the address family
// being of type internetwork, and protocol being IP
mainSocket = newSocket(AddressFamily.InterNetwork, SocketType.Raw,
ProtocolType.IP);
// Bind the socket to the selected IP address
mainSocket.Bind(newIPEndPoint(IPAddress.Parse(cmbInterfaces.Text),0));
// Set the socket options
mainSocket.SetSocketOption(SocketOptionLevel.IP, //Applies only to IP packets
SocketOptionName.HeaderIncluded, //Set the include header
true); //option to true
byte[] byTrue = newbyte[4]{1, 0, 0, 0};
byte[] byOut = newbyte[4];
//Socket.IOControl is analogous to the WSAIoctl method of Winsock 2
mainSocket.IOControl(IOControlCode.ReceiveAll, //SIO_RCVALL of Winsock
byTrue, byOut);
//Start receiving the packets asynchronously
mainSocket.BeginReceive(byteData, 0, byteData.Length, SocketFlags.None,
newAsyncCallback(OnReceive), null);
为了捕获数据包,我们使用原始套接字并将其绑定到 IP 地址。 在设置套接字的适当选项后,我们调用其 IOControl
方法。 请注意,IOControl
类似于 Winsock2WSAIoctl
方法。 IOControlCode.ReceiveAll
表示捕获特定接口上的所有传入和传出数据包。
传递给 IOControl
的第二个参数,使用 IOControlCode.ReceiveAll
应该是 TRUE
,因此创建了一个数组 byTrue
并将其传递给它(感谢 Leonid Molochniy 的指正)。 接下来,我们异步开始接收所有数据包。
分析数据包
IP 数据报封装了 TCP 和 UDP 数据包。 这进一步包含由应用程序层协议(如 DNS、HTTP、FTP、SMTP、SIP 等)发送的数据。 因此,TCP 数据包以如下方式封装在 IP 数据报中
+-----------+------------+--------------------+
| IP header | TCP header | Data |
+-----------+------------+--------------------+
因此,我们需要做的第一件事是解析 IP 标头。 下面显示了精简版的 IP 标头类,注释描述了事件的发生情况。
public classIPHeader
{
//IP Header fields
private byte byVersionAndHeaderLength; // Eight bits for version and header
// length
private byte byDifferentiatedServices; // Eight bits for differentiated
// services
private ushort usTotalLength; // Sixteen bits for total length
private ushort usIdentification; // Sixteen bits for identification
private ushort usFlagsAndOffset; // Eight bits for flags and frag.
// offset
private byte byTTL; // Eight bits for TTL (Time To Live)
private byte byProtocol; // Eight bits for the underlying
// protocol
private short sChecksum; // Sixteen bits for checksum of the
// header
private uint uiSourceIPAddress; // Thirty two bit source IP Address
private uint uiDestinationIPAddress; // Thirty two bit destination IP Address
//End IP Header fields
private byte byHeaderLength; //Header length
private byte[] byIPData = new byte[4096]; //Data carried by the datagram
public IPHeader(byte[] byBuffer, int nReceived)
{
try
{
//Create MemoryStream out of the received bytes
MemoryStream memoryStream = newMemoryStream(byBuffer, 0, nReceived);
//Next we create a BinaryReader out of the MemoryStream
BinaryReader binaryReader = newBinaryReader(memoryStream);
//The first eight bits of the IP header contain the version and
//header length so we read them
byVersionAndHeaderLength = binaryReader.ReadByte();
//The next eight bits contain the Differentiated services
byDifferentiatedServices = binaryReader.ReadByte();
//Next eight bits hold the total length of the datagram
usTotalLength =
(ushort) IPAddress.NetworkToHostOrder(binaryReader.ReadInt16());
//Next sixteen have the identification bytes
usIdentification =
(ushort)IPAddress.NetworkToHostOrder(binaryReader.ReadInt16());
//Next sixteen bits contain the flags and fragmentation offset
usFlagsAndOffset =
(ushort)IPAddress.NetworkToHostOrder(binaryReader.ReadInt16());
//Next eight bits have the TTL value
byTTL = binaryReader.ReadByte();
//Next eight represent the protocol encapsulated in the datagram
byProtocol = binaryReader.ReadByte();
//Next sixteen bits contain the checksum of the header
sChecksum = IPAddress.NetworkToHostOrder(binaryReader.ReadInt16());
//Next thirty two bits have the source IP address
uiSourceIPAddress = (uint)(binaryReader.ReadInt32());
//Next thirty two hold the destination IP address
uiDestinationIPAddress = (uint)(binaryReader.ReadInt32());
//Now we calculate the header length
byHeaderLength = byVersionAndHeaderLength;
//The last four bits of the version and header length field contain the
//header length, we perform some simple binary arithmetic operations to
//extract them
byHeaderLength <<= 4;
byHeaderLength >>= 4;
//Multiply by four to get the exact header length
byHeaderLength *= 4;
//Copy the data carried by the datagram into another array so that
//according to the protocol being carried in the IP datagram
Array.Copy(byBuffer,
byHeaderLength, //start copying from the end of the header
byIPData, 0, usTotalLength - byHeaderLength);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message, "MJsniff", MessageBoxButtons.OK,
MessageBoxIcon.Error);
}
}
//Please see the attached codes for the properties…
}
首先,该类包含与 IP 标头字段对应的成员数据。 请参阅 RFC 791,以获取 IP 标头及其字段的详细说明。 类的构造函数接收收到的字节,并在收到的字节上创建一个 MemoryStream
,然后创建一个 BinaryReader
以逐字节从 MemoryStream
读取数据。 另外请注意,从网络接收到的数据采用大端格式,因此我们使用 IPAddress.NetworkToHostOrder
来纠正字节顺序。 这必须对所有非字节数据成员进行操作。
TCP 和 UDP 标头也以相同的方式解析,唯一的区别是它们从 IP 标头结束的位置读取。 作为解析应用程序层协议的示例,附带的项目还可以检测 DNS 数据包。
参考文献
更新
- 2008 年 2 月 9 日:修复了代码以捕获传入和传出数据包。 感谢 Darren_vms 在评论中指出了这一点。