C:基于WinPcap的网络抓包

WinPcap

WinPcap是Windows平台下访问网络数据链路层的开源库。WinPcap允许应用程序绕开网络协议栈来捕获与传递网络数据包,并具有额外的有用特性,包括内核层的数据包过滤、一个 网络统计引擎与支持远程数据包捕获。Winpcap提供了一个强大的编程接口,为win32应用程序提供访问网络底层的能力。

Wpcap.dll库

主要结构体

struct_pcap_if_t

1
2
3
4
5
6
7
Struct pcap_if{ 
pcap_if * next;
char * name;
char *description;
pcap_addr * addresses;
u_int flags;
}

网络设备结构,表示一个网络接口设备.
参数:
next:指向下一个元素的指针, 如果是NULL,表示链表结束。
name:winpacap为网络接口卡分配的名字,作为一个参数传递给pcap_open_live(),用户打开网卡。
description:适配器的描述addresses:指向接口的地址列表的第一个元素。
flags:标志是是否回送网卡

struct_pcap_addr

1
2
3
4
5
6
7
struct pcap_addr {
struct pcap_addr *next;
struct sockaddr *addr;
struct sockaddr *netmask;
struct sockaddr *broadaddr;
struct sockaddr *dstaddr;
}

表示接口地址。
参数:
next:指向下一个元素的指针;
addr:IP地址;
netmask:网络掩码;
broadaddr:广播地址;
dstaddr:P2P目的地址。

struct pcap_pkthdr

1
2
3
4
5
struct pcap_pkthdr{
struct timeval ts;
bpf_u_int32 caplen;
bpf_u_int32 len;
};

这个结构体是由pcap_loop自己填充的,用来取得一些关于数据包的信息所以,在callback函数当中只有第一个user指针是可以留给用户使用的,如果你想给callback传递自己参数,那就只能通过pcap_loop的最后一个参数user来实现。
参数:
ts:时间戳;
caplen:已捕获部分的长度;
len:该包的脱机长度。

struct sockaddr_in

1
2
3
4
5
6
struct sockaddr_in {
short sin_family;
u_short sin_port;
struct in_addr sin_addr;
char sin_zero[8];
};

接口地址的表示形式

struct in_addr

1
2
3
struct in_addr {
in_addr_t s_addr;
};

表示一个32位的IPv4地址。in_addr_t一般为32位的unsigned int,其字节顺序为网络字节序,即该无符号数采用大端字节序。其中每8位表示一个IP地址中的一个数值。

主要函数

int pcap_findalldevs_ex

1
2
3
4
5
6
int pcap_findalldevs_ex(
char * source,
struct pcap_rmtauth * auth,
pcap_if_t ** alldevs,
char * errbuff
)

功能:获得当前所有可用的网络设备(网卡)的列表,并且这个列表可以被pcap_open()打开。
返回值:成功返回0,alldevs返回设备列表,alldevs不会为NULL。否则返回-1,那就是说系统没有任何接口可以列举的。出错的消息在errbuf里面返回。
参数:
source: 指定从哪获取网络接口设备列表,例如”rpcap://”,表示本地适配器;
auth:保存连接到远程主机上授权信息,查询本机时为NULL;
alldevs:指向pcap_if_t结构的指针;
errbuff:存放错误信息。

pcap_t *pcap_open

1
2
3
4
5
6
7
8
pcap_t *pcap_open(
const char * source,
int snaplen,
int flags,
int read_timeout,
struct pcap_rmtauth * auth,
char * errbuf
)

功能:打开一个通用的源。
返回值:指向’pcap_t’的指针,可以用作以下调用(pcap_compile()等)的参数,并指定打开的WinPcap会话。如果出现问题,它返回NULL,’errbuf’变量保留错误消息。
参数:
source:包含要打开的源名称;
snplen:保留的包的长度;
flags:保留捕获数据包可能需要的几个标志;
read_timeout:以毫秒为单位读取超市;
auth:保存连接到远程主机上授权信息,查询本机时为NULL;
errbuf:指向用户分配的缓冲区的指针,该缓冲区将在该函数失败的情况下包含错误。

Pcap_sendpacket

1
2
3
4
5
Pcap_sendpacket(
pcap_t *p,
u_char * buf,
int size
)

功能:发送一个数据包。
返回值:返回值为0说明数据包已经成功的发送了,否则返回-1。
参数:
p:将要在它上面发送数据的适配器;
buf:一个包含将要发送的数据缓冲区;
size:缓冲区的长度。

void pcap_freealldevs

1
2
3
void pcap_freealldevs(
pcap_if_t *alldevsp
)

功能:由函数pcap_findalldevs_ex或pcap_findalldevs函数返回的网络适配器设备链表,必须调用pcap_freealldevs函数释放。

int pcap_loop

1
2
3
4
5
6
int pcap_loop(
pcap_t * p,
int cnt,
pcap_handler callback,
uchar * user
);

功能:捕获数据包,不会响应pcap_open_live()函数设置的超时时间
返回值:成功返回0,失败返回1;
参数:
p:是由pcap_open_live()返回的所打的网卡的指针;
cnt:用于设置所捕获数据包的个数;
pcap_handler:是与void packet_handler()使用的一个参数,即回调函数(回调函数就是一个通过函数指针调用的函数。)的名称; 回调函数原型为pcap_callback;
user:值一般为NULL。

pcap_callback

1
2
3
4
5
pcap_callback(
u_char* argument,
const struct pcap_pkthdr* packet_header,
const u_char* packet_content
)

功能:解析数据包
参数:
pcap_content:表示的捕获到的数据包的内容;
argument:是从函数pcap_loop()传递过来的。注意:这里的参数就是指 pcap_loop中的 *user 参数;
pcap_pkthdr:表示捕获到的数据包基本信息,包括时间,长度等信息。

计算机网络中的数据传递过程分析

了解计算机网络协议是本课题研究的基础,要实现发包、抓包的功能,必须先梳理清楚数据在网络中的传递。数据发送过程阐述如下:
在发送主机端,一个应用层报文被传送给传输层。传输层收到报文之后,在报文上附上附加信息,即所谓的传输层首部信息,该首部信息将被接收端的传输层使用。应用层报文和传输层首部信息一起构成了传输层报文段,传输层报文段因此封装了应用层报文。
传输层则向网络层传递该报文段,网络层增加了网络层首部信息,比如源和目的端系统的地址等,由此产生了网络层数据报。
该数据报接下来被传递给链路层,链路层增加它自己的链路层首部信息,创建了链路层帧。
所以,我们看到在每一层,一个分组都具有两种类型的字段:首部字段和有效载荷字段。而有效载荷即来自于上一层的分组。
简言之,发送端就是对应用层数据一层一层加头的过程,到接收端后,接收端再一层一层去掉头部信息,然后交给对应的应用程序。

程序设计

数据包结构体设计

ARP报文(网络层)

ARP(地址解析协议),是根据IP地址获取物理地址的一个TCP/IP协议,主要作用是通过IP地址来获取MAC地址。
本机向局域网内主机发送ARP包,ARP包内包含了目的IP,源IP,目的MAC,源MAC,其中目的MAC地址为广播地址,FF-FF-FF-FF-FF-FF,即向局域网内所有主机发送一个ARP请求,那么其他主机收到这个请求之后则会向请求来源返回一个数据包。在这个返回的数据包中包含了自身的MAC地址。那么本机收到这些返回的数据包进行解析之后便会得到局域网内所有主机的MAC地址。

构造ARP报文结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
//14字节以太网首部
struct EthernetHeader
{
u_char DestMAC[6]; //目的MAC地址 6字节
u_char SourMAC[6]; //源MAC地址 6字节
u_short EthType; //上一层协议类型,如0x0800代表上一层是IP协议,0x0806为arp 2字节
};
//28字节ARP帧结构
struct ArpHeader
{
unsigned short hdType; //硬件类型
unsigned short proType; //协议类型
unsigned char hdSize; //硬件地址长度
unsigned char proSize; //协议地址长度
unsigned short op; //操作类型,ARP请求(1),ARP应答(2),RARP请求(3),RARP应答(4)。
u_char smac[6]; //源MAC地址
u_char sip[4]; //源IP地址
u_char dmac[6]; //目的MAC地址
u_char dip[4]; //目的IP地址
};
//定义整个arp报文包,总长度42字节
struct ArpPacket {
EthernetHeader ed;
ArpHeader ah;
};

以太网数据报(数据链路层)

以太网是目前使用最广泛的局域网技术。以太网技术作为数据链路层的一种简单、高效的技术,以其为核心,与其它物理层技术相结合,形成以太网技术接入体系。

1
2
3
4
5
6
7
//以太网协议头
struct ether_header
{
u_int8_t ether_dhost[6]; //目的Mac地址
u_int8_t ether_shost[6]; //源Mac地址
u_int16_t ether_type; //协议类型
};

IP数据报(网络层)

IP协议提供不可靠无连接的数据报传输服务,IP层提供的服务是通过IP层对数据报的封装与拆封来实现的。IP数据报的格式分为报头区和数据区两大部分,其中报头区是为了正确传输高层数据而加的各种控制信息,数据区包括高层协议需要传输的数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
struct ip_header
{
u_int8_t ip_version:4,ip_header_length:4; //版本4 + 首部长度4
u_int8_t ip_tos; //服务类型
u_int16_t ip_length; //总长度
u_int16_t ip_id; //标识
u_int16_t ip_off; //偏移
u_int8_t ip_ttl; //生存时间
u_int8_t ip_protocol; //协议类型
u_int16_t ip_checksum;
struct in_addr ip_souce_address; //32位的IPv4地址,in_addr_t一般为32位的unsigned int,
struct in_addr ip_destination_address; //其字节顺序为网络字节序,即该无符号数采用大端字节序,其中每8位表示一个IP地址中的一个数值。
};

TCP协议(运输层)

TCP协议的数据报头的解析,其长度为20个字节。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
//TCP协议头
#define __LITTLE_ENDIAN_BITFIELD
//小端字节序
struct tcphdr
{
u_int16_t source_port; //源地址端口
u_int16_t dest_port; //目的地址端口
u_int32_t seq; //序列号
u_int32_t ack_seq; //确认序列号
#if defined(__LITTLE_ENDIAN_BITFIELD)
u_int16_t res1:4, //保留
doff:4, //偏移
fin:1, //关闭连接标志
syn:1, //请求连接标志
rst:1, //重置连接标志
psh:1, //接收方尽快将数据放到应用层标志
ack:1, //确认序号标志
urg:1, //紧急指针标志
ece:1, //拥塞标志位
cwr:1; //拥塞标志位
#elif defined(__BIG_ENDIAN_BITFIELD)
u_int16_t doff:4, //偏移
res1:4, //保留
cwr:1, //拥塞标志位
ece:1, //拥塞标志位
urg:1, //紧急指针标志
ack:1, //确认序号标志
psh:1, //接收方尽快将数据放到应用层标志
rst:1, //重置连接标志
syn:1, //请求连接标志
fin:1; //关闭连接标志
#else
u_int16_t flag;
#endif
u_int16_t window; //滑动窗口大小
u_int16_t check; //校验和
u_int16_t urg_ptr; //紧急字段指针
}

UDP协议(运输层)

1
2
3
4
5
6
7
8
//UDP协议头
struct udphdr
{
u_int16_t source_port; //源地址端口
u_int16_t dest_port; //目的地址端口
u_int16_t len; //UDP长度
u_int16_t check; //UDP校验和
}

函数子功能设计

打印获取的网络适配器信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
void ifprint(pcap_if_t *d)
{
pcap_addr_t *a;//网络适配器的地址用来存储变量
//打印网卡信息
printf("\n%d.\n\t%s\n",++i,d->name);
if(d->description)
{
////打印适配器的描述信息
printf("\tdescription:(%s)\n",d->description);
}else{
//适配器不存在描述信息
printf("\t(No description available)\n");
}
//打印本地环回地址
printf("\tLoopback: %s\n",(d->flags & PCAP_IF_LOOPBACK)?"yes":"no");
for (a=d->addresses;a != NULL;a=a->next)
{
//sa_family :是2字节的地址家族,一般都是“AF_xxx”的形式,
//它的值包括三种:AF_INET,AF_INET6和AF_UNSPEC。
switch (a->addr->sa_family)
{
case AF_INET:
//地址类型
printf("\tAddress Family Name:: AF_INET\n");
if(a->addr)
{
//Ipv4地址
printf("\tAddress:%s\n",iptos(((struct sockaddr_in *)a->addr)->sin_addr.s_addr));
}
if(a->netmask)
{
//子网掩码
printf("\tNetmask:%s\n",iptos(((struct sockaddr_in *)a->netmask)->sin_addr.s_addr));
}
if(a->broadaddr)
{
//广播地址
printf("\tBroadcast Address:%s\n",iptos(((struct sockaddr_in *)a->broadaddr)->sin_addr.s_addr));
}
if(a->dstaddr)
{
//目的地址
printf("\tDestination Address:%s\n",iptos(((struct sockaddr_in *)a->dstaddr)->sin_addr.s_addr));
}
break;
}
}
}

将数字类型的IP地址转换成点分十进制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
char * iptos(u_long in)
{
static char output[IPTOSBUFFERS][3*4+3+1];
static short which;//静态变量
u_char *p;
p = (u_char *)∈
if(which+1 == IPTOSBUFFERS)
{
which=0;
}else
{
which++;
}
//转换为点分十进制
sprintf(output[which],"%d.%d.%d.%d",p[0],p[1],p[2],p[3]);
return output[which];
}

以太网协议分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
//1.pcap_loop中的user参数;2.捕获到的基本信息;3.loop捕获到的数据包内容
void ethernet_protocol_packet_callback(u_char *argument,const struct pcap_pkthdr* packet_header,const u_char* packet_content)
{
u_short ethernet_type;
struct ether_header *ethernet_protocol;
u_char *mac_string;
static int packet_number = 1;
struct tm * ltime;
//时间戳处理
char timestr[16];
time_t local_tv_sec;
local_tv_sec= packet_header->ts.tv_sec;
ltime=localtime(&local_tv_sec);
strftime(timestr, sizeof timestr, "%H:%M:%S", ltime);
printf("\n\n");
printf("捕获第%d个网络数据包\n",packet_number);
printf("捕获时间:%s\n",timestr);
printf("数据包长度:%d\n",packet_header->len);
printf("---------以太网协议---------\n");
ethernet_protocol=(struct ether_header*)packet_content;//获得数据包内容
//othohs,作用是将一个16位数由网络字节顺序转换为主机字节顺序。
ethernet_type=ntohs(ethernet_protocol->ether_type);//获得以太网类型
printf("以太网类型:%04x\n",ethernet_type);
switch (ethernet_type)
{
case 0x0800: printf("上层协议是IPv4协议\n");break;
case 0x0806: printf("上层协议是ARP协议\n");break;
case 0x8035: printf("上层协议是RARP协议\n");break;
case 0x814C: printf("上层协议是简单网络管理协议SNMP\n");break;
case 0x8137: printf("上层协议是因特网包交换(IPX:Internet Packet Exchange)\n");break;
case 0x86DD: printf("上层协议是IPv6协议\n");break;
case 0x880B: printf("上层协议是点对点协议(PPP:Point-to-Point Protocol)\n");break;
default:break;
}
mac_string=ethernet_protocol->ether_shost;
printf("MAC帧源地址:%02x:%02x:%02x:%02x:%02x:%02x\n",*mac_string,*(mac_string+1),*(mac_string+2),*(mac_string+3),*(mac_string+4),*(mac_string+5));
mac_string=ethernet_protocol->ether_dhost;
printf("MAC帧目的地址:%02x:%02x:%02x:%02x:%02x:%02x\n",*mac_string,*(mac_string+1),*(mac_string+2),*(mac_string+3),*(mac_string+4),*(mac_string+5));
if(ethernet_type==0x0800)//继续分析IP协议
{
ip_protool_packet_callback (argument,packet_header,packet_content+sizeof(ether_header));
}
printf("\n");
packet_number++;
}

IP协议分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
void ip_protool_packet_callback(u_char *argument,const struct pcap_pkthdr* packet_header,const u_char* packet_content)
{
struct ip_header *ip_protocol;
u_int header_length=0;
u_int offset;
u_char tos;
u_int16_t checksum;
ip_protocol = (struct ip_header *)packet_content;
checksum =ntohs(ip_protocol->ip_checksum);
tos = ip_protocol->ip_tos;
offset = ntohs(ip_protocol->ip_off);
printf("---------IP协议---------\n");
printf("版本号:%d\n", ip_protocol->ip_version);
printf("首部长度:%d\n",header_length);
printf("服务质量:%d\n",tos);
printf("总长度:%d\n",ntohs(ip_protocol->ip_length));
printf("标识:%d\n",ntohs(ip_protocol->ip_id));
printf("偏移:%d\n",(offset & 0x1fff) * 8);
printf("生存时间:%d\n",ip_protocol->ip_ttl);
printf("协议类型:%d\n",ip_protocol->ip_protocol);
printf("检验和:%d\n",checksum);
printf("源IP地址:%s\n", inet_ntoa(ip_protocol->ip_souce_address));
printf("目的地址:%s\n", inet_ntoa(ip_protocol->ip_destination_address));
switch (ip_protocol->ip_protocol)
{
case 1: printf("上层协议是ICMP协议\n");break;
case 2: printf("上层协议是IGMP协议\n");break;
case 6:
{
//printf("上层协议是TCP协议\n");
tcp_protool_packet_callback (argument,packet_header,packet_content+sizeof(ip_header));
}
break;
case 17:
{
//printf("上层协议是UDP协议\n");
udp_protool_packet_callback (argument,packet_header,packet_content+sizeof(ip_header));
}
break;
default:break;
}
}

TCP协议分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
void tcp_protool_packet_callback(u_char *argument,const struct pcap_pkthdr* packet_header,const u_char* packet_content)
{
struct tcphdr *tcp_protocol;
u_int header_length=0;
u_int offset;
u_char tos;
u_int16_t checksum;
tcp_protocol = (struct tcphdr *) packet_content;
checksum =ntohs(tcp_protocol->check);
printf("---------TCP协议---------\n");
printf("源端口:%d\n", ntohs(tcp_protocol->source_port));
printf("目的端口:%d\n",ntohs(tcp_protocol->dest_port));
printf("SEQ:%d\n",ntohl(tcp_protocol->seq));
printf("ACK SEQ:%d\n",ntohl(tcp_protocol->ack_seq));
printf("check:%d\n",checksum);
if(ntohs(tcp_protocol->source_port)==80 || ntohs(tcp_protocol->dest_port)==80)
{
//http协议
printf("http data:\n%s\n",packet_content+sizeof(tcphdr));
}
}

UDP协议分析

1
2
3
4
5
6
7
8
9
10
11
12
13
void udp_protool_packet_callback(u_char *argument,const struct pcap_pkthdr* packet_header,const u_char* packet_content)
{
struct udphdr *udp_protocol;
u_int header_length=0;
u_int16_t checksum;
udp_protocol = (struct udphdr *) packet_content;
checksum =ntohs(udp_protocol->check);
printf("---------UDP协议---------\n");
printf("源端口:%d\n", udp_protocol->source_port);
printf("目的端口:%d\n",udp_protocol->dest_port);
printf("len:%d\n",udp_protocol->len);
printf("check:%d\n",checksum);
}

发送ARP数据包

发包流程简述图

捕获并分析数据包

抓包流程简述图