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 在评论中指出了这一点。


