65.9K
CodeProject 正在变化。 阅读更多。
Home

C# 中的网络嗅探器

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.87/5 (74投票s)

2007年1月5日

CPOL

2分钟阅读

viewsIcon

902702

downloadIcon

48367

一个简单的网络嗅探器,可以解析 IP、TCP、UDP 和 DNS 数据包。

Sample Image - CSNetworkSniffer.jpg

引言

在本文中,我将讨论一个简单的网络嗅探器的工作原理,它可以解析 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 在评论中指出了这一点。
© . All rights reserved.