【网络编程3】网络编程基础-arp请求(局域网主机扫描)

ARP协议

ARP(Add ress Resolution Protocol)地址解析协议位于数据链路层,是根据IP地址获取MAC地址的一个协议。

ARP 查看指令

arp -a 显示所有接口的当前ARP缓存表
arp -d 删除指定的IP地址项
arp -s 添加静态IP-MAC映射记录

ARP 缓存中毒(ARP欺骗)

arp传送原理在于主机发送信息时将包含目标IP地址的ARP请求广播到网络上的所有主机,并接收返回消息,以此确定目标的物理地址;收到返回消息后将该IP地址和物理地址存入本机ARP缓存中并保留一定时间。

攻击者可以向某一主机发送伪ARP应答报文,使其发送的信息无法到达预期的主机或到达错误的主机,这就构成了一个ARP欺骗。

arp欺骗之所以有效,是因为特意构造的ARP数据包使两台主机相信它们是在互相通信,而实际上它们是在与一个中间转发数据包的第三方通信。

ARP格式

image

硬件类型:数据链路层使用的类型数据,大多数情况下,这个类型是以太网(类型 1)

协议类型:arp请求正在使用的高层协议

硬件地址长度:正在使用的物理地址长度(字节)

协议地址长度:正在使用的逻辑地址长度(字节)

操作:ARP数据包功能,1表示请求,2表示响应

发送方硬件地址:发送者的物理地址

发送方协议地址:发送者的协议地址

目标方硬件地址:接收者的物理地址(广播arp请求中接收者的物理地址常为0)

目标方协议地址:接收者的协议地址

网络编程

通过学习上面的理论知识就可以尝试着用来写一些实用的小工具练手了,例如扫描网络段存活主机与MAC地址。

发送ARP包->arp请求

发送arp包前需要了解两个函数,SendARP()与CreateThread();

SendARP()函数

这个函数用来发送ARP数据包并在定义的MAC缓冲区中返回定义的IP对应的MAC地址。

该函数有一个缺点:
该函数本质上就是向目标主机发送一个ARP请求包,然后得到应答包来更新MAC,但是ARP请求包里的发送端IP和MAC是本机的实际IP和MAC,这样对方arp -a查看缓存表时记录里就有我的IP-MAC映射记录,容易知道有人在扫描其机器或者是ARP病毒也可能。

SendARP(
	IPAddr DestIP,
	IPAddr SrcIP,
	PULONG pMacAddr,
	PULONG PhyAddrLen
);
第一个参数是IP地址的网络字节顺序,而不是一个指针,当初我就是赋值成指针而使得获取不了MAC地址。
第二个参数填0就可以
第三个参数是MAC缓冲区指针
第四个参数是一个指向一个DWORD型数值为6的指针

多线程函数

CreateThread()函数

Windows API函数。该函数在主线程的基础上创建一个新线程。微软在Windows API中提供了建立新的线程的函数CreateThread。
CreateThread()函数是Windows提供的API接口,作用是在主线程中再开启一个多线程。
在C/C++语言另有一个创建线程的函数_beginthreadex()

实例代码

实例代码中主要就用了SendARP()函数和CreateThread()函数,还有自定义的函数checkActive()

主要分为三步处理:

一、 main()函数中的处理

定义了IP的格式,开始IP与结束IP分别设定为:192.168.1.1,192.168.1.255;

in_addr类型是结构体组成,结构体构成如下:

传入的IP在联合体中s_b1就是192,s_b2就是168,s_b3就是1,s_b4就是IP四个段中的最后一个值X.X.X.255

typedef struct in_addr {
        union {
                struct { UCHAR s_b1,s_b2,s_b3,s_b4; } S_un_b;   
                struct { USHORT s_w1,s_w2; } S_un_w;
                ULONG S_addr;
        } S_un;

通过for循环比较【开始IP】最后一段数值x.x.x.1,如果比【结束IP】的最后一段数值小x.x.x.255,进入多线程函数中CreateThread()中的线程函数threadProc()中做处理;

二、 threadPro中的处理c

这里传入的IP结构体信息进行转换,然后交给自定义函数checkActive()判断主机是否存活,然后将信息打印出来。

涉及到的系统小函数知识:
inet_ntoa()函数: 将一个IP转换成一个互联网标准点分格式的字符串;

三、checkActive()函数

整个程序的核心就用了SendARP(),将被检测的目的IP输入,源IP置为0。然后查看返回的结果值是否没有错误。

#include "stdafx.h"
#define _WINSOCK_DEPRECATED_NO_WARNINGS

#include <winsock2.h>
#pragma comment(lib,"ws2_32.lib")
#include <iphlpapi.h>
#pragma comment(lib,"iphlpapi.lib")



//检查存活主机
bool checkActive(in_addr ip) 
{
	ULONG dstMac[2] = { 0 };
	memset(dstMac, 0xff, sizeof(dstMac));
	ULONG size = 6;
	//这里是可以直接读取mac地址的,但是加了多线程不容易操控了。
	HRESULT re = SendARP(ip.S_un.S_addr, 0, dstMac, &size);
	if (re == NO_ERROR)
	{
		return true;
	}
	else
	{
		return false;
	}
}

//进入线程中的函数中
DWORD WINAPI threadProc(LPVOID lpThreadParameter) 
{
	in_addr ip;
	ip.S_un.S_addr = (ULONG)lpThreadParameter;
    //调用自定义函数
	if (checkActive(ip))
	{
		printf("发现存活主机 : %s \n", inet_ntoa(ip));
	}
	return 0;
}



int main()
{

	in_addr ip_start;
	in_addr ip_end;

	ip_start.S_un.S_addr = inet_addr("192.168.1.1");
	ip_end.S_un.S_addr = inet_addr("192.168.1.255");

	//循环探测主机
	//如果开始IP小于结束IP,那么IP的最后一段增加;
	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);
	}

	getchar();
    return 0;
}

参考:

用SendARP()获取对方的MAC地址

http://blog.csdn.net/gaojinshan/article/details/8990802

主机IP存活扫描,扫描MAC地址

以上代码单线程模式下其实是可以直接读取mac地址并输出的,但是加了多线程后就不容易操控了。因为打印出来的不知道是哪个线程获取到的数据。所以需要了解多线程内如何操控同一内存数据;

涉及到的系统小函数知识

多个线程操作相同的数据时,一般是需要按顺序访问的,否则会引导数据错乱,无法控制数据,变成随机变量。为解决这个问题,就需要引入互斥变量,让每个线程都按顺序地访问变量。这样就需要使用EnterCriticalSection和LeaveCriticalSection函数。

EnterCriticalSection函数原型

线程进入临界区,其他线程不能再进入,控制多线程在界面上的打印顺序

WINBASEAPI
VOID
WINAPI
EnterCriticalSection(
    _Inout_ LPCRITICAL_SECTION lpCriticalSection
    );

LeaveCriticalSection函数原型

线程离开临界区,其他线程能够继续进入

WINBASEAPI
VOID
WINAPI
LeaveCriticalSection(
    _Inout_ LPCRITICAL_SECTION lpCriticalSection
    );

实例代码

与上面的发送arp请求差不多,但是增加了对线程的处理,输出mac地址;

#include "stdafx.h"
#define _WINSOCK_DEPRECATED_NO_WARNINGS

#include <winsock2.h>
#pragma comment(lib,"ws2_32.lib")
#include <iphlpapi.h>
#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++) {
			if (i == (size - 1))                      //如果是mac地址的最后一段,就输出换行
				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()
{
	in_addr ip_start;
	in_addr ip_end;

	ip_start.S_un.S_addr = inet_addr("192.168.1.1");
	ip_end.S_un.S_addr = inet_addr("192.168.1.255");

	//循环探测主机
	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++)
	{
		//开启多线程
		CreateThread(NULL, 0, threadProc, (LPVOID)ip.S_un.S_addr, 0, 0);

	}

	getchar();
	return 0;
}

运行效果

posted @ 2016-12-13 00:02  17bdw  阅读(2044)  评论(0编辑  收藏  举报