C语言通过调用npcap库发送以太网报文

写在前面:C语言通过调用Npcap库发送以太网报文,可自定义Mac地址以及协议类型。

首先,Npcap 是专为 Windows 开发的一款网络抓包 SDK,该 SDK 提供了被应用程序调用的库文件和系统驱动程序。通过 Npcap,我们可以得到原始(raw)网络数据,即未经过 TCP/IP 协议栈的数据,也就是网卡收到的数据。也就是可直接通过Mac地址发送报文。

1.使用前准备事项

1.1安装Npcap,安装过wireshark可直接跳过这一步骤

1.2下载头文件和库文件,下载地址:https://npcap.com/#download

      下载SDK,未安装wireshark记得下载installer

安装包包含以下文件,主要使用到Include和Lib

2.正式使用

菜单栏项目-属性中进行设置

C/C++ -> 常规,附加包含目录,添加 npcap-sdk-1.13\Include。
连接器 -> 常规,附加库目录,添加 npcap-sdk-1.13\Lib\x64。以上需根据自己补全路径。
链接器 -> 输入,附加依赖项,加上 Ws2_32.lib;wpcap.lib;。
链接器 -> 输入,延迟加载的 DLL,填入 wpcap.dll。

---------------------------------------------------------------------------------------------------------------------

完成以上步骤,准备工作就已完成。
 

本文发送数据包用的是 int pcap_sendpacket(pcap_t *p, const u_char *buf, int size); 这个函数。下面先给出代码工程所需的.c和.h文件代码。

// npcap.c
#ifdef _MSC_VER
#define _CRT_SECURE_NO_WARNINGS
#endif

#include <stdlib.h>
#include <string.h>

#include "common.h"

// 合成 MAC 地址(只能是常量)
#define MAC(aa, bb, cc, dd, ee, ff) (int8_t[6]) { 0x##aa, 0x##bb, 0x##cc, 0x##dd, 0x##ee, 0x##ff }

#define ETHERNET_TYPE_IPV4 0x0800 // 以太网类型 IPv4
#define ETHERNET_TYPE_END 0x3412 // 结束包,自定义类型

#ifdef _WIN32
#include <tchar.h>
// 加载 Npcap DLL
int LoadNpcapDlls()
{
	_TCHAR npcap_dir[512];
	UINT len;
	len = GetSystemDirectory(npcap_dir, 480);
	if (!len) {
		fprintf(stderr, "Error in GetSystemDirectory: %x", GetLastError());
		return 0;
	}
	_tcscat_s(npcap_dir, 512, _T("\\Npcap"));
	if (SetDllDirectory(npcap_dir) == 0) {
		fprintf(stderr, "Error in SetDllDirectory: %x", GetLastError());
		return 0;
	}
	return 1;
}
#endif

// 按点分十进制输出 IPv4 地址
void pretty_print_ipv4(uint8_t ip[4])
{
	printf("%d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3]);
}

// 输出 MAC 地址
void pretty_print_mac(uint8_t mac[6])
{
	printf("%02x:%02x:%02x:%02x:%02x:%02x", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
}


//组包
int make_ethernet_packet(
	uint8_t target_address[6], // 目的 MAC 地址
	uint8_t source_address[6], // 源 MAC 地址
	int16_t ethertype, // 以太网类型(DIX Ethernet II)/帧长(IEEE 802.3)
	uint8_t* payload, // 数据部分
	size_t payload_length, // 数据长度(比特数)
	uint8_t** data, // 返回值:以太网帧。由调用者释放内存。
	size_t* data_length // 返回值:以太网帧长度(字节数))
{
	// 计算总长度并分配内存
	*data_length = 6 + 6 + 2 + payload_length;

	// 最大帧长 1500-4 字节
	if (*data_length > 1496)
		return -2;

	// 最小帧长 64-4(FCS长度)-6-6-2(头长度)=46 字节
	// 不够要填充额外数据
	if (*data_length < 46)
	{
		int8_t* new_payload = calloc(60, sizeof(int8_t));
		memcpy(new_payload, payload, payload_length);
		// 覆盖掉原始 payload
		payload = new_payload;
		payload_length = 46;
		// 重新计算 data_length
		*data_length = 6 + 6 + 2 + payload_length;
	}

	*data = calloc(*data_length, sizeof(int8_t));
	if (data == NULL)
		return -1;

	// 处理大小端问题
	ethertype = htons(ethertype);

	// 填充头部
	memcpy(*data, target_address, 6);
	memcpy(*data + 6, source_address, 6);
	memcpy(*data + 12, &ethertype, sizeof(ethertype));

	// 填充数据
	memcpy(*data + 14, payload, payload_length);
	return 0;
}


int network_main(pcap_t* pcap)
{
		uint8_t payload[56] = { 0x36, 00,0x2e, 00, 00, 00, 00, 00 ,07, 00 ,00 ,00 ,0x43 
               ,0x4e ,0x31 ,00, 00 ,00, 00, 00, 00 ,00, 00, 00, 00, 00, 00, 00, 00, 00,
				 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 
                00, 00, 00, 00, 0xf7, 00, 00, 00 };     //命名报文
	uint8_t* data = NULL;
	size_t data_length = 0;

	int ret = make_ethernet_packet(
		MAC(11, 22, 33, 44, 55, 66), // 目的地址,板卡Mac
		MAC(5e, 60, ba, 37, ee, b6), // 源地址,主机Mac
		ETHERNET_TYPE_END, //结束包    0x1234    
		payload,
		sizeof(payload) / sizeof(int8_t),
		&data,
		&data_length);

	if (ret != 0)
	{
		printf("创建以太网帧失败");
		return -5;
	}

	if (pcap_sendpacket(pcap, data, (int)data_length) != 0) // 返回值不为 0 表示出错
		printf("发送数据包时出错:%s\n", pcap_geterr(pcap));
	free(data); // 最后发完了记得 free 掉

	return 0;
}


int main()
{
	char error_buffer[PCAP_ERRBUF_SIZE]; // 用于储存错误信息

	// 载入 DLL
#ifdef _WIN32
	if (!LoadNpcapDlls())
	{
		fprintf(stderr, "无法加载 Npcap。\n");
		return -1;
	}
#endif

	// 初始化
	if (pcap_init(PCAP_CHAR_ENC_LOCAL, error_buffer) != 0) {
		printf("初始化 pcap 库失败: %s\n", error_buffer);
		return -2;
	}

	// 获取所有适配器并让用户选择
	pcap_if_t* devices;
	char err_buffer[1024]; // 用来储存错误信息
	if (pcap_findalldevs(&devices, err_buffer) == -1)
	{
		printf("pcap_findalldevs 时出错:%s\n", err_buffer);
		exit(1);
	}

	// 打印所有适配器名称(devices 是个链表)
	/*int i = 0;
	for (pcap_if_t* device = devices; device; device = device->next)
	{
		i++;
		printf("[%d] %s | 名称=%s\n", i, device->description, device->name);
	}*/

	//int choice = 0;
	//printf("请选择一个适配器:");
	//scanf("%d", &choice);
	int choice = 6;      //默认选择以太网2

	// 找到选中的适配器
	pcap_if_t* device = devices;
	for (int i = 0; i != choice - 1; i++)
	{
		device = device->next;
	}
	// 打开它
	pcap_t* pcap = pcap_open_live(
		device->name, // 适配器名称
		0,  
		0,
		1000, // 超时时间,毫秒
		error_buffer
	);
	if (pcap == NULL)
	{
		printf("打开适配器 %s 失败。\n", device->description);
		return -3;
	}

	// 打开之后要释放之前的适配器列表
	pcap_freealldevs(devices);

	// 检查数据链路层协议
	int datalink_type = pcap_datalink(pcap);
	if (datalink_type != DLT_EN10MB) // 10Mb 以上的以太网
	{
		printf("不支持的数据链路层协议。");
		return -4;
	}
	
	int ret = network_main(pcap);         //发送配置报文

	pcap_close(pcap);
	system("pause");
	return ret;
}

以上是c代码,下面是.h文件代码。主要函数如下:

// npcap.h
#pragma once
#include <inttypes.h>
#include <pcap.h>

// 加载 Npcap DLL,需要在调用任何 pcap 函数之前调用先本函数。
int LoadNpcapDlls();
// 按点分十进制输出 IPv4 地址
void pretty_print_ipv4(uint8_t ip[4]);
// 输出 MAC 地址
void pretty_print_mac(uint8_t mac[6]);
// 程序入口
int network_main(pcap_t* pcap);

按照以上配置可实现自定义发送数据链路层报文。通过wireshark抓取到的以太网口报文如下,前六字节为目的Mac,后六字节为源Mac。

最后,以上为学习中做笔记所用,未有任何商业用途,谢谢XcantloadX分享。欢迎大家一起讨论。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值