ARP (Address Resolution Protocol,地址解析协议),是一种用于将 IP 地址转换为物理地址(MAC地址)的协议。它在 TCP/IP 协议栈中处于链路层,为了在局域网中能够正确传输数据包而设计,由协议数据单元和对应的操作命令组成。ARP 既可以由操作系统处理,也可以由网卡处理。
该协议的作用是通过一个局域网上的互联网协议(IP)地址来查询对应的物理硬件地址,如数据包发送到路由器时,ARP 协议将使用嵌入在数据包中的目的 IP 地址查找对应的物理地址,路由器根据获取的 MAC 地址转发数据包到下一个网络。
协议工作过程如下:
ARP主机探测原理是通过发送 ARP 查询报文,来获取目标主机的 MAC 地址,进而获取目标主机的 IP 地址。
主机探测的具体实现步骤如下:
在Windows系统下,我们可以调用SendARP()函数实现ARP探测,该函数用于发送ARP请求到指定的 IP 地址,以获取其 MAC 地址。该函数参数传入目标 IP 地址时能够返回对应 MAC 地址。
SendARP 函数原型如下:
DWORD SendARP( IN IPAddr DestIP, // 目标 IP 地址 IN IPAddr SrcIP, // 源 IP 地址(可以为 0) OUT PULONG pMacAddr, // 接收目标 MAC 地址 IN OUT PULONG PhyAddrLen // 接收目标 MAC 地址的缓冲区大小,单位为字节);
该函数的第一个参数为目标IP地址,第二个参数为本地主机IP地址(可以填 0),第三个参数为接收返回的目标 MAC 地址的指针,第四个参数为指向缓冲区大小的指针。
当调用 SendARP() 函数时,如果目标 IP 地址是在同一物理网络中,则返回目标 IP 地址对应的 MAC 地址,并且函数返回值为 NO_ERROR。如果目标 IP 地址无效,或者无法获得对应的 MAC 地址,则函数返回值为错误代码,应该根据错误代码来进行处理。
如下代码实现了扫描局域网中指定ARP主机地址的功能。代码主要使用了SendARP()函数来查询目标主机的MAC地址,并将结果输出。具体实现步骤如下:
#include <stdio.h>#include <winsock2.h>#include <IPHlpApi.h>#pragma comment (lib,"ws2_32.lib") #pragma comment (lib,"iphlpapi.lib")// 扫描局域网中指定ARP主机地址void ArpScan(char *LocalIP,char *TargetIP){ ULONG localIP = inet_addr(LocalIP); ULONG targetIP = inet_addr(TargetIP); ULONG macBuf[2] = { 0 }; ULONG macLen = 6; DWORD retValue = SendARP(targetIP, localIP, macBuf, &macLen); unsigned char *mac = (unsigned char*)macBuf; printf("IP: %-12s --> MAC: ", TargetIP); for (int x = 0; x < macLen; x++) { printf("%.2X", mac[x]); if (x != macLen - 1) printf("-"); } printf("\n");}int main(int argc,char * argv[]){ for (int x = 1; x < 100; x++) { char target[32] = { 0 }; sprintf(target, "192.168.1.%d", x); ArpScan("192.168.1.2", target); } system("pause"); return 0;}
根据端口探测中所使用的方法,实现多线程也很容易,如下代码实现了使用多线程方式扫描局域网内存活的主机。代码中使用 SendARP() 函数来探测目标主机是否存活,并使用多线程方式来加快扫描速度,同时使用临界区来控制多线程条件下的输出效果。
具体实现过程如下:
#include <stdio.h>#include <winsock2.h>#include <iphlpapi.h>#pragma comment(lib,"ws2_32.lib")#pragma comment(lib,"iphlpapi.lib")// 临界区,控制多线程打印顺序CRITICAL_SECTION g_critical;bool checkActive(in_addr ip){ ULONG dstMac[2] = { 0 }; memset(dstMac, 0xff, sizeof(dstMac)); ULONG size = 6; HRESULT re = SendARP(ip.S_un.S_addr, 0, dstMac, &size); if (re == NO_ERROR && size == 6) { // 线程进入临界区,其他线程不能再进入,控制多线程在界面上的打印顺序 EnterCriticalSection(&g_critical); printf("[+] 发现存活主机: %-15s ---> MAC :", inet_ntoa(ip)); BYTE *bPhysAddr = (BYTE *)& dstMac; for (int i = 0; i < (int)size; i++) { // 如果是mac地址的最后一段,就输出换行 if (i == (size - 1)) { printf("%.2X\n", (int)bPhysAddr[i]); } else { // 否则没有到最后一段,依旧输出,但不换行 printf("%.2X-", (int)bPhysAddr[i]); } } // 线程离开临界区,其他线程能够继续进入 LeaveCriticalSection(&g_critical); return true; } else { return false; }}// 启动多线程DWORD WINAPI threadProc(LPVOID lpThreadParameter){ in_addr ip; ip.S_un.S_addr = (ULONG)lpThreadParameter; checkActive(ip); return 0;}int main(int argc, char *argv[]){ in_addr ip_start, ip_end; // 定义开始IP ip_start.S_un.S_addr = inet_addr("192.168.9.1"); // 定义结束IP ip_end.S_un.S_addr = inet_addr("192.168.9.254"); // 循环探测主机 //初始临界区 InitializeCriticalSection(&g_critical); for (in_addr ip = ip_start; ip.S_un.S_addr < ip_end.S_un.S_addr; ip.S_un.S_un_b.s_b4++) { printf("探测: %s \r", inet_ntoa(ip)); CreateThread(NULL, 0, threadProc, (LPVOID)ip.S_un.S_addr, 0, 0); } system("pause"); return 0;}
编译并运行上述代码片段,则会探测192.168.9.1到192.168.9.254网段内存活的主机,并输出该主机的MAC信息,输出效果图如下所示;