Socket嗅探

来源:互联网 发布:程序员电脑壁纸1080 编辑:程序博客网 时间:2024/05/16 06:24


Socket嗅探


上面几篇博文介绍了基础支持至此,在我们的代码示例中已经使用过流套接字了。使用流套接字发送和接收时,数据被简洁地包装在一个TCP/IP连接中。访问OSI模型的会话层(5),操作系统负责所有传输过程中的低级细节、纠错和路由。使用原始套接字可以在较低层对网络进行访问。在网络的较低层,程序员必须处理所有暴露出来的细节。通过使用类型SOCKRAW即可指定原始套接字。这种情况下协议很重要, 因为有多种选择。协议可以是IPPROTO_TCP、IPPROTO_UDP或IPPROTO_ICMP。

下面的例子是一个使用原始套接字的窃听程序。

raw_tcpsniff.c

#include<stdio.h>

#include<stdlib.h>

#include<string.h>

#include<sys/socket.h>

#include<netinet/in.h>

#include<arpa/inet.h>

#include"hacking.h"

int main(void){

  int i, recv_length, sockfd;

  u_char buffer[9000];

  if ((sockfd = socket(PF_INET, SOCK_RAW,IPPROTO_TCP)) == -1)

     fatal("in socket");

  for(i=0; i < 3; i++) {

     recv_length = recv(sockfd, buffer, 8000,0);

     printf("Got a %d bytepacket\n", recv_length);

     dump(buffer, recv_length);

  }

}

这个程序打开了一个原始TCP套接字并且侦听三个数据包使用dump()函数打印每个包的原始数据。注意缓存被声明为u_char变量,它只是为了方便对sys/socket.h中unsignedchar!类型的扩展定义。这只是为了方便,因为unsigned变量在网络程序设计中使用得非常多,每次都输入unsigned会很麻烦。

编译时,程序需要以root运行,因为使用原始套接字需要root访问权限。下面的输出显示了我们向simple服务器发送示例文本信息时程序对网络的窃听。

[root@localhost booksrc]# ./raw_tcpsniff

Got a 44 byte packet

45 00 00 2c 05 8a 00 00 80 06 11 df cb d0 27 68 |E..,..........'h

c0 a8 6f 82 00 50 eb e4 5d a6 2d 16 fe c5 9c 6b |..o..P..].-....k

60 12 fa f0 67 9f 00 00 02 04 05 b4            | `...g.......

Got a 40 byte packet

45 00 00 28 05 8b 00 00 80 06 11 e2 cb d0 27 68 |E..(..........'h

c0 a8 6f 82 00 50 eb e4 5d a6 2d 17 fe c5 9e 20 |..o..P..].-....

50 10 fa f0 7d a7 00 00                        | P...}...

Got a 174 byte packet

45 00 00 ae 05 8c 00 00 80 06 11 5b cb d0 27 68 |E..........[..'h

c0 a8 6f 82 00 50 eb e4 5d a6 2d 17 fe c5 9e 20 |..o..P..].-....

50 18 fa f0 38 6c 00 00 48 54 54 50 2f 31 2e 31 |P...8l..HTTP/1.1

20 33 30 34 20 4e 6f 74 20 4d 6f 64 69 66 69 65 | 304 Not Modifie

64 0d 0a 58 2d 43 6f 6e 74 65 6e 74 2d 54 79 70 |d..X-Content-Typ

65 2d 4f 70 74 69 6f 6e 73 3a 20 6e 6f 73 6e 69 |e-Options: nosni

66 66 0d 0a 44 61 74 65 3a 20 4d 6f 6e 2c 20 30 |ff..Date: Mon, 0

37 20 46 65 62 20 32 30 31 31 20 30 32 3a 30 37 | 7 Feb2011 02:07

3a 30 33 20 47 4d 54 0d 0a 53 65 72 76 65 72 3a | :03GMT..Server:

20 73 66 66 65 0d 0a 58 2d 58 53 53 2d 50 72 6f | sffe..X-XSS-Pro

74 65 63 74 69 6f 6e 3a 20 30 0d 0a 0d 0a      | tection: 0....

虽然这个程序会捕获数据包,但它并不可靠,会丢失一些数据包,尤其是通信量较大时。而且它只捕获TCP数据包,要想捕获UDP或ICMP数据包,需要为这两种协议打开额外的原始套接字。原始套接字的另一个大问题是,不同的系统之间的兼容性非常差。用于Linux的原始套接字很可能在BSD或Solaris系统中不能正常工作。这使得使用原始套接字设计多平台程序几乎不可能。

一、 Libpcap窃听

一个名为libpcap的标准程序设计库可用于消除原始套接字的不兼容性。这个库中的函数仍然使用原始套接字实现它们的魔法,但库知道如何在多种系统架构上使原始套接字正确工作。Tcpdump和dsniff都使用libpcap,这使得它们在所有系统平台上编译起来都比较容易。让我们使用libpcap的函数代替我们自己的函数来重写原始数据包窃听程序。这些函数的含义很直观,因此我们将使用下面列出的代码对它们进行讨论。

Pcap_sniff.c

#include<pcap.h>

#include"hacking.h"

 

voidpcap_fatal(const char *failed_in, const char *errbuf) {

   printf("Fatal Error in %s: %s\n",failed_in, errbuf);

   exit(1);

}

首先,代码包含的pcap.h程序可以提供函数pcap使用的各种结构和定义。此外,还编写了一个pcap_fatal()函数来显示致命错误。函数pcap使用一个错误缓存区返回错误和状态信息,因此这个函数设计用来向用户显示这个缓存区中的内容。

int main() {

   struct pcap_pkthdr header;

   const u_char *packet;

   char errbuf[PCAP_ERRBUF_SIZE];

   char *device;

   pcap_t *pcap_handle;

   int i;

变量errbuf就是上面提到的错误缓存它的大小为256字节pcap.h中定义。变量header是一个pcap_pkthdr结构,它包含了有关数据包的额外的捕获信息,例如数据包何时被捕获以及它的长度。指针pcap_handle的工作方式类似于文件描述符,但它用来引用一个packet-capturing对象。
device =pcap_lookupdev(errbuf);

   if(device == NULL)

       pcap_fatal("pcap_lookupdev",errbuf);

 

   printf("Sniffing on device %s\n",device);

函数pcap_lookupdev()用于查找一个适合窃听的设备。这个设备作为一个引用静态函数内存的字符串指针返回。虽然BSD在系统中的值将会不同,但对于我们的系统来说,它始终是/dev/eth0。如果函数不能找到一个合适的设备,会返回NULL

pcap_handle =pcap_open_live(device, 4096, 1, 0, errbuf);

   if(pcap_handle == NULL)

       pcap_fatal("pcap_open_live",errbuf);

与套接字函数和文件打开函数类似,函数pcap_open_live()打开一个packet-capturing设备,并返回它的一个句柄。这个函数的参数是被窃听的设备、数据包的最大尺寸、一个混杂标志、一个超时值和一个指向错误缓存的指针。因为我们想以混杂模式捕获数据,所以将混杂标志设置为1

for(i=0; i< 3; i++) {

       packet = pcap_next(pcap_handle,&header);

       printf("Got a %d bytepacket\n", header.len);

       dump(packet, header.len);

   }

   pcap_close(pcap_handle);

}

数据包捕获循环使用pcap_next()抓取下一个数据包。向这个函数传递的参数是pcap_handle和一个指向pcap_pkthdr结构的指针,这样函数可以用捕获到的详细信息来填充这个结构。函数返回一个指向数据包的指针,然后打印数据包,并从捕获的报头中获得数据长度。最后pcap_close()关闭捕获接口。

编译该程序时,必须链接pc印库。可以使用带有-1标记的GCC来链接库,如下面的输出中所示。由于已经将pcap库安装到系统中,所以库和包含文件已经位于编译程序已知的标准位置。


注意在示例文本之前有许多字节,并且它们中的许多字节相似。因为捕获的这些是原始数据包,所以这些字节中的大部分是以太网、IPTCP的报头信息。

二、 对层进行解码

在数据包捕获中,最外层是以太网,它也是最低的可见层。该层用于在具有MAC地址的以太网终端之间发送数据。这个层的报头包含源MAC地址、目的MAC地址和一个用于说明以太网数据包类型的16位数值。在Linux中,用于这个报头的结构在/usr/include/Linux/if_ethernet.h中定义,用于IP报头和TCP报头的结构分别在/usr/include/netinet/ip.h/usr/include/netinet/tcp.h中定义。tcpdump的源代码也有用于这些报头的结构,或者我们可以以RFC为基础创建我们自己的报头结构。从编写我们自己的结构中可以获得更好的理解,因此我们以结构定义为引导来创建自己的数据包报头结构,并将它包含在hacking-network.h中。首先,让我们查看以太网报头的现有定义。

.

#defineETH_ALEN   6      /* Octets in one ethernet addr */

#defineETH_HLEN   14     /* Total octets in header.  */

#defineETH_ZLEN   60     /* Min. octets in frame sans FCS */

#defineETH_DATA_LEN   1500       /* Max. octets in payload  */

#define ETH_FRAME_LEN  1514       /* Max. octets in frame sans FCS */


/*

 * Thisis an Ethernet frame header.

 */


 

struct ethhdr{

   unsigned char  h_dest[ETH_ALEN];   /*destination eth addr */

   unsigned char  h_source[ETH_ALEN]; /* sourceether addr    */

   __be16     h_proto;        /* packet type ID field*/

}__attribute__((packed));

这个结构包含以太网报头的三个元素。经证实变量声明__be1616位无符号短整型的类型定义。可以通过在包含文件中递归查找类型定定义来确定它的真实类型。


包含文件还将以太网的长度定义为长度为14字节的ETH_HLEN。这是一个合计值,因为源和目的MAC地址各使用6个字节,数据包类型域是一个1 6位的短整型,它占据2个字节。但是,许多编译程序在填充结构时会以4字节为边界对齐,这意味着sizeof(struct ethhdr)会返回错误的数据大小。为了避免这种情况,对于以太网报头长度,应当使用ETH_HLEN或一个固定的14字节值。

通过包含<linux/if_ether.h>,那些包含必需的__be16类型定义的其他包含文件也被包含进来。因为我们想要使我们自己的结构用于hacking-network.h,所以我们应当剔除对未知类型定义的引用。我们定义时,可以给这些域取一个更好的名字。

添加到hacking-network.h中的代码

#defineETHER_ADDR_LEN 6

#defineETHER_HDR_LEN 14

structether_hdr {

  unsigned char ether_dest_addr[ETHER_ADDR_LEN];// Destination MAC address

  unsigned charether_src_addr[ETHER_ADDR_LEN]; //Source MAC address

  unsigned short ether_type; // Type ofEthernet packet

};


我们可以对IPTCP结构采用同样的措施,并参考相应的结构和RFC图表。

/usr/include/netinet/ip.h片段

struct iphdr

 {

#if__BYTE_ORDER == __LITTLE_ENDIAN

   unsigned int ihl:4;

   unsigned int version:4;

#elif__BYTE_ORDER == __BIG_ENDIAN

   unsigned int version:4;

   unsigned int ihl:4;

#else

# error"Please fix <bits/endian.h>"


#endif

   u_int8_t tos;

   u_int16_t tot_len;

   u_int16_t id;

   u_int16_t frag_off;

   u_int8_t ttl;

   u_int8_t protocol;

   u_int16_t check;

   u_int32_t saddr;

   u_int32_t daddr;

   /*The options start here. */

 };


RFC 791片断

0                      1                     2               3

0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 89 0 1 

+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

|Version |   IHL |    Type of Service  |    Total Length   |

+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

| Identification   |     Flags  |    Fragment Offset       | 

+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

| Time to Live     |      Protocol   |     Header Checksum  |

+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

|                          Source Address                     |

+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

|                          DestinationAddress                |

+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

|                 Options        |        Padding            |

+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

Example Internet Datagram Header


RFC报头图表中显示了结构中的每个元素对应的域。因为前两个域VersionIHLInternet报头长度)的大小只有4位,并且在C语言中没有4位大小的变量,所以Linux报头定义对字节的拆分是不同的,这取决于主机的字节顺序。这些域是以网络字节顺序排列的,所以,如果主机采用little-endian,则IHL会在Version之前,因为字节顺序是颠倒的。根据我们的意图,我们实际上不会使用这些域中的任何一个,所以不需要拆分字节。


添加到hacking-network.h中的代码


struct ip_hdr{

  unsigned char ip_version_and_header_length;// version and header length combined

  unsigned char ip_tos;         // type of service

  unsigned short ip_len;        // total length

  unsigned short ip_id;         // identification number

  unsigned short ip_frag_offset; // fragmentoffset and flags

  unsigned char ip_ttl;         // time to live

  unsigned char ip_type;        // protocol type

  unsigned short ip_checksum;   // checksum

  unsigned int ip_src_addr;     // source IP address

  unsigned int ip_dest_addr;    // destination IP address

};

如前所求,i通过填充结构奇的其余部编译程呈序填充会14字节为边界将结构对齐。IP报头始终是20字节。

对于TCP数据包报头,结构参考/usr/include/netinet/tcp.h,报头图表参考RFC793

/usr/include/netinet/tcp.h片断

typedefu_int32_t tcp_seq;

/*

 * TCP header.

 * Per RFC 793, September, 1981.

 */

struct tcphdr

 {

   u_int16_t th_sport;     /* source port */

   u_int16_t th_dport;     /* destination port */

   tcp_seq th_seq;     /* sequence number */

   tcp_seq th_ack;     /* acknowledgement number */

# if __BYTE_ORDER == __LITTLE_ENDIAN

   u_int8_t th_x2:4;       /* (unused) */

   u_int8_t th_off:4;      /* data offset */

# endif

# if __BYTE_ORDER == __BIG_ENDIAN

   u_int8_t th_off:4;      /* data offset */

   u_int8_t th_x2:4;       /* (unused) */

# endif

   u_int8_t th_flags;

# define TH_FIN0x01

# define TH_SYN0x02

# define TH_RST0x04

# define TH_PUSH  0x08

# define TH_ACK0x10

# define TH_URG0x20

   u_int16_t th_win;       /* window */

   u_int16_t th_sum;       /* checksum */

   u_int16_t th_urp;       /* urgent pointer */

};

RFC 793片断

TCP Header Format

0                1                      2                  3

0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 89 0 1

+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

|          SourcePort        |        Destination Port      |

+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 

|                       Sequence Number                       |

+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

|                    Acknowledgment Number                    |

+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

|   Data |            |U|A|P|R|S|F|                          |

| Offset|     Reserved |R|C|S|S|Y|I|             Window      |

|      |              |G|K|H|T|N|N|                          |

+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 

|            Checksum         |        Urgent Pointer        |

+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

|             Options         |          Padding             |

+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 

|                            data                             |

+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+


TCP Header Format

Note that one tick mark represents one bit position.

Data Offset: 4 bits

The number of 32 bit words in the TCP Header. Thisindicates where

RFC

the data begins. The TCP header (even one includingoptions) is an

integral number of 32 bits long.

Reserved: 6 bits

Reserved for future use. Must be zero.

Options: variable

根据主机字节顺序的不同,Linuxtcphdr结构也会交换4位数据偏移量域和保留域的4位部分之间的顺序。数据偏移量域很重要,因为它说明了TCP报头的可变长度的大小。您也许已经注意到Linuxtcphdr结构并不会为TCP选项保留任何空间。这是因为RFC将这个域定义为可选项。TCP报头的大小始终是32位对齐的,数据偏移量告诉我们报头中有多少个32位字。因此TCP报头的字节大小等于报头的数据偏移量域的4倍。因为计算报头大小需要数据偏移量域,所以我们会拆分包含这个域的字节,并假设主机字节顺序为little-endian

Linuxtcphdr结构的th_flags域被定义成一个8位无符号字符。这个域下面定义的数值是位掩码,它对应6个可能的标记。

struct tcp_hdr{

  unsigned short tcp_src_port;  // source TCP port

  unsigned short tcp_dest_port; // destination TCP port

  unsigned int tcp_seq;         // TCP sequence number

  unsigned int tcp_ack;         // TCP acknowledgement number

  unsigned char reserved:4;     // 4-bits from the 6-bits of reservedspace

  unsigned char tcp_offset:4;   // TCP data offset for little endian host

  unsigned char tcp_flags;      // TCP flags (and 2-bits from reservedspace)

#defineTCP_FIN  0x01

#defineTCP_SYN  0x02

#defineTCP_RST  0x04

#defineTCP_PUSH 0x08

#defineTCP_ACK  0x10

#defineTCP_URG  0x20

  unsigned short tcp_window;    // TCP window size

  unsigned short tcp_checksum;  // TCP checksum

  unsigned short tcp_urgent;    // TCP urgent pointer

};

现在,报头被定义成了结构,我们可以编写一个程序对每个数据包位于不同层的报头进行解码了。但是在开始之前,让我们先讨论一下libpcap。这个库有一个名为pcap_loop()的函数,它是一种比循环调用pcap_next()更好的捕获数据包的方法。实际上很少有程序使用pcap_next(),因为它使用不便并且效率很低。函数pcap_loop()使用了一个回调函数。这意味着向pcap_loop()函数传递的是一个函数指针,每次捕获一个数据包时都会调用这个函数。pcap_loop()的原型如下所示

intpcap_loop(pcap_t *handle,int count,pcap_handler callback,u_char *args);

1个参数是pcap的句柄,下一个是要捕获的数据包的个数,第3个是回调函数的函数指针。如果将count参数设置为-l,则它将一直循环到程序将其中断为止。最后的参数是一个可选的指针,它会被传递到回调函数。自然,回调函数需要遵循某种原型,因为pcap_loop()必须调用这个函数。可以随便命名回调函数,但其参数必须像下面这样:

void callback(u_char *args,const struct pcap_pkthdr*cap_header,const u_char*packet);

1个参数是可选的参数指针,它正好来自pcap_loop()的最后一个参数。可以用它向回调函数传递附加信息,但我们不会使用这个参数。接下来的两个参数应当在pcap_next()中就已经熟悉了:一个指向已捕获报头的指针和一个指向数据包本身的指针。

下面的示例代码使用pcap_loop()和一个回调函数捕获数据包,并且我们的报头结构用于对它们进行解码。在列出代码时,会对程序进行解释。

decode_sniff.c

#include<pcap.h>

#include"hacking.h"

#include"hacking-network.h"

voidpcap_fatal(const char *, const char *);

voiddecode_ethernet(const u_char *);

void decode_ip(constu_char *);

u_intdecode_tcp(const u_char *);

voidcaught_packet(u_char *, const struct pcap_pkthdr *, const u_char *);

int main() {

   struct pcap_pkthdr cap_header;

   const u_char *packet, *pkt_data;

   char errbuf[PCAP_ERRBUF_SIZE];

   char *device;

   pcap_t *pcap_handle;

   

   device = pcap_lookupdev(errbuf);

   if(device == NULL)

       pcap_fatal("pcap_lookupdev",errbuf);

   printf("Sniffing on device %s\n",device);

   

   pcap_handle = pcap_open_live(device, 4096,1, 0, errbuf);

   if(pcap_handle == NULL)

       pcap_fatal("pcap_open_live",errbuf);

   pcap_loop(pcap_handle, 3, caught_packet,NULL);

   pcap_close(pcap_handle);

}
在程序开始声明了名为caught_packet()的回调函数的原型和几个解码函数。除了for循环被单一pcap_loop()调用替换之外,main()中的其他所有代码在本质上与以前的代码相同。向函数传递的pcap_handle告诉它捕获三个数据包,并向它传递了回调函数caught_packet()的指针。最后的参数是NULL,因为并没有任何数据要传递给caught_packet()。还要注意decode_tcp()函数返回一个u_int类型的值。因为TCP报头长度是变量,所以这个函数返回TCP报头的长度。

voidcaught_packet(u_char *user_args, const struct pcap_pkthdr *cap_header, constu_char *packet) {

   int tcp_header_length, total_header_size,pkt_data_len;

   u_char *pkt_data;

   printf("==== Got a %d byte packet====\n", cap_header->len);

   decode_ethernet(packet);

   decode_ip(packet+ETHER_HDR_LEN);

   tcp_header_length =decode_tcp(packet+ETHER_HDR_LEN+sizeof(struct ip_hdr));

   total_header_size = ETHER_HDR_LEN+sizeof(structip_hdr)+tcp_header_length;

   pkt_data = (u_char *)packet +total_header_size; // pkt_data points tothe data portion

   pkt_data_len = cap_header->len -total_header_size;

   if(pkt_data_len > 0) {

       printf("\t\t\t%u bytes of packet data\n",pkt_data_len);

       dump(pkt_data, pkt_data_len);

   } else

       printf("\t\t\tNo PacketData\n");

}

voidpcap_fatal(const char *failed_in, const char *errbuf) {

   printf("Fatal Error in %s: %s\n",failed_in, errbuf);

   exit(1);

}

每当pcap_loop()捕获一个数据包时,会调用函数caught_packet()。这个函数利用报头长度按照层对数据包进行拆分,解码函数会打印出每层报头的详细信息。

voiddecode_ethernet(const u_char *header_start) {

   int i;

   const struct ether_hdr *ethernet_header;

 

   ethernet_header = (const struct ether_hdr*)header_start;

   printf("[[ Layer 2 :: Ethernet Header ]]\n");

   printf("[ Source: %02x",ethernet_header->ether_src_addr[0]);

   for(i=1; i < ETHER_ADDR_LEN; i++)

       printf(":%02x",ethernet_header->ether_src_addr[i]);

 

   printf("\tDest: %02x",ethernet_header->ether_dest_addr[0]);

   for(i=1; i < ETHER_ADDR_LEN; i++)

       printf(":%02x",ethernet_header->ether_dest_addr[i]);

   printf("\tType: %hu ]\n",ethernet_header->ether_type);

}

 

voiddecode_ip(const u_char *header_start) {

   const struct ip_hdr *ip_header;

 

   ip_header = (const struct ip_hdr*)header_start;

   printf("\t(( Layer 3 ::: IP Header  ))\n");

   printf("\t( Source: %s\t",inet_ntoa(ip_header->ip_src_addr));

   printf("Dest: %s )\n",inet_ntoa(ip_header->ip_dest_addr));

   printf("\t( Type: %u\t", (u_int)ip_header->ip_type);

   printf("ID: %hu\tLength: %hu )\n",ntohs(ip_header->ip_id), ntohs(ip_header->ip_len));

}

 

u_intdecode_tcp(const u_char *header_start) {

   u_int header_size;

   const struct tcp_hdr *tcp_header;

 

   tcp_header = (const struct tcp_hdr*)header_start;

   header_size = 4 * tcp_header->tcp_offset;

   

   printf("\t\t{{ Layer 4 :::: TCP Header }}\n");

   printf("\t\t{ Src Port: %hu\t",ntohs(tcp_header->tcp_src_port));

   printf("Dest Port: %hu }\n",ntohs(tcp_header->tcp_dest_port));

   printf("\t\t{ Seq #: %u\t",ntohl(tcp_header->tcp_seq));

   printf("Ack #: %u }\n",ntohl(tcp_header->tcp_ack));

   printf("\t\t{ Header Size: %u\tFlags:", header_size);

   if(tcp_header->tcp_flags & TCP_FIN)

       printf("FIN ");

   if(tcp_header->tcp_flags & TCP_SYN)

       printf("SYN ");

   if(tcp_header->tcp_flags & TCP_RST)

       printf("RST ");

   if(tcp_header->tcp_flags & TCP_PUSH)

       printf("PUSH ");

   if(tcp_header->tcp_flags & TCP_ACK)

       printf("ACK ");

   if(tcp_header->tcp_flags & TCP_URG)

       printf("URG ");

   printf(" }\n");

   return header_size;

}

向解码函数传递的是一个指向报头开始位置的指针,它会被强制转换成合适的类型。这样就允许访问报头的各个域,但重要的是要记住这些值是按网络字节顺序存储的。这些数据直接来自电缆,因此如果要在x86处理器上使用,需要转换字节顺序。

reader@hacking:~/booksrc $ gcc -o decode_sniffdecode_sniff.c -lpcap

reader@hacking:~/booksrc $ sudo ./decode_sniff     

Sniffing on device eth0

==== Got a 86 byte packet ====

[[ Layer 2 ::Ethernet Header ]]

[ Source: 00:0c:29:c3:2e:d7    Dest: 00:50:56:c0:00:08 Type: 8 ]

       (( Layer 3 ::: IP Header  ))

       ( Source:192.168.111.133      Dest: 192.168.111.1)

       ( Type:6      ID: 3020        Length: 72 )

               {{ Layer 4 :::: TCP Header }}

               {Src Port: 51407      Dest Port: 21 }

               {Seq #:3724734344    Ack #: 989611869 }

               {Header Size: 32      Flags: PUSHACK }

                       20bytes of packet data

55 53 45 52 20 61 64 6d 69 6e 69 73 74 72 61 74 | USERadministrat

6f 72 0d 0a                                    | or..

==== Got a 108 byte packet ====

[[ Layer 2 ::Ethernet Header ]]

[ Source: 00:50:56:c0:00:08    Dest: 00:0c:29:c3:2e:d7 Type: 8 ]

       (( Layer 3 ::: IP Header ))

       ( Source:192.168.111.1 Dest: 192.168.111.133 )

       ( Type:6      ID: 23707       Length: 94 )

               {{ Layer 4 :::: TCP Header }}

               {Src Port: 21 Dest Port: 51407 }

               { Seq #: 989611869     Ack #:3724734364 }

               {Header Size: 32      Flags: PUSHACK }

                       42 bytes of packet data

33 33 31 20 50 61 73 73 77 6f 72 64 20 72 65 71 | 331Password req

75 69 72 65 64 20 66 6f 72 20 61 64 6d 69 6e 69 | uiredfor admini

73 74 72 61 74 6f 72 2e 0d 0a                  | strator...

==== Got a 66 byte packet ====

[[ Layer 2 ::Ethernet Header ]]

[ Source: 00:0c:29:c3:2e:d7    Dest: 00:50:56:c0:00:08 Type: 8 ]

       (( Layer 3 ::: IP Header  ))

       ( Source:192.168.111.133      Dest: 192.168.111.1)

       ( Type:6      ID: 3021        Length: 52 )

               {{ Layer 4 :::: TCP Header }}

               {Src Port: 51407      Dest Port: 21 }

               {Seq #: 3724734364    Ack #: 989611911 }

               {Header Size: 32      Flags: ACK  }

                       No Packet Data



在对报头进行解码并将它们分离到不同层的过程中,会更容易理解TCP/IP连接。注意哪些IP地址与哪些MAC地址相关,还要注意为什么在来自192.168.111.133(第1个和最后1个数据包)的两个数据包中序号的增量为20,因为第1个数据包实际上包含9个字节的数据:3724734364-3724734344=20TCP协议就是这样来确定所有的数据是否是按顺序到达的,因为数据包可能会由于各种原因而延迟。


尽管在数据包报头中内置了各种机制,但数据包对于同网段上的所有人来说仍然可见。诸如FTP POP3Telnet之类的协议传输数据时是不加密的。即便没有像dsniff那样的工具协助,对于一个攻击者来说,窃听网络以便在数据包中发现用户名和密码并且使用它们危及其他系统的安全也是很容易的。从安全的角度来看,这样不是很好,因此更为智能化的交换设备提供了交换网络环境。




0 0
原创粉丝点击