1.申请ID:win32的无君
2.个人邮箱: wujunxxw@126.com
3.本科大一学生,原创首发文章:使用winpcap遍历网络设备,并在链路层直接发送TCP/SYN包
winpcap是一个功能强大的API,它为我们提供了直接访问计算机网络底层的能力。最近因为某些特殊需求,要发送一些篡改过的TCP/SYN包,首先想到的就是使用winpcap来实现。现在我已经完成了这个项目,那就趁热打铁给大家分享一下我的经验与方法吧。
如果要完整地讲解winpcap,以一篇帖子的篇幅是远远不够的。下面只以最简洁扼要的方式,讲述实现这个功能(发包)所要明白的知识。
本文仅供学习研究使用,禁止用作非法用途,如有不妥之处请告之,本人会在第一时间修改删除。
1、winpcap环境的建立
winpcap官网地址:https://www.winpcap.org/
首先,我们要从官网上下载两个zip,分别是 winpcap的驱动程序 和 winpcap SDK。前者解压直接安装,后者包含了编译winpcap程序所必需的头文件、库文件和一些程序示例。
安装完驱动程序后,我们还得将 头文件 和 库文件 放置在IDE正确的路径,否则就include不到这些函数了。我使用的是VS2019,路径可以在项目->属性->VC++目录中找到。
双击“包含目录”条目,会给出这个目录在你计算机内的绝对路径。把sdk中的头文件拖动到这个路径下就可以啦。库文件同理。
注意:winpcap只支持32位程序。将
这个东西设置为x86,以避免可能出现的问题。
2、遍历网络设备,打开一个接口
在传输什么东西之前,winpcap必须使用某个网络设备的名称(一串最长为512的char字符串)来打开这个网络设备的接口。
这串字符当然不建议手动输入,我们可以使用pcap_findalldevs();来遍历系统上的所有网络设备的属性。
这个函数通过指针的方式,将系统上第一个网络设备的属性传递到一个名为pcap_if_t的结构里。
然后通过pcap_if_t->next这种调用,我们又可以找到下一个网络设备的pcap_if_t。这样就实现了设备的遍历。直到pcap_if_t->next为NULL时,那么它就是系统上最后一个设备了。
当遍历完成后,我们使用pcap_freealldevs();关掉winpcap对网络设备的遍历。
话不多说,我们直接上代码。
[C++] 纯文本查看 复制代码 struct DEVS_NAME
{
char szDevName[512];
};
int GetAllDevs(DEVS_NAME devsList[])
{ int nDevsNum = 0;
pcap_if_t* alldevs;
char errbuf[PCAP_ERRBUF_SIZE];
if (pcap_findalldevs(&alldevs, errbuf) == -1)
{
return -1;
printf("error in pcap_findalldevs_ex: %s\n", errbuf);
}
for (pcap_if_t* d = alldevs; d != NULL; d = d->next)
{
strcpy(devsList[nDevsNum].szDevName, d->name);
nDevsNum++;
}
pcap_freealldevs(alldevs);
return nDevsNum;
}
这个自定义函数能自动对系统上所有网络设备进行遍历,并将它们的名称通过指针输出到一个自定义结构中。返回值为系统上网络设备的数量。
然后我们可以使用pcap_open_live();函数打开某个设备。代码示例:
[C++] 纯文本查看 复制代码 char szError[PCAP_ERRBUF_SIZE];
pcap_t* handleD = pcap_open_live(szDevName, 65536, 1, 1000, szError);
pcap_open_live();接受5个参数,分别是设备名称、接口传输的最大字节数、是否处于混杂模式、超时时间(毫秒为单位),以及错误警告文本。
如果设备打开成功,这个函数将返回一个pcap_t句柄。否则返回NULL。
3、发送第一个TCP包
我们使用wireshark抓一个网站的TCP/SYN包,如图
我们先尝试用自己的代码把这个包原封不动地发出去。
将TCP包的全部原文以16进制的形式复制下来,然后粘贴到我们的程序里,使用pcap_sendpacket();这个函数发包。代码如下:
[C++] 纯文本查看 复制代码 byte packet[66] = {
0xb8,0xf8,0x83,0xbf,0xc5,0x7c,0x84,0x3a,
0x4b,0x6d,0xd3,0x88,0x08,0x00,0x45,0x00,
0x00,0x34,0x73,0xb4,0x40,0x00,0x80,0x06,
0x1e,0xfd,0xc0,0xa8,0x00,0x73,0x68,0x81,
0x3e,0x76,0x1a,0x1e,0x01,0xbb,0x84,0x93,
0x20,0x10,0x00,0x00,0x00,0x00,0x80,0x02,
0x20,0x00,0x26,0x87,0x00,0x00,0x02,0x04,
0x05,0xb4,0x01,0x03,0x03,0x02,0x01,0x01,
0x04,0x02 };
pcap_sendpacket(handleD, packet, sizeof(packet));
pcap_sendpacket();的三个参数分别为打开的设备的句柄、要发送的包、包的大小。
数据包将不经过任何加工,直接由链路层发送到路由器上。
用wireshark开始抓包,并运行这个程序。
我们看到程序已经发包成功了,并收到了这个网站的回复。
4、IP头、TCP头、两个校验和
由于篇幅所限,关于TCP/IP协议,本文不进行过多赘述。想要对它们深入了解的朋友可以在互联网上收集资料,或等我另起一篇文章对此进行详细解释。
总而言之,我们从计算机发出去的TCP包由“以太网报头”、“IP报头”、“TCP报头”三个部分组成。为了防止长途传输中可能的错误,“IP报头”、“TCP报头”都是要计算"校验和"的。既然我们要篡改TCP包中的数据,那么校验和也必须得重新计算了,否则网站那边的路由器一算,诶,校验和不对,这个包就直接被丢掉了。
校验和的算法:
1.把校验和字段置为0。
2.把整个报文分割成16bits(4Bytes)的小块。
3.将这些小块求和,得到一个16bits+的数。
4.将16bit以上的位加回16bit里面。
5.所得数求反码。
计算代码如下:
[C++] 纯文本查看 复制代码 unsigned short CheckSum(unsigned short packet[], int size)
{
unsigned long cksum = 0;
while (size > 1)
{
cksum += *packet++;
size -= sizeof(USHORT);
}
if (size)
{
cksum += *(UCHAR*)packet;
}
cksum = (cksum >> 16) + (cksum & 0xFFFF);
cksum += (cksum >> 16);
return (USHORT)(~cksum);
}
这个代码可以计算从输入的指针开始、到长度为size的数列的校验和。
(小部分代码摘录并修改自https://github.com/wangzhenshan3/SYN-Flood/blob/master/SYN-Flood.cpp,感谢原作者)
p.s:第一次写教程,就发在了这个大牛林立的论坛上,令我不胜惶恐。若有谬误之处,还请大神指出,本人将在第一时间修改。 |