基于winpcap的网络mac地址发现

arp数据包格式:

基于winpcap的网络mac地址发现_第1张图片



主要文件有:

datastruct.h   存储数据包格式的数据结构

transfunc.h   发送arp的封装函数声明

transfunc.cpp 定义

Winpcap_arp.cpp  main函数


datastruct.h

#ifndef HEADERSTRUCT_H
#define HEADERSTRUCT_H

// ip地址
typedef struct ip_address
{
	unsigned char byte1;
	unsigned char byte2;
	unsigned char byte3;
	unsigned char byte4;
}ip_address;

// ipv4首部
typedef struct ip_header
{
	unsigned char ver_ihl;  // 版本和首部长度各占4位,长度是4位数据乘以4,所以首部长度最长是60字节
	unsigned char tos;      // 区分服务
	unsigned short tlen;    // 总长度
	unsigned short identification;  // 标识
	unsigned short flags_fo;    // 标志 和 片偏移
	unsigned char ttl;          // 生存时间
	unsigned char proto;        // 协议
	unsigned short crc;         // 首部检验和
	ip_address saddr;           // 源地址
	ip_address daddr;           // 目的地址
	unsigned int op_pad;        // 可选字段 + 填充
}ip_header;

// udp首部
typedef struct udp_header
{
	unsigned short sport;
	unsigned short dport;
	unsigned short len;
	unsigned short crc;
}udp_header;

// arp数据包中的帧头
typedef struct ether_header
{   // 注意:类型字段需要转为网络字节序
	char etherdaddr[6];   // 以太网目的地址
	char ethersaddr[6];   // 以太网源地址
	unsigned short etherflametype;  // 以太网帧类型,0x0806是arp,0x0800是IP
}ether_header;

//
// arp 头部
#pragma pack(push, 1)
// c struct 编译层面字节对齐,编译器做,根据数据类型进行对齐,通过pack设置
typedef struct arp_header
{   // 注意:类型字段 和 操作码均需要转为网络字节序
	unsigned short hardtype;   // 硬件类型,如以太网为1
	unsigned short prototype;   // 协议类型,如ip 是0x0800
	unsigned char hardaddrlen;  // 硬件地址长度(6)
	unsigned char protoaddrlen; //协议地址长度,ip为4
	unsigned short operate;     // 操作字段,1为arp请求,2为arp应答
	char sendetheraddr[6]; // 发送端以太网地址
	unsigned long sendipaddr;       // 发送端ip地址
	char destetheraddr[6];    // 接收端以太网地址
	unsigned long destipaddr;       // 接收端ip地址
}arp_header/*__attribute__((aligned(1))) or __attribute__((pack)) 都是设置1字节对齐*/;
#pragma pack(pop)
//

// 值得注意的是,MSVN编译器编译结果中,两个struct并非按声明顺序在内存中存放,
// 而且,两个struct地址并不连续
typedef struct arp_packet
{
	struct ether_header etherheader;
	struct arp_header arpheader;
}arp_packet;



#endif // HEADERSTRUCT_H

transfunc.cpp

#include "stdafx.h"

#include "transfunc.h"


void arp_handler(unsigned char*param,                   // 对应pcap_loop / pcap_dispatch 的参数
	const struct pcap_pkthdr *header,      //winpcap 生成的一个头
	const unsigned char *pkt_data)         // 数据包
{   // 代解决:解析响应得arp 数据包
	struct arp_packet arp;
	memcpy(&arp.etherheader, pkt_data, 14);
	memcpy(&arp.arpheader, pkt_data + 14, 28);
	//struct ip_address *ipaddr = (struct ip_address *)(static_cast<void *>(&(arp->arpheader.sendipaddr)));

	unsigned char mac[6];
	for (int index = 0; index < 6; ++index)
	{
		mac[index] = arp.etherheader.ethersaddr[index];
	}
	unsigned long sendip = arp.arpheader.sendipaddr;
	struct ip_address *ipaddr = static_cast<struct ip_address *>((void *)&sendip);
	// 注意,这里会看到输出mac 为全0,因为我在BroadArp函数里广播了一个arp包,
	// 曾发送一个源mac为全零的arp,估计现在收到了

	// 后来正确了,那是arp系统自动发的包(不是我触发的),我抓取到了
	if (arp.etherheader.etherflametype == htons(0x0806))
		printf("ip: %d.%d.%d.%d -> MAC:%.2x-%.2x-%.2x-%.2x-%.2x-%.2x\n",
		ipaddr->byte1, ipaddr->byte2, ipaddr->byte3, ipaddr->byte4,
		mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);

}
// 问:为什么需要网络字节序?
void BroadcastArp(pcap_t *handle, pcap_if_t *dev)
{
	struct arp_packet arp;
	char mac[6];
	if (dev->addresses->addr->sa_family == AF_INET6)
		dev->addresses = dev->addresses->next;


	if (
		GetSelfMac(handle,
		inet_ntoa(((struct sockaddr_in *)(dev->addresses->addr))->sin_addr),
		mac)
		< 0)
	{
		fprintf(stderr, "GetSelfMac error\n");
		exit(-1);
	}

	bpf_u_int32 broadip = 0;

	//    inet_addr 得到的正数解析出的地址顺序相反,就是说传输中的Ip是按照反序(8bit为一段)存储的
	//    如192.168.1.107,则在整数的存储方式为 (((107*256 + 1)*256)+168)*256 + 192
	broadip = ((struct sockaddr_in *)(dev->addresses->broadaddr))->sin_addr.S_un.S_addr;
	//    printf("1.%u\n", broadip);


	//初始化以太网头部
	memset(arp.etherheader.etherdaddr, 0xff, 6); // 广播MAC地址

	strncpy(arp.etherheader.ethersaddr, mac, 6);
	arp.etherheader.etherflametype = htons(0x0806); // 0x8060是arp
	//初始化arp头, 请求时,arp_header里的mac地址可以是任意值,不受影响
	arp.arpheader.hardtype = htons(1);
	arp.arpheader.prototype = htons(0x0800); // 0x0800是ip
	arp.arpheader.hardaddrlen = 6;
	arp.arpheader.protoaddrlen = 4;
	arp.arpheader.operate = htons(1); // 1是arp请求,2是arp应答
	strncpy(arp.arpheader.sendetheraddr, arp.etherheader.ethersaddr, 6);
	// 任意值
	memset(arp.arpheader.destetheraddr, 0xff, 6);
	arp.arpheader.sendipaddr = ((struct sockaddr_in *)(dev->addresses->addr))->sin_addr.S_un.S_addr;
	// 这里broadip 是255.255.255.255(全网广播IP来的),用wireshark发现提示,谁是255.255.255.255 ,请告诉上面设置的sendipaddr
	arp.arpheader.destipaddr = broadip;
	// 发送arp数据包

	unsigned char *buf = (unsigned char *)malloc(42);
	memset(buf, 0, 42);
	memcpy(buf, &(arp.etherheader), sizeof(arp.etherheader));
	memcpy(buf + sizeof(arp.etherheader), &(arp.arpheader), sizeof(arp.arpheader));
	// fatal bad memory block!!!
	if (pcap_sendpacket(handle, buf, 42) < 0)
	{
		fprintf(stderr, "pacap_sendpacket error\n");
		exit(-1);
	}
	free(buf);
}
int GetSelfMac(pcap_t *adhandle, const char *ip_addr, char *ip_mac)
{
	// 
	unsigned char sendbuf[42]; //arp包结构大小
	int i = -1;
	int res;
	ether_header eh; //以太网帧头
	arp_header ah;  //ARP帧头
	struct pcap_pkthdr * pkt_header;
	const u_char * pkt_data;
	//将已开辟内存空间 eh.dest_mac_add 的首 6个字节的值设为值 0xff。
	memset(eh.etherdaddr, 0xff, 6); //目的地址为全为广播地址
	// 以以太网源地址为0发送arp, 接收端网卡接收到无端的arp,就发送一个包含自己mac地址的arp
	// 到”无端“的ip对应的主机,
	memset(eh.ethersaddr, 0x00, 6);

	// 当有源mac地址则是正式的arp请求
	// arpheader里的以太网地址没用
	memset(ah.destetheraddr, 0xff, 6);
	memset(ah.sendetheraddr, 0x00, 6);
	//htons将一个无符号短整型的主机数值转换为网络字节顺序
	eh.etherflametype = htons(0x0806);
	ah.hardtype = htons(0x0001);
	ah.prototype = htons(0x0800);
	ah.hardaddrlen = 6;
	ah.protoaddrlen = 4;
	ah.sendipaddr = inet_addr(ip_addr); //随便设的请求方ip
	ah.operate = htons(0x0001);
	// 如果是192.168.223.255, 则是提高这个地址是谁的,因为这是个广播地址
	// 是否什么错误?(vmnet8 的测试),xxx.1则是gratuitous(无端的)请求,来自xxx.1
	ah.destipaddr = inet_addr(ip_addr);
	printf("sizeof(eh) = %d \t sizeof(ah) = %d\n", sizeof(eh), sizeof(ah));
	memset(sendbuf, sizeof(sendbuf), 0);
	memcpy(sendbuf, &eh, sizeof(eh));
	memcpy(sendbuf + sizeof(eh), &ah, sizeof(ah));
	///	printf("%s", sendbuf);
	if (pcap_sendpacket(adhandle, sendbuf, 42) == 0) {
		printf("\nPacketSend succeed\n");
	}
	else {
		printf("PacketSendPacket in getmine Error: %d\n", GetLastError());
		return 0;
	}
	//从interface或离线记录文件获取一个报文
	//pcap_next_ex(pcap_t* p,struct pcap_pkthdr** pkt_header,const u_char** pkt_data)
	int count = 0;
	while ((res = pcap_next_ex(adhandle, &pkt_header, &pkt_data)) >= 0) {
		if (*(unsigned short *)(pkt_data + 12) == htons(0x0806)
			&& *(unsigned short*)(pkt_data + 20) == htons(0x0002)
			&& *(unsigned long*)(pkt_data + 28)
			== inet_addr(ip_addr)) {
			for (i = 0; i < 6; i++) {
				ip_mac[i] = *(unsigned char *)(pkt_data + 22 + i);
			}
			// 为什么会输出很多ffff
			printf("MAC:%2.2x.%2.2x.%2.2x.%2.2x.%2.2x.%2.2x\t", ip_mac[0], ip_mac[1], ip_mac[2], ip_mac[3], ip_mac[4],
				ip_mac[5]);
			printf("Get mac success !\n");
			break;
		}
	}
	if (i == 6) {
		return 1;
	}
	else {
		return -1;
	}
}


// 网上有这个方法,只是我调用时总是打开适配器的函数出错
int GetMacAddress(char* source, char* mac_buf)
{
	LPADAPTER lpAdapter;
	PPACKET_OID_DATA  OidData;
	BOOLEAN status;

	lpAdapter = PacketOpenAdapter(source);

	if (!lpAdapter || (lpAdapter->hFile == INVALID_HANDLE_VALUE))
	{
		printf("error : %d\n", GetLastError());
		return -1;
	}

	OidData = (PPACKET_OID_DATA)malloc(6 + sizeof(PACKET_OID_DATA));
	if (OidData == NULL)
	{
		return 0;
	}

	OidData->Oid = 0x01010102;//OID_802_3_CURRENT_ADDRESS;
	OidData->Length = 6;
	ZeroMemory(OidData->Data, 6);

	status = PacketRequest(lpAdapter, FALSE, OidData);
	if (!status)
	{
		return -1;
	}

	memcpy((void *)mac_buf, (void *)OidData->Data, 6);

	printf("The MAC address of the adapter is %.2x:%.2x:%.2x:%.2x:%.2x:%.2x\n",
		(PCHAR)(OidData->Data)[0],
		(PCHAR)(OidData->Data)[1],
		(PCHAR)(OidData->Data)[2],
		(PCHAR)(OidData->Data)[3],
		(PCHAR)(OidData->Data)[4],
		(PCHAR)(OidData->Data)[5]);

	free(OidData);
	PacketCloseAdapter((LPADAPTER)source);

	return 1;
}

char *ip6toa(struct sockaddr *sockaddr, char *address, int addrlen)
{
	socklen_t sockaddrlen;
#ifdef WIN32
	sockaddrlen = sizeof(struct sockaddr_in6);
#else
	sockaddrlen = sizeof(struct sockaddr_storage);
#endif
	if (getnameinfo(sockaddr, sockaddrlen, address, addrlen, NULL,
		0, NI_NUMERICHOST) != 0)
		address[0] = '\0';
	return address;
}


winpcap_arp.cpp

// Winpcap_arp.cpp : 定义控制台应用程序的入口点。
//

#include "stdafx.h"
// 为remote***.h 头文件
#ifndef HAVE_REMOTE
#define HAVE_REMOTE
#endif

#include "pcap.h"
#include "remote-ext.h"
#include <winsock.h>
#include <winsock2.h>
#include <ws2tcpip.h>
#include "datastruct.h"
#include "transfunc.h"

#pragma comment(lib, "wpcap.lib")
#pragma comment(lib, "Packet.lib")
#pragma comment(lib, "Ws2_32.lib")

int _tmain(int argc, _TCHAR* argv[])
{
	// 设备类型
	/*
	* struct pcap_if{
	* struct pcap_if *next;
	* char *name;
	* char *description;
	* struct pcap_addr *addresses;  设备地址结构
	* bpf_u_int32 flags;
	* };
	*/
	pcap_if_t *alldevs = NULL, *onedev = NULL;
	/*
	* struct pcap_addr{
	* struct pcap_addr *next;
	* struct sockaddr *addr;
	* ...              netmask, broadaddr, dstaddr;
	* };
	*/
	pcap_addr *devsaddr = NULL;
	char ip6str[128];
	char error[PCAP_ERRBUF_SIZE];
	char source[PCAP_BUF_SIZE] = "rpcap://";
	int index;

	// 获取所有网卡设备
	if (pcap_findalldevs_ex(PCAP_SRC_IF_STRING, NULL, &alldevs, error) < 0)
	{
		fprintf(stderr, "pcap_findalldevs error.\n");
		exit(-1);
	}
	if (alldevs == NULL)
	{
		printf("No device found\n");
		return 0;
	}
	// 遍历所有获取到的网卡设备
	for (onedev = alldevs, index = 1; onedev != NULL; onedev = onedev->next, ++index)
	{
		printf("(%d)%s", index, onedev->name);
		if (onedev->description)
			printf("description: %s\n", onedev->description);
		else
			printf("No description available\n");
		if (onedev->flags == PCAP_IF_LOOPBACK) 
			printf("Lookback device.\n");
		devsaddr = onedev->addresses;
		
		for (; devsaddr; devsaddr = devsaddr->next)
		{
			switch (devsaddr->addr->sa_family)
			{   // 1.待解决:解析本地地址等等 2.获取本机适配器mac 地址
			case AF_INET:
				printf("\tAddress family name : AF_INET\n");
				if (devsaddr->addr)
					printf("\tAddress : %s\n", inet_ntoa(
					((struct sockaddr_in *)(devsaddr->addr))->sin_addr));
				if (devsaddr->netmask)
					printf("\tAddress netmask : %s\n", inet_ntoa(
					((struct sockaddr_in *)(devsaddr->netmask))->sin_addr));
				if (devsaddr->broadaddr)
					printf("\tAddress broadcast : %s\n", inet_ntoa(
					((struct sockaddr_in *)(devsaddr->broadaddr))->sin_addr));
				if (devsaddr->dstaddr)
					printf("\tAddress destination : %s\n", inet_ntoa(
					((struct sockaddr_in *)(devsaddr->dstaddr))->sin_addr));
				break;
			case AF_INET6:
				printf("\tAddress family name : AF_INET6\n");
				if (devsaddr->addr)
				{
					//                    memset(ip6str, 0, sizeof(ip6str));
					//                    ip6toa(devsaddr->addr, ip6str, sizeof(ip6str));
					//                    printf("\tAddress : %s\n", ip6str);
				}
				break;
			default:
				printf("\tAddress family no found\n");
				break;

			}
		}
		printf(("-----------------------------------sperator-------------------------------------\n"));
	}

	// 选择监控的设备
reenter:
	printf("Enter which interface[1,%d] you want to scrap:", index - 1);
	int which, iindex;
	scanf("%d", &which);
	if (which < 1 || which > index - 1)
	{
		printf("enter error\n");
		goto reenter;
	}
	for (onedev = alldevs, iindex = 1; iindex <= index - 1; ++iindex, onedev = onedev->next)
	{
		if (iindex == which)
		{
			break;
		}
	}
	if (iindex == index)
	{
		fprintf(stderr, "Find device error\n");
		exit(-1);
	}
	pcap_t *capHandle = NULL;

	// 打开设备
	if ((capHandle = pcap_open_live(onedev->name,
		65536,   // 最大数据包长度
		1,  // PCAP_OPENFLAG_PROMISCUOUS,1为混杂模式
		1000,  // 超时时间,单位毫秒。注意:
		// pcap_loop 不会因为超时而返回,直到当cnt(pcap_loop第二个参数)
		// 个数据包被捕获后才返回,pcap_dispatch则因超时会返回。
		NULL   // error buf
		)) == NULL)
	{
		fprintf(stderr, "pcap_open error\n");
		pcap_freealldevs(alldevs);
		exit(-1);
	}

	// 返回链路层的类型,如以太网/wifi 。。等等不同的帧格式对应的类型
	if (pcap_datalink(capHandle) != DLT_EN10MB)
	{
		fprintf(stderr, "pcap_datalink error\n");
		pcap_freealldevs(alldevs);
		exit(-1);
	}

	// 问题:整形数据怎么和ip,掩码等等转换?
	struct bpf_program fcode;
	char packet_filter[] = "arp";   // or / and / not / src 192.168.1.x / 等等布尔表达式树

	bpf_u_int32 mask; //掩码

	if (onedev->addresses->addr->sa_family == AF_INET6)
		onedev->addresses = onedev->addresses->next;

	// 网络字节序是大端,一般电脑是小端字节序(针对long 和 short 等控制字段要注意字节序)
	if (onedev)
		mask = ((struct sockaddr_in *)(onedev->addresses->netmask))->sin_addr.S_un.S_addr;
	else
		mask = 0xffffff;

	// 将packet_filter 字符串表达式转换成过滤结构
	// int pcap_compile(pcap_t *p, struct bpf_program *fp,char *str, int optimize, bpf_u_int32 netmask)
	if (pcap_compile(capHandle, &fcode, packet_filter, 1, mask) < 0)
	{
		fprintf(stderr, "pcap_compile error\n");
		pcap_freealldevs(alldevs);
		exit(-1);
	}
	// 设置过滤
	if (pcap_setfilter(capHandle, &fcode) < 0)
	{
		fprintf(stderr, "pcap_setfilter error\n");
		pcap_freealldevs(alldevs);
		exit(-1);
	}

	// 广播arp数据包
	BroadcastArp(capHandle, onedev);


	printf("\nListening on %s ...\n", onedev->description);

	// pcap_breakloop , 设置标志,强制使pcap_loop , pcap_dispatch 返回,不继续循环
	// pcap_loop 不会因为超时而返回,直到当cnt个数据包被捕获后才返回,pcap_dispatch则因超时会返回。
	// 第二个参数为-1表示无限捕获
	pcap_loop(capHandle, -1, arp_handler, NULL);
	// pcap_next_ex 可用易于并发的读取帧

	//释放资源
	pcap_freealldevs(alldevs);

	return 0;
}





你可能感兴趣的:(基于winpcap的网络mac地址发现)