通常我们只对特定网络通信感兴趣。比如我们只打算嗅探23端口(telnet)用于搜索密码信息,或者劫持发往21端口的文件 (FTP), 也可能是DNS通信(port 53 UDP)。无论哪种情形,我们很少会盲目地嗅探所有的网络通信。
在我们正式开始嗅探之前,我们应该要了解TCP/IP/UDP/ethernet 各种协议头部结构体。
太网帧结构
网络中传输数据时需要定义并遵循一些标准,以太网中大多数的数据帧使用的是Ethernet_II格式。下面来讲讲Ethernet_II数据帧中各字段说明:
1.Destination 是目的MAC地址。字段长度为6个字节,标识帧的接受者。
2.Source是源MAC地址。字段长度为6个字节,标识帧的发送者。
3.类型字段(Type)用于表示数据字段中包含的高层协议,该字段长度为2个字节。类型字段取值为0x0800的帧代表IP协议帧;
类型字段取值为0806的帧代表ARP协议帧(ARP是工作在链路层)。所以说,以太网帧中的Ethernet_II格式的Type可以标识是3层的协议,也可以标识为2层的协议。
以太网头部信息:
/* 以太网头信息 */
struct sniff_ethernet {
u_char ether_dhost[ETHER_ADDR_LEN]; /* 目标主机地址 */
u_char ether_shost[ETHER_ADDR_LEN]; /* 源主机地址 */
u_short ether_type; /* IP? ARP? RARP? 等等 */
};
IP协议头部格式
IP是TCP/IP协议簇中最为重要的协议。所有的TCP,UDP, ICMP和IGMP数据都以IP数据报格式传输。IP提供的是不可靠、无连接的协议。
Version(版本号):IP 协议版本号。目前只有两个版本:IPv4 和 IPv6
Header Length(IP 协议头部长度):IP 协议头部的长度,单位字节(32 bit)需要这个值是因为任选字段的长度是可变的, 这个字段占4bit(最多能表示15个32bit的的字,即4*15=60个字节的首部长度),因此IP 头部最多有60字节长度。正常的长度是20字节; 如果有额外的 IP 的 options 选项,还得加上 option 的长度。
Type of Service (服务类型):标示包传输优先级。总共8位,是由3个优先权位(不再使用),4个 TOS 位,1个固定的0组成。
4个 TOS 位:最新延迟、最大吞吐量、最高可靠性、最小成本,只能4选一。
Total Length(包长度):整个IP包的长度,16位,最大可以标示 65536个字节,Total Length - Header Length = 数据长度。通过 Header Length 和 Total Length 就可以知道数据的起始位置和结束位置。
Identifier(标识符):网络中转发的IP报文的长度可以不同,但如果报文长度超过了数据链路所支持的最大长度,则报文就需要分割成若干个小的片段才能在链路上传输。比如以太网帧中数据最大长度(MTU)为 1500字节,大于 MTU 的都会被分割,被分割的每个包都有相同的一个值,表示这是同一个 ip 包。
Flag(标志位): 标志字段在IP报头中占3位。
第1位作为保留;
第2位,分段,是否允许分片;(如果不允许分片,包超过了数据连路支持的最大长度,则丢弃该包,返回发送者一个 ICMP 错误)
第3位,更多分段。表示是否最后一个分片。
当目的主机接收到一个IP数据报时,会首先查看该数据报的标识符,并且检查标志位的第3位是置0或置1,以确定是否还有更多的分段。如果还有后续报文,接收主机则将接收到的报文放在缓存直到接收完所有具有相同标识符的数据报,然后再进行重组。
Fragmented Offset(偏移量): 当某个 IP 大包分成多片时,各个分片是不按顺序达到目的地的,IP 包根据分片的偏移量进行重组包。(跟TCP 原理一样)
(Time to Live)生存时间:表示数据包经过的路由器个数。如果网络上有些路由器的路由表配置不合理,路由寻址可能会导致死循环,数据包会一直循环传输。 IP 包发送的时候可以设置一个 TTL 值,比如 TTL=64,没经过一个路由器 TTL 减1,减到0 还没到到目的地,路由器会抛弃这个IP包,并使用一个ICMP消息通知发送方。
Protocal(协议): 协议类型 1:ICMP, 2:IGMP, 6:TCP, 17:UDP。
Header CheckSum(首部校验和):校验 IP 协议头,判断IP协议头是否正确传输。
Source Address(源IP): 请求方 IP
Distination Address(目的IP): 响应方 IP
Options(可选字段): IP支持很多可选选项。
IP头部信息结构体定义:
/* IP头部信息 */
struct sniff_ip {
u_char ip_vhl; /* 版本<< 4 | 标题长度>> 2 */
u_char ip_tos; /* 服务类型e */
u_short ip_len; /* 总长度 */
u_short ip_id; /* 标识符 */
u_short ip_off; /* 片段偏移字段*/
#define IP_RF 0x8000 /* 保留片段标志 */
#define IP_DF 0x4000 /* 不要片段标志 */
#define IP_MF 0x2000 /* 更多片段标志 */
#define IP_OFFMASK 0x1fff /* 碎片位掩码 */
u_char ip_ttl; /* 生存时间 */
u_char ip_p; /* 协议 */
u_short ip_sum; /* 校验和 */
struct in_addr ip_src,ip_dst; /* 源和目的地址 */
};
UDP协议头部
UDP协议基本上是 IP 协议与上层协议的接口。 与 TCP不同, UDP协议并不提供对 IP 协议的可靠机制、流控制以及错误恢复功能等。
Source Port — 16位。源端口是可选字段。当使用时,它表示发送程序的端口,同时它还被认为是没有其它信息的情况下需要被寻址的答复端口。如果不使用,设置值为0。
Destination Port — 16位。目标端口在特殊因特网目标地址的情况下具有意义。
Length — 16位。该用户数据报的八位长度,包括协议头和数据。长度最小值为8。
Checksum — 16位。IP 协议头、UDP协议头和数据位,最后用0填补的信息假协议头总和。如果必要的话,可以由两个八位复合而成。
Data — 包含上层数据信息。
UDP协议头结构体定义:
/* UDP协议头信息。 */
struct sniff_udp {
u_short uh_sport; /* 源端口 */
u_short uh_dport; /* 目的端口 */
u_short uh_ulen; /* udp长度 */
u_short uh_sum; /* udp校验和 */
};
TCP协议及帧格式
TCP(传输控制协议)是一种面向连接的、可靠的、基于字节流的传输层通信协议。而TCP的源端口、目的端口、以及IP层的源IP地址、目的IP地址四元组唯一的标识了一个TCP连接。
TCP源端口(Source Port):16位的源端口其中包含发送方应用程序对应的端口。源端口和源IP地址标示报文发送端的地址。
TCP目的端口(Destination port):16位的目的端口域定义传输的目的。这个端口指明报文接收计算机上的应用程序地址接口。
TCP序列号(SequenceNumber):32位的序列号标识了TCP报文中第一个byte在对应方向的传输中对应的字节序号。当SYN出现,SN=ISN(随机值)单位是byte。比如发送端发送的一个TCP包净荷(不包含TCP头)为12byte,SN为5,则发送端接着发送的下一个数据包的时候,SN应该设置为5+12=17。通过序列号,TCP接收端可以识别出重复接收到的TCP包,从而丢弃重复包,同时对于乱序数据包也可以依靠系列号进行重排序,进而对高层提供有序的数据流。另外如果接收的包中包含SYN或FIN标志位,逻辑上也占用1个byte,应答号需加1。
TCP应答号(Acknowledgment Number简称ACK Number):32位的ACK Number标识了报文发送端期望接收的字节序列。如果设置了ACK控制位,这个值表示一个准备接收的包的序列码,注意是准备接收的包,比如当前接收端接收到一个净荷为12byte的数据包,SN为5,则会回复一个确认收到的数据包,如果这个数据包之前的数据也都已经收到了,这个数据包中的ACK Number则设置为12+5=17,表示之前的数据都已经收到了,准备接受SN=17的数据包。
头长(Header Length):4位包括TCP头大小,指示TCP头的长度,即数据从何处开始。
保留(Reserved):4位值域,这些位必须是0。为了将来定义新的用途所保留,其中RFC3540将Reserved字段中的最后一位定义为Nonce标志。后续拥塞控制部分的讲解我们会简单介绍Nonce标志位。
标志(Code Bits):8位标志位。
窗口大小(Window Size):16位,该值指示了从Ack Number开始还愿意接收多少byte的数据量,也即用来表示当前接收端的接收窗还有多少剩余空间,用于TCP的流量控制。
校验位(Checksum):16位TCP头。发送端基于数据内容计算一个数值,接收端要与发送端数值结果完全一样,才能证明数据的有效性。接收端checksum校验失败的时候会直接丢掉这个数据包。CheckSum是根据伪头+TCP头+TCP数据三部分进行计算的。
优先指针(紧急,Urgent Pointer):16位,指向后面是优先数据的字节,在URG标志设置了时才有效。如果URG标志没有被设置,紧急域作为填充。
选项(Option):长度不定,但长度必须以是32bits的整数倍。常见的选项包括MSS、SACK、Timestamp等等。
TCP 头部结构体信息:
/* TCP 头部信息 */
typedef u_int tcp_seq;
struct sniff_tcp {
u_short th_sport; /* 源端口 */
u_short th_dport; /* 目的端口 */
tcp_seq th_seq; /* 序列号 */
tcp_seq th_ack; /* 确认号 */
u_char th_offx2; /* 数据偏移量 */
#define TH_OFF(th) (((th)->th_offx2 & 0xf0) >> 4)
u_char th_flags;
#define TH_FIN 0x01
#define TH_SYN 0x02
#define TH_RST 0x04
#define TH_PUSH 0x08
#define TH_ACK 0x10
#define TH_URG 0x20
#define TH_ECE 0x40
#define TH_CWR 0x80
#define TH_FLAGS (TH_FIN|TH_SYN|TH_RST|TH_ACK|TH_URG|TH_ECE|TH_CWR)
u_short th_win; /* 窗口 */
u_short th_sum; /* 校验和 */
u_short th_urp; /* 紧急指针 */
};
Dos-监控器
dos攻击,俗称拒绝服务攻击,通过发送大量的无用请求数据包给服务器,耗尽服务器资源,从而无法通过正常的访问服务器资源,导致服务器崩溃。
如果多个ip通过发起对一个服务器的攻击,如果无防御措施,不管服务器内存多大,宽带多宽,CPU多快,都无法抵御这种攻击。
#include <pcap.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
pcap_t * descr;
char *device;
/* 以太网头大小 */
#define SIZE_ETHERNET 14
/* 以太网地址为6个字节 */
#define ETHER_ADDR_LEN 6
/* 以太网头信息 */
struct sniff_ethernet {
u_char ether_dhost[ETHER_ADDR_LEN]; /* 目标主机地址 */
u_char ether_shost[ETHER_ADDR_LEN]; /* 源主机地址 */
u_short ether_type; /* IP? ARP? RARP? 等等 */
};
/* IP头部信息 */
struct sniff_ip {
u_char ip_vhl; /* 版本<< 4 | 标题长度>> 2 */
u_char ip_tos; /* 服务类型e */
u_short ip_len; /* 总长度 */
u_short ip_id; /* identification */
u_short ip_off; /* 片段偏移字段*/
#define IP_RF 0x8000 /* 保留片段标志 */
#define IP_DF 0x4000 /* 不要片段标志 */
#define IP_MF 0x2000 /* 更多片段标志 */
#define IP_OFFMASK 0x1fff /* 碎片位掩码 */
u_char ip_ttl; /* 生存时间 */
u_char ip_p; /* 协议 */
u_short ip_sum; /* 校验和 */
struct in_addr ip_src,ip_dst; /* 源和目的地址 */
};
#define IP_HL(ip) (((ip)->ip_vhl) & 0x0f)
#define IP_V(ip) (((ip)->ip_vhl) >> 4)
/* UDP协议标头。 */
struct sniff_udp {
u_short uh_sport; /* 源端口 */
u_short uh_dport; /* 目的端口 */
u_short uh_ulen; /* udp长度 */
u_short uh_sum; /* udp校验和 */
};
/* TCP 头部信息 */
typedef u_int tcp_seq;
struct sniff_tcp {
u_short th_sport; /* 源端口 */
u_short th_dport; /* 目的端口 */
tcp_seq th_seq; /* 序列号 */
tcp_seq th_ack; /* 确认号 */
u_char th_offx2; /* 数据偏移量 */
#define TH_OFF(th) (((th)->th_offx2 & 0xf0) >> 4)
u_char th_flags;
#define TH_FIN 0x01
#define TH_SYN 0x02
#define TH_RST 0x04
#define TH_PUSH 0x08
#define TH_ACK 0x10
#define TH_URG 0x20
#define TH_ECE 0x40
#define TH_CWR 0x80
#define TH_FLAGS (TH_FIN|TH_SYN|TH_RST|TH_ACK|TH_URG|TH_ECE|TH_CWR)
u_short th_win; /* 窗口 */
u_short th_sum; /* 校验和 */
u_short th_urp; /* 紧急指针 */
};
void processPacket(u_char *useless,const struct pcap_pkthdr *header,const u_char* packet) {
static int allpackets = 0;
static int monitoredpackets = 0;
allpackets++;
const struct sniff_ethernet *ethernet; /* 以太网头*/
const struct sniff_ip *ip; /* IP 头 */
const struct sniff_tcp *tcp; /* TCP 头 */
const struct sniff_udp *udp; /* UDP 头 */
int size_ip;
int size_tcp;
int size_udp;
/* 定义以太网头 */
ethernet = (struct sniff_ethernet*)(packet);
/* 定义/计算IP标头偏移 */
ip = (struct sniff_ip*)(packet + SIZE_ETHERNET);
size_ip = IP_HL(ip)*4;
char* protocol = "";
/* 确定协议 */
switch(ip->ip_p) {
case IPPROTO_TCP:
protocol = "TCP";
/* 定义/计算TCP标头偏移 */
tcp = (struct sniff_tcp*)(packet + SIZE_ETHERNET + size_ip);
size_tcp = TH_OFF(tcp)*4;
if (size_tcp < 20) {
printf(" * Invalid TCP header length: %u bytes\n", size_tcp);
return;
}
/* 检查TCP SYN数据包 */
if (tcp->th_flags & TH_SYN) {
monitoredpackets++;
/* 打印源和目标IP地址 */
printf("%s:%d -> ",
inet_ntoa(ip->ip_src),
ntohs(tcp->th_sport));
printf("%s:%d - %s (SYN) | %d monitored / %d total packets\n",
inet_ntoa(ip->ip_dst),
ntohs(tcp->th_dport),
protocol,
monitoredpackets,
allpackets);
}
break;
case IPPROTO_UDP:
protocol = "UDP";
udp = (struct sniff_udp*)(packet + SIZE_ETHERNET + size_ip);
if (ntohs(udp->uh_dport) == 53) {
monitoredpackets++;
/* 打印源和目标IP地址 */
printf("%s:%d -> ",
inet_ntoa(ip->ip_src),
ntohs(udp->uh_sport));
printf("%s:%d - %s (DNS) | %d monitored / %d total packets\n",
inet_ntoa(ip->ip_dst),
ntohs(udp->uh_dport),
protocol,
monitoredpackets,
allpackets);
}
break;
default:
protocol = "Unknown protocol";
break;
}
}
int main(int argc, char *argv[])
{
if(argc != 2)
{
printf("Usage : %s <network interface>\n",argv[0]);
return 1;
}
else
{
device = argv[1];
char errbuf[PCAP_ERRBUF_SIZE]; // 如果失败,则包含错误文本
memset(errbuf, 0, PCAP_ERRBUF_SIZE);
// 以混杂模式打开设备
descr=pcap_open_live(device, BUFSIZ, 1, 512, errbuf);
// 开始无限数据包处理循环
pcap_loop(descr, -1, processPacket, (u_char *) NULL);
// 关闭打开的设备的描述符
pcap_close(descr);
return 0;
}
}
编译运行:
打开一个终端,然后ping目标:
ping命令的参数也可以组合使用,比如执行“ping IP地址 –l 65500 –t”命令,就可以连续地向某一台主机发送最大数据包,这样就有可能使对方系统资源耗尽而死机或导致无法上网,这条命令就是历史上大名鼎鼎的“死亡之ping”,这里我就不能了哈。
运行观看:
该程序监视TCP SYN和DNS流量(端口53上的UDP),打印这些数据包的详细信息,并从总数据包中计数受监视的数据包。本程序不是类似企业的dos检测器。预防死亡之ping的最好方法是对操作系统打补丁。
总结
由于拒绝服务和分布式拒绝服务包含大量发送这些数据包,因此该程序可用于监视网络上的TCP SYN和DNS流量。本程序主要用于使用libpcap进行C语言编程,libpcap可以man pcap
参考帮助文档。
参考:TCP/IP详解 卷1
欢迎关注微信公众号【程序猿编码】,添加本人微信号(17865354792),回复:领取学习资料。或者回复:进入技术交流群。网盘资料有如下: