您的位置:首页 > 理论基础 > 计算机网络

基于winpcap的网络mac地址发现

2015-11-20 23:30 645 查看
arp数据包格式:



主要文件有:

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;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: