0%

应用层抓取网络数据包的实现(1)

简介

大多情况下,我们都是直接使用抓包工具,来抓取网络数据包分析,比如著名的开源软件wireshark,
那如何自己实现抓包功能,然后自动对数据包进行分析呢?wireshark是基于winpcap实现的,另外还有
一种简单的方法就是使用原始套接字。

原始套接字

在创建 socket 时,一般我们都是直接指定 SOCK_STREAMSOCK_DGRAM 类型,但是这两种是不展示IP头
信息的,微软还提供了一种 SOCK_RAW 类型,绑定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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
#include <winsock2.h>
#include <mstcpip.h>
#pragma comment(lib, "ws2_32.lib")

BOOL CapturePacket()
{
// 初始化socket模块
WSADATA wsaData = { 0 };
WSAStartup(MAKEWORD(2, 2), &wsaData);
// 创建socket
SOCKET Socket = socket(AF_INET, SOCK_RAW, IPPROTO_IP);
if (Socket == INVALID_SOCKET) return FALSE;
// 绑定socket
SOCKADDR_IN SockAddr = { 0 };
SockAddr.sin_family = AF_INET;
SockAddr.sin_addr.S_un.S_addr = inet_addr("10.10.10.10");
SockAddr.sin_port = 0; // 端口随意
int nRet = bind(Socket, (SOCKADDR*)&SockAddr, sizeof(SOCKADDR));
if (nRet == SOCKET_ERROR)
{
closesocket(Socket);
return FALSE;
}
// 设为监控IP级别
DWORD dwValue = RCVALL_IPLEVEL, dwRet = 0;
nRet = WSAIoctl(Socket, SIO_RCVALL,
&dwValue, sizeof(DWORD), NULL, 0, &dwRet, NULL, NULL);
if (nRet == SOCKET_ERROR)
{
closesocket(Socket);
return FALSE;
}
// 设为非阻塞状态只是为了限制5秒的时间
dwValue = 1;
nRet = ioctlsocket(Socket, FIONBIO, &dwValue);
if (nRet == SOCKET_ERROR)
{
closesocket(Socket);
return FALSE;
}
// 循环抓取数据
int nLen = 0;
char szBuffer[512] = { 0 };
DWORD dwTick = GetTickCount(); // 获取开机后毫秒数
while ((GetTickCount() - dwTick) < 5000) // 5秒
{
Sleep(1);
memset(szBuffer, 0, 512); // 清空缓冲区
nLen = recv(Socket, szBuffer, 508, 0); // 接收包
if (nLen < 1) continue;
// ……
// 分析数据包的程序
// ……
}
closesocket(Socket);
return FALSE;
}

使用WINPCAP抓包

winpcap所包含的功能特别多,开发包中也提供了大量示例,这里不再详细的介绍原理,可自行搜索相关
文章。如下为根据 pcap_filter 例子所修改的一个简单抓包实现

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
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
#include <winsock2.h>
#include <iphlpapi.h>
#define HAVE_REMOTE
#include "pcap.h"
#pragma comment(lib, "ws2_32.lib")
#pragma comment(lib, "iphlpapi.lib")
#pragma comment(lib, "wpcap.lib")

// 假设只有1个有效的网卡
char g_szSubMask[20] = { 0 };
char g_szAdapter[40] = { 0 };

BOOL GetCardAdapter()
{
// 第一次查询需要的空间
ULONG size = 0;
ULONG ret = GetAdaptersInfo(NULL, &size);
if (ret != ERROR_BUFFER_OVERFLOW) return FALSE;
PIP_ADAPTER_INFO iai = (PIP_ADAPTER_INFO)malloc(size);
if (iai == NULL) return FALSE;
memset(iai, 0, size);
// 第二次获取实际的信息
ret = GetAdaptersInfo(iai, &size);
if (ret != ERROR_SUCCESS)
{
free(iai);
return FALSE;
}
// 查询所有网卡信息
PIP_ADAPTER_INFO iaitemp = iai;
while (iaitemp != NULL)
{
// 网卡IP不为0.0.0.0时
if (strcmp(iaitemp->IpAddressList.IpAddress.String, "0.0.0.0") != 0)
{
strcpy_s(g_szSubMask, 20, iaitemp->IpAddressList.IpMask.String);
strcpy_s(g_szAdapter, 40, iaitemp->AdapterName);
free(iai);
return TRUE;
}
// 下一个网卡信息
iaitemp = iaitemp->Next;
}
free(iai);
return FALSE;
}

BOOL CapturePacket()
{
// 获取网卡适配器名称
if (!GetCardAdapter()) return FALSE;
bpf_program fcode = { 0 };
char szDevName[128] = { 0 };
char errbuf[PCAP_ERRBUF_SIZE] = { 0 };
// 例:"rpcap://\\Device\\NPF_{4B30BE7D-20EB-4EE4-87C0-DD180B5696F8}"
sprintf_s(szDevName, "rpcap://\\Device\\NPF_%s", g_szAdapter);
// 绑定网卡,注意此处errbuf不能设置为NULL
pcap_t *handle = pcap_open(szDevName, 65536, FALSE, 1000, NULL, errbuf);
if (handle == NULL) return FALSE;
// 转换子网掩码
unsigned int nMask = inet_addr(g_szSubMask); // 子网掩码
if (nMask == INADDR_ANY) nMask = INADDR_NONE;
// 编译过滤器
if (pcap_compile(handle, &fcode, "ip and tcp", 1, nMask) < 0)
{
pcap_close(handle);
return FALSE;
}
// 设置过滤器
if (pcap_setfilter(handle, &fcode) < 0)
{
pcap_close(handle);
return FALSE;
}
// 循环抓包
int nLen = 0;
pcap_pkthdr *head = NULL;
const u_char *pktdata = NULL;
DWORD dwTick = GetTickCount(); // 获取开机后毫秒数
while ((GetTickCount() - dwTick) < 5000) // 5秒
{
Sleep(1);
nLen = pcap_next_ex(handle, &head, &pktdata);
if (nLen < 1) continue;
// ……
// 分析数据包的程序
// ……
}
pcap_close(handle); // 关闭句柄
return FALSE;
}