首页 > 编程语言 >使用C语言实现简单的网络嗅探程序

使用C语言实现简单的网络嗅探程序

时间:2023-01-25 19:11:30浏览次数:44  
标签:include struct ip 程序 嗅探 uint16 C语言 data port

嗅探程序可以捕捉到通过网卡的数据包并进行分析
接下来会使用C语言实现一个简单的嗅探程序
程序大概的思路:

  1. 开始嗅探
  2. 将捕捉到的数据包转发给监听者

准备工作

导入所需的头文件

#include <stdio.h>
#include <time.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdint.h>
#include <string.h>
#include <pthread.h>
#include <sys/types.h>
#include <sys/signal.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/if_ether.h>
#define RECV_MAX_SIZE 0xffff // 接收数据最大大小
#define MAX_LISTENERS 100 // 最大监听数
#define BUFFER_SIZE 0xffff // 缓冲区大小
#define IP "0.0.0.0" // 转发时绑定的IP
#define IPTYPE_TCP 6 // 当IP头部的protocol字段为6时,表示是tcp协议

监听者列表

int listenersnumber = 0; // 数量
int listenerslist[MAX_LISTENERS]; // 储存所有监听者的网络套接字

以太帧头部

typedef struct
{
	uint64_t source_mac: 48;
	uint64_t destination_mac: 48;
	uint16_t ether_type;
} __attribute__((packed)) ETHER_HEADER;

IP头部

typedef struct
{
	uint8_t header_length: 4;
	uint8_t version: 4;
	uint8_t type_of_serive;
	uint16_t total_length;
	uint16_t id;
	uint16_t fragmentfation_offset: 13;
	uint16_t flags: 3;
	uint8_t time_to_live;
	uint8_t protocol;
	uint16_t header_checksum;
	uint32_t source_ip_address;
	uint32_t destination_ip_address;
} __attribute__((packed)) IP_HEADER;

TCP头部

typedef struct
{
	uint16_t source_port;
	uint16_t destination_port;
	uint32_t sequence_number;
	uint32_t acknowledgment_number;
	uint8_t reserved: 4;
	uint8_t header_length: 4;
	uint8_t flags;
	uint16_t window_size;
	uint16_t header_checksum;
	uint16_t urgent_pointer;
} __attribute__((packed)) TCP_HEADER;

如果不清楚__attribute__的用法可以去搜一下, 此处__attribute__((packed))的用途是使用单字节对齐

开始嗅探

单独开一个线程出来嗅探

void *sniff(void *args)

创建套接字

int fd = -1;
fd = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL)); // 网卡混杂模式
if (fd < 0)
{
	return 0;
}

接收数据并转发给所有监听者

for (;;)
{
	struct sockaddr srcaddr;
	int srcaddr_size = sizeof(struct sockaddr);
	memset(&srcaddr, 0, srcaddr_size);
	char *data = (char *)malloc(BUFFER_SIZE * sizeof(char));
	memset(data, 0, BUFFER_SIZE);
	size_t recvlen = recvfrom(fd, data, BUFFER_SIZE, 0, &srcaddr, (socklen_t *)&srcaddr_size);
	// 转发消息 //
	for (int c = 0; c < listenersnumber; ++c)
	{
		if (send(listenerslist[c], data, recvlen, 0) < 0)
		{
			for (int i = c; i < listenersnumber; ++i)
			{
				listenerslist[i] = listenerslist[i + 1]; 
			}
			listenersnumber--;
		}
	}
	free(data);
}

不过此处有一个严重的问题: 因为使用tcp协议转发数据,发出去的数据包大小增长了 1个IP头部大小 和 一个TCP头部大小。数据包发出去后程序又捕捉到发出去的数据包,再次转发给监听者,大小再次增加。这样的过程一直重复,不光是程序工作不正常,电脑也会死机。
我给出的解决方案:如果需要转发的数据包使用tcp协议且其中的目的地址或原地址是任意一个监听者的,就停止发送此数据包

获取数据包的类型(和原地址和目的地址)

uint32_t data_source_ip, data_destination_ip;
uint16_t data_source_port, data_destination_port;
ETHER_HEADER *ether_header = (ETHER_HEADER *)data;
if (ntohs(ether_header -> ether_type) == ETHERTYPE_IP)
{
	IP_HEADER *ip_header = (IP_HEADER *)(data + sizeof(ETHER_HEADER));
	data_source_ip = ntohl(ip_header -> source_ip_address);
	data_destination_ip = ntohl(ip_header -> destination_ip_address);
	if ((ip_header -> protocol) == IPTYPE_TCP)
	{
		TCP_HEADER *tcp_header = (TCP_HEADER *)(data + sizeof(ETHER_HEADER) + sizeof(IP_HEADER));
		data_source_port = ntohs(tcp_header -> source_port);
		data_destination_port = ntohs(tcp_header -> destination_port);
	}
}

ntohs, htons, ntohl, htonl 的用途是实现主机字节序与网络字节序之间的转换

在转发数据前所做的判断

int inlist = 0; // 0表示不是某个监听者的,1表示是
for (int c = 0; c < listenersnumber; ++c)
{	
	struct sockaddr remoteaddr;
	struct sockaddr_in *remoteaddr_in;
	socklen_t sizeofaddr = sizeof(struct sockaddr);
	getpeername(listenerslist[c], &remoteaddr, &sizeofaddr);
	remoteaddr_in = (struct sockaddr_in *)&remoteaddr;
	uint32_t listener_ip = ntohl(remoteaddr_in -> sin_addr.s_addr);
	uint16_t listener_port = ntohs(remoteaddr_in -> sin_port);
	if ((data_destination_ip == listener_ip && data_destination_port == listener_port) || \
		(data_source_ip == listener_ip && data_source_port == listener_port))
	{
		inlist = 1;
		break;
	}
}
if (inlist)
{
	continue;
}

代码中的getpeername()用来通过监听者的套接字获得其套结字

与监听者建立连接

启动嗅探程序

pthread_t sniffth;
pthread_create(&sniffth, 0, sniff, 0);

获取转发服务所绑定的端口

uint16_t port = -1;
sscanf(argv[1], "%hd", &port);
if (port >= 0xffff || port <= 0)
{
	return 0;
}
int tcpfd = -1;
struct sockaddr_in address;
memset(&address, 0, sizeof(struct sockaddr_in));
address.sin_family = AF_INET;
address.sin_port = htons(port);
address.sin_addr.s_addr = inet_addr(IP);
tcpfd = socket(AF_INET, SOCK_STREAM, 0);
if (tcpfd == -1)
{
	return 0;
}
struct sigaction sa;
sa.sa_handler = SIG_IGN;
sa.sa_flags = 0;
sigemptyset(&sa.sa_mask);
sigaction(SIGPIPE, &sa, 0);
if (bind(tcpfd, (struct sockaddr *)&address, sizeof(struct sockaddr)) < 0)
{
	return 0;
}
if (listen(tcpfd, MAX_LISTENERS) < 0)
{
	return 0;
}
for (;;)
{
	int new_fd;
	struct sockaddr raddress;
	socklen_t addrlen = sizeof(struct sockaddr);
	new_fd = accept(tcpfd, &raddress, &addrlen);
	if (new_fd < 0)
	{
		continue;
	}
	// 添加套结字到监听者列表中
	listenerslist[listenersnumber] = new_fd;
	listenersnumber++;
	struct sockaddr_in *new_address = (struct sockaddr_in *)(&raddress);
	uint8_t *ipp = (uint8_t *)&(((struct in_addr *)&(new_address -> sin_addr)) -> s_addr);
	uint16_t portv = ntohs((uint16_t)(new_address -> sin_port));
}

代码中间需要注意的是对SIGPIPE信号的处理
客户端断开连接会产生SIGPIPE信号,程序受到此信号之后一般会直接停止
如果希望不对此信号产生反应,可以使用

signal(SIGPIPE, SIG_IGN);

也可以使用上面代码里面的

struct sigaction sa;
sa.sa_handler = SIG_IGN;
sa.sa_flags = 0;
sigemptyset(&sa.sa_mask);
sigaction(SIGPIPE, &sa, 0);

完整代码:

#include <stdio.h>
#include <time.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdint.h>
#include <string.h>
#include <pthread.h>
#include <sys/types.h>
#include <sys/signal.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/if_ether.h>
#define RECV_MAX_SIZE 0xffff
#define MAX_LISTENERS 100
#define BUFFER_SIZE 0xffff
#define IP "0.0.0.0"
#define IPTYPE_TCP 6

typedef struct
{
	uint64_t source_mac: 48;
	uint64_t destination_mac: 48;
	uint16_t ether_type;
} __attribute__((packed)) ETHER_HEADER;

typedef struct
{
	uint8_t header_length: 4;
	uint8_t version: 4;
	uint8_t type_of_serive;
	uint16_t total_length;
	uint16_t id;
	uint16_t fragmentfation_offset: 13;
	uint16_t flags: 3;
	uint8_t time_to_live;
	uint8_t protocol;
	uint16_t header_checksum;
	uint32_t source_ip_address;
	uint32_t destination_ip_address;
} __attribute__((packed)) IP_HEADER;

typedef struct
{
	uint16_t source_port;
	uint16_t destination_port;
	uint32_t sequence_number;
	uint32_t acknowledgment_number;
	uint8_t reserved: 4;
	uint8_t header_length: 4;
	uint8_t flags;
    uint16_t window_size;
    uint16_t header_checksum;
    uint16_t urgent_pointer;
} __attribute__((packed)) TCP_HEADER;

int listenersnumber = 0;
// Save socket fd //
int listenerslist[MAX_LISTENERS];

void *sniff(void *args)
{
	/* Create sniffing socket */
	int fd = -1;
	fd = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL));
	if (fd < 0)
	{
		printf("[!] Create sniffing socket error: sniffing service " \
			"will stop and programme will not work correctly\n");
		return 0;
	}
	printf("[+] Create sniffing socket\n");

	/* Receive data */
	printf("[+] Start sniffing\n");
	for (;;)
	{
		struct sockaddr srcaddr;
		int srcaddr_size = sizeof(struct sockaddr);
		memset(&srcaddr, 0, srcaddr_size);
		// Data buffer //
		char *data = (char *)malloc(BUFFER_SIZE * sizeof(char));
		memset(data, 0, BUFFER_SIZE);
		size_t recvlen = recvfrom(fd, data, BUFFER_SIZE, 0, &srcaddr, (socklen_t *)&srcaddr_size);
		/*
		Issue:
		If this computer sends LAN data to listener,
		the size of data sent to listen will increase the size of IP and TCP header
		and this computer sniffs this bigger packet, then sends to listener
		So it's not good to sniff data sent to listener 
		*/
		uint32_t data_source_ip, data_destination_ip;
		uint16_t data_source_port, data_destination_port;
		ETHER_HEADER *ether_header = (ETHER_HEADER *)data;
		if (ntohs(ether_header -> ether_type) == ETHERTYPE_IP)
		{
			IP_HEADER *ip_header = (IP_HEADER *)(data + sizeof(ETHER_HEADER));
			data_source_ip = ntohl(ip_header -> source_ip_address);
			data_destination_ip = ntohl(ip_header -> destination_ip_address);
			if ((ip_header -> protocol) == IPTYPE_TCP)
			{
				TCP_HEADER *tcp_header = (TCP_HEADER *)(data + sizeof(ETHER_HEADER) + sizeof(IP_HEADER));
				data_source_port = ntohs(tcp_header -> source_port);
				data_destination_port = ntohs(tcp_header -> destination_port);
			}
		}
		// If data_destination_ip:port or data_source_ip:port in listeners list, stop sending //
		int inlist = 0;
		for (int c = 0; c < listenersnumber; ++c)
		{	
			struct sockaddr remoteaddr;
			struct sockaddr_in *remoteaddr_in;
			socklen_t sizeofaddr = sizeof(struct sockaddr);
			getpeername(listenerslist[c], &remoteaddr, &sizeofaddr);
			remoteaddr_in = (struct sockaddr_in *)&remoteaddr;
			uint32_t listener_ip = ntohl(remoteaddr_in -> sin_addr.s_addr);
			uint16_t listener_port = ntohs(remoteaddr_in -> sin_port);
			if ((data_destination_ip == listener_ip && data_destination_port == listener_port) || \
				(data_source_ip == listener_ip && data_source_port == listener_port))
			{
				inlist = 1;
				break;
			}
		}
		if (inlist)
		{
			continue;
		}

		// Broadcast //
		for (int c = 0; c < listenersnumber; ++c)
		{
			if (send(listenerslist[c], data, recvlen, 0) < 0)
			{
				for (int i = c; i < listenersnumber; ++i)
				{
					listenerslist[i] = listenerslist[i + 1]; 
				}
				listenersnumber--;
				printf("[+] Disconnect(index:%d), left:%d\n", c, listenersnumber);
			}
		}
		free(data);
	}
	close(fd);
}

int main(int argc, char **argv)
{
	/* Setup sniffing programme */
	pthread_t sniffth;
	pthread_create(&sniffth, 0, sniff, 0);
	// give sniffing programme 1 second to get ready //
	sleep(1);

	/*
	1. Get the port of the service 
	*/
	uint16_t port = -1;
	sscanf(argv[1], "%hd", &port);
	if (port >= 0xffff || port <= 0)
	{
		printf("[x] You gave a wrong port\n");
		return 0;
	}

	/*
	1. Create Tcp socket
		- create
		- bind
		- max listeners
	2. Statu of listening
		- accept
	3. Add listener to list of listeners
	(4. included by function `sniff`) Send data to listeners in list
	*/
	int tcpfd = -1;
	struct sockaddr_in address;
	memset(&address, 0, sizeof(struct sockaddr_in));
	address.sin_family = AF_INET;
	address.sin_port = htons(port);
	address.sin_addr.s_addr = inet_addr(IP);
	tcpfd = socket(AF_INET, SOCK_STREAM, 0);
	if (tcpfd == -1)
	{
		printf("[x] Create listener error\n");
		return 0;
	}
	printf("[+] Create listener socket\n");
	// Introduction: programme might quit when client is closed because of SIGPIPE signal! //
	// struct sigaction sa;
	// sa.sa_handler = SIG_IGN;
	// sa.sa_flags = 0;
	// if ((sigemptyset(&sa.sa_mask) < 0) || (sigaction(SIGPIPE, &sa, 0) < 0))
	if (signal(SIGPIPE, SIG_IGN) < 0)
	{
		printf("[!] Set SIGPIPE handler, the programme will quit when client is closed\n");
	}
	printf("[+] Set SIGPIPE handler\n");
	if (bind(tcpfd, (struct sockaddr *)&address, sizeof(struct sockaddr)) < 0)
	{
		printf("[x] Bind error\n");
		return 0;
	}
	printf("[+] Bind to port: %hd\n", port);
	if (listen(tcpfd, MAX_LISTENERS) < 0)
	{
		printf("[x] Set maximum number the listeners error\n");
		return 0;
	}
	printf("[+] Set max listeners: %d\n", MAX_LISTENERS);
	for (;;)
	{
		int new_fd;
		struct sockaddr raddress;
		socklen_t addrlen = sizeof(struct sockaddr);
		new_fd = accept(tcpfd, &raddress, &addrlen);
		if (new_fd < 0)
		{
			printf("[x] Accept error(igrone)\n");
			continue;
		}
		// Add to listeners list //
		listenerslist[listenersnumber] = new_fd;
		listenersnumber++;

		struct sockaddr_in *new_address = (struct sockaddr_in *)(&raddress);
		uint8_t *ipp = (uint8_t *)&(((struct in_addr *)&(new_address -> sin_addr)) -> s_addr);
		uint16_t portv = ntohs((uint16_t)(new_address -> sin_port));
		printf("[+] Accept: %d.%d.%d.%d:%hu\n", *ipp, *(ipp + 1), *(ipp + 2), *(ipp + 3), portv);
	}

	return 0;
}

监听者

监听者的代码就比较简单了, 不做过多解释

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stdint.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>
#define RECV_MAX_SIZE 0xffff
#define BUFFER_SIZE 0xffff
#define DESTINATION_IP "127.0.0.1" // 监听的机器
#define DESTINATION_PORT 9999 // 监听的机器的转发服务所在端口
/* 以太帧头部type字段代表的协议,我只列出IP和ARP的. 在 netinet/if_ether.h 也有定义 */
#define ETHERTYPE_IP 0x0800
#define ETHERTYPE_ARP 0x0808

typedef struct
{
	uint64_t source_mac: 48;
	uint64_t destination_mac: 48;
	uint16_t ether_type;
} __attribute__((packed)) ETHER_HEADER;

void data_handler(char *data, int len)
{
	printf("[*] %-5d ", len);
	ETHER_HEADER *ether_header = (ETHER_HEADER *)data;
	switch (ntohs(ether_header -> ether_type))
	{
		case (ETHERTYPE_IP): {
			printf("IP ");
			break;
		}
		case (ETHERTYPE_ARP): {
			printf("ARP ");
			break;
		}
		default: {
			printf("Unk ");
		}
	}
	printf("\n");
}

int main()
{
	/* Create connection and receive data from tgt server */
	int fd = -1;
	fd = socket(AF_INET, SOCK_STREAM, 0);
	if (fd == -1)
	{
		printf("[x] Create socket error\n");
		return 0;
	}
	printf("[+] Create socket\n");
	// Address //
	struct sockaddr_in server_addr;
	server_addr.sin_family = AF_INET;
	server_addr.sin_port = htons(DESTINATION_PORT);
	server_addr.sin_addr.s_addr = inet_addr(DESTINATION_IP);
	// Connect to server //
	if (connect(fd, (struct sockaddr *)&server_addr, sizeof(struct sockaddr_in)) < 0)
	{
		printf("[x] Connect error\n");
		return 0;
	}
	printf("[+] Connect to %s:%d\n", DESTINATION_IP, DESTINATION_PORT);
	// Receive data //
	for (;;)
	{
		char *data = (char *)malloc(BUFFER_SIZE * sizeof(char));
		memset(data, 0, BUFFER_SIZE);
		int len = recv(fd, data, RECV_MAX_SIZE, 0);
		if (len < 0)
		{
			printf("[!] Receive error(ignore)\n");
			free(data);
			continue;
		}
		data_handler(data, len);
		free(data);
	}

	return 0;
}

标签:include,struct,ip,程序,嗅探,uint16,C语言,data,port
From: https://www.cnblogs.com/await0/p/17067115.html

相关文章

  • 腾讯出品小程序自动化测试框架【Minium】系列(二)项目配置及测试套件使用说明
    一、写在前面真的人这一散漫惯了,收心就很难了,上午把小程序开发环境启动后,在QQ游戏里,杀了三把象棋,5把2D桌球,一上午没了,还是没法心静下来去学点东西。那就老样子,逼着自己开......
  • 怎样才能快速成为优秀的程序员
    ​​http://www.itcast.cn/subject/czschool/index.shtml​​在国内,IT行业目前仍然属于新兴行业,整个社会的信息化也处在快速发展的过程中,从事软件开发,可以说是性价比最高的......
  • C语言库函数qsort的使用
    前言qsort是C语言的库函数,使用前需包含头文件#include<stdlib.h>,函数原型是voidqsort(void*base,size_tnum,size_twidth,int(__cdecl*compare)(constvoid*......
  • 非计算机专业如何转行做程序员?
    随着互联网的飞速发展,云计算、大数据由“热点”到落地,也带动着市场对软件开发者的需求,而目前国内软件开发从业者本已供不应求。互联网的火热不仅给软件开发者带来更为广阔的......
  • “一句话证明你是程序员”,你们的评论要不要这么有才!
     “一句话证明你是程序员”活动在大家的热情参与下落下帷幕,在这个属于猿媛们的“1024程序员节”里,你们都玩开心了,玩过瘾了,才是我们的初衷哦!小透露一下,明年的“1024程序员节......
  • 黑马程序员前端-CSS综合案例:学成在线模块添加
     前端学习笔记教程不定期更新中,传送门:​​前端HTML第一天:什么是网页?什么是HTML?网页怎么形成?​​​​黑马程序员前端-CSS入门总结​​​​黑马程序员前端-CSS之emmet语法​......
  • 黑马程序员前端-CSS属性书写顺序(重点)
     前端学习笔记教程不定期更新中,传送门:​​前端HTML第一天:什么是网页?什么是HTML?网页怎么形成?​​​​黑马程序员前端-CSS入门总结​​​​黑马程序员前端-CSS之emmet语法​......
  • Flask框架简介、安装及Hello World程序运行
    一、Flask简介Flask诞生于2010年,是用Python语言基于Werkzeug工具箱编写的轻量级Web开发框架。Flask本身相当于一个内核,其他几乎所有的功能都要用到扩展(邮件扩展Flask-Mail,用......
  • 小程序逆向错误之 typeof3 is not a function
    反编译小程序的解包,在微信开发者工具当中预览界面是空白的。Console显示 app.js错误:TypeError:_typeof3isnotafunction 按错误提示找到文件@babel/runtime/hel......
  • 嵌入式Linux驱动程序开发基本概念和方法
    系统调用是操作系统内核和应用程序之间的接口,设备驱动程序是操作系统内核和机器硬件之间的接口。设备驱动程序为应用程序屏蔽了硬件的细节,这样在应用程序看来,硬件设备只是......