2017-2018-1 20179202《Linux内核原理与分析》第十二周作业

C语言实现Linux网络嗅探器

一、知识准备

1.一般情况下,网络上所有的机器都可以“听”到通过的流量,但对不属于自己的数据包则不予响应。如果某个工作站的网络接口处于混杂模式,那么它就可以捕获网络上所有的数据包和帧。

2.为了绕过标准的TCP/IP堆栈,网卡就必须设置为混杂模式。一般情况下,要激活这种方式,内核需要root权限来运行这种程序,所以Sniffer需要root身份安装。

3.当一个黑客成功地攻陷了一台主机,并拿到了root权限,而且还想利用这台主机去攻击同一网段上的其他主机时,就会在这台主机上安装Sniffer软件,对以太网设备上传送的数据包进行侦听,从而发现感兴趣的包。

4.socket套接字用于描述IP地址和端口,是一个通信链的句柄,可以用来实现不同虚拟机或不同计算机之间的通信。

5.网络嗅探器是拦截通过网络接口流入和流出的数据的程序。在本实验中用C语言实现一个网络嗅探器。程序框架和功能描述如图所示:

二、源码分析

1.sniffer.h

该头文件定义了s_protocol和s_sniffer两个结构体,并声明了把IP头部、TCP数据包、UDP数据包、ICMP数据包及用户包数据写到日志文件的函数、解析出数据包类型的函数(ProcessPacket)等:

#ifndef	__SNIFFER_H__
#define	__SNIFFER_H__


typedef struct	s_protocol
{
	int	tcp;
	int	udp;
	int	icmp;
	int	igmp;
	int	others;
	int	total;
} t_protocol;

typedef struct	s_sniffer
{
	FILE *logfile; /* 日志文件 */
	t_protocol *prot; /* 数据包协议类型 */
} t_sniffer;

void ProcessPacket(unsigned char*, int, t_sniffer *);
void print_ip_header(unsigned char* , int, t_sniffer *);
void print_tcp_packet(unsigned char* , int, t_sniffer *);
void print_udp_packet(unsigned char * , int, t_sniffer *);
void print_icmp_packet(unsigned char* , int, t_sniffer *);
void PrintData (unsigned char* , int, t_sniffer *);
void display_time_and_date();
void getting_started();
void signal_white_now(int);

#endif

2.main.c

该文件包含main函数,创建套接字侦听,解析数据包,将解析结果写入log.txt中:

#include	<signal.h>
#include	<unistd.h>
#include	<stdio.h>
#include	<stdlib.h>
#include	<string.h>
#include	<netinet/ip.h>
#include	<sys/socket.h>
#include	<sys/select.h>
#include	<fcntl.h>
#include	<sys/types.h>
#include	<sys/time.h>
#include	<errno.h>

#include	"sniffer.h"
#include	"tools.h"

#define ETH_P_IP 0x0800

int	exec_cmd(char *buffer, int len)
{
	if (strncmp(buffer, "quit", 4) == 0)
		return (1);
	return (0);
}

int	command_interpreter(int sd)
{
	int	len;
	char buf[512];

	len = read(0, buf, 512);
	if (len > 0)
	{
		if (exec_cmd(buf, len) == 1)
			return (1);
	}
	return (0);
}

void display_time_and_date()
{
	INITCOLOR(RED_COLOR);
	printf("[%s]", __DATE__); /* 打印日期 */
	INITCOLOR(GREEN_COLOR);
	printf("[%s]  ", __TIME__); /* 打印时间 */
	INITCOLOR(ZERO_COLOR);
}

void getting_started()
{
	CLEARSCREEN(); /* 清空屏幕 */
	display_time_and_date();
	printf("Getting started of Network sniffer\n\n");  
}

int	main()
{
	int	sd;
	int	res;
	int	saddr_size;
	int	data_size;
	struct sockaddr saddr;
	unsigned char *buffer; /* 保存数据包的数据 */
	t_sniffer sniffer; /* 保存数据包的类型和日志文件等信息 */
	fd_set fd_read;

	buffer = malloc(sizeof(unsigned char *) * 65536); 

	/* 以可写的方式在当前文件夹中创建日志文件 */
	sniffer.logfile = fopen("log.txt", "w");
	fprintf(sniffer.logfile,"***LOGFILE(%s - %s)***\n", __DATE__, __TIME__);
	if (sniffer.logfile == NULL)
	{
		perror("fopen(): ");
		return (EXIT_FAILURE);
	}

	sniffer.prot = malloc(sizeof(t_protocol *));  

	/* 创建原始套接字,ETH_P_IP 表示侦听负载为 IP 数据报的以太网帧 */
	sd = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_IP)); 
	if (sd < 0)
	{
		perror("socket(): ");
		return (EXIT_FAILURE);
	}
	getting_started();
	signal(SIGINT, &signal_white_now);/* 信号处理函数*/
	signal(SIGQUIT, &signal_white_now);

	/* 循环侦听以太网帧,并调用 ProcessPacket 函数解析 */
	while (1)
	{
		FD_ZERO(&fd_read);
		FD_SET(0, &fd_read);
		FD_SET(sd, &fd_read);

		/* 多路复用检测可读的套接字和标准输入 */
		res = select(sd + 1, &fd_read, NULL, NULL, NULL);
		if (res < 0)
			{
				close(sd);
				if (errno != EINTR)
				perror("select() ");
				return (EXIT_FAILURE);
			}
		else
			{
				/* 如果是标准输入可读,进入命令行处理程序 command_interpreter,暂时只支持 'quit' 命令 */
				if (FD_ISSET(0, &fd_read)) 
				{
					if (command_interpreter(sd) == 1)
					break;
				}

				/* 如果是套接字可读,则读取以太网数据帧的内容,并调用 ProcessPacket 函数解析出数据包的类型 */
				else if (FD_ISSET(sd, &fd_read))
					{
						saddr_size = sizeof(saddr);
						data_size = recvfrom(sd, buffer, 65536, 0, &saddr,(socklen_t*)&saddr_size); /* 读取以太网数据帧的内容 */
						if (data_size <= 0)
							{
								close(sd);
								perror("recvfrom(): ");
								return (EXIT_FAILURE);
							}

						ProcessPacket(buffer, data_size, &sniffer); /* 调用 ProcessPacket 函数解析出数据包的类型 */
					}
			}
	}
	
	close(sd);
	return (EXIT_SUCCESS);
}

void ProcessPacket(unsigned char* buffer, int size, t_sniffer *sniffer)
{
	buffer = buffer + 6 + 6 + 2; /* 根据太网帧结构,前 6B 是目的 MAC 地址,接下来的是源 MAC 地址,接下来 2B 是帧长度,其余的是负载(上层的 IP 数据报) */
	struct iphdr *iph = (struct iphdr*)buffer;
	++sniffer->prot->total; /* 数据包总数加 1 */

	/* 根据 TCP/IP 协议规定的 IP 数据报头部的 protocol 字段的值,判断上层的数据包类型 */
	switch (iph->protocol)
		{
			/* 1 表示 icmp 协议 */
			case 1: 
				++sniffer->prot->icmp;
				print_icmp_packet(buffer, size, sniffer);
				break;
				
			/* 2 表示 igmp 协议 */
			case 2:
				++sniffer->prot->igmp;
				break;
				
			/* 6 表示 tcp 协议 */
			case 6:
				++sniffer->prot->tcp;
				print_tcp_packet(buffer , size, sniffer);
				break;
				
			/* 17 表示 udp 协议 */
			case 17:
				++sniffer->prot->udp;
				print_udp_packet(buffer , size, sniffer);
				break;
      
			default:
				++sniffer->prot->others;
				break;
		}

	display_time_and_date(); /* 显示时间 */

	/* 打印 sniffer 中的信息 */
	printf("TCP : %d   UDP : %d   ICMP : %d   IGMP : %d   Others : %d Total : %d\n",
	 sniffer->prot->tcp, sniffer->prot->udp,
	 sniffer->prot->icmp, sniffer->prot->igmp,
	 sniffer->prot->others, sniffer->prot->total);
}

3.show_data.c

该文件就是往log.txt中写入数据包信息:

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <netinet/ip_icmp.h>
#include <netinet/udp.h>
#include <netinet/tcp.h>
#include <netinet/ip.h>
#include <sys/socket.h>
#include <arpa/inet.h>

#include "sniffer.h"
#include "tools.h"

/* 写 IP 头部到日志文件 */
void print_ip_header(unsigned char *buf, int size, t_sniffer *sniffer)
{
	unsigned short iphdrlen;
	struct iphdr *iph;
	struct sockaddr_in source;
	struct sockaddr_in dest;

	iph = (struct iphdr *)buf;
	iphdrlen = iph->ihl*4; 
	(void)iphdrlen;
	(void)size;
	memset(&source, 0, sizeof(source));
	source.sin_addr.s_addr = iph->saddr;
  
	memset(&dest, 0, sizeof(dest));
	dest.sin_addr.s_addr = iph->daddr;
  
	fprintf(sniffer->logfile,"\n");
	fprintf(sniffer->logfile,"IP Header\n");
	fprintf(sniffer->logfile,"   |-IP Version        : %d\n",(unsigned int)iph->version);
	fprintf(sniffer->logfile,"   |-IP Header Length  : %d DWORDS or %d Bytes\n",(unsigned int)iph->ihl,((unsigned int)(iph->ihl))*4);
	fprintf(sniffer->logfile,"   |-Type Of Service   : %d\n",(unsigned int)iph->tos);
	fprintf(sniffer->logfile,"   |-IP Total Length   : %d  Bytes(size of Packet)\n",ntohs(iph->tot_len));
	fprintf(sniffer->logfile,"   |-Identification    : %d\n",ntohs(iph->id));
	fprintf(sniffer->logfile,"   |-TTL      : %d\n",(unsigned int)iph->ttl);
	fprintf(sniffer->logfile,"   |-Protocol : %d\n",(unsigned int)iph->protocol);
	fprintf(sniffer->logfile,"   |-Checksum : %d\n",ntohs(iph->check));
	fprintf(sniffer->logfile,"   |-Source IP        : %s\n",inet_ntoa(source.sin_addr));
	fprintf(sniffer->logfile,"   |-Destination IP   : %s\n",inet_ntoa(dest.sin_addr));
}

/* 写 TCP 数据包到日志文件 */
void print_tcp_packet(unsigned char *buf, int size, t_sniffer *sniffer)
{
	unsigned short iphdrlen;
	struct iphdr *iph;
	struct tcphdr *tcph;
  
	iph = (struct iphdr *)buf;
	iphdrlen = iph->ihl * 4;  
	tcph = (struct tcphdr*)(buf + iphdrlen);
	print_ip_header(buf, size, sniffer);
    
	/* 把 tcp 头信息写入日志文件中 */
	fprintf(sniffer->logfile,"\n");
	fprintf(sniffer->logfile,"TCP Header\n");
	fprintf(sniffer->logfile,"   |-Source Port      : %u\n",ntohs(tcph->source));
	fprintf(sniffer->logfile,"   |-Destination Port : %u\n",ntohs(tcph->dest));
	fprintf(sniffer->logfile,"   |-Sequence Number    : %u\n",ntohl(tcph->seq));
	fprintf(sniffer->logfile,"   |-Acknowledge Number : %u\n",ntohl(tcph->ack_seq));
	fprintf(sniffer->logfile,"   |-Header Length      : %d DWORDS or %d BYTES\n" ,(unsigned int)tcph->doff,(unsigned int)tcph->doff*4);
	fprintf(sniffer->logfile,"   |-Urgent Flag          : %d\n",(unsigned int)tcph->urg);
	fprintf(sniffer->logfile,"   |-Acknowledgement Flag : %d\n",(unsigned int)tcph->ack);
	fprintf(sniffer->logfile,"   |-Push Flag            : %d\n",(unsigned int)tcph->psh);
	fprintf(sniffer->logfile,"   |-Reset Flag           : %d\n",(unsigned int)tcph->rst);
	fprintf(sniffer->logfile,"   |-Synchronise Flag     : %d\n",(unsigned int)tcph->syn);
	fprintf(sniffer->logfile,"   |-Finish Flag          : %d\n",(unsigned int)tcph->fin);
	fprintf(sniffer->logfile,"   |-Window         : %d\n",ntohs(tcph->window));
	fprintf(sniffer->logfile,"   |-Checksum       : %d\n",ntohs(tcph->check));
	fprintf(sniffer->logfile,"   |-Urgent Pointer : %d\n",tcph->urg_ptr);
	fprintf(sniffer->logfile,"\n");
	fprintf(sniffer->logfile,"                        DATA Dump                         ");
	fprintf(sniffer->logfile,"\n");
  
	fprintf(sniffer->logfile,"IP Header\n");
	PrintData(buf, iphdrlen, sniffer);
  
	fprintf(sniffer->logfile,"TCP Header\n");
	PrintData(buf+iphdrlen, tcph->doff*4, sniffer);
  
	fprintf(sniffer->logfile,"Data Payload\n");

	/* 把用户数据写入日志文件 */
	PrintData(buf + iphdrlen + tcph->doff*4,
		(size - tcph->doff*4-iph->ihl*4),
		sniffer );
  
	fprintf(sniffer->logfile,"\n###########################################################");
}

/* 写 UDP 数据包到日志文件 */
void print_udp_packet(unsigned char *buf , int size, t_sniffer *sniffer)
{
	unsigned short iphdrlen;

	struct iphdr *iph;
	struct udphdr *udph;

	iph = (struct iphdr *)buf;
	iphdrlen = iph->ihl*4;
	udph = (struct udphdr*)(buf + iphdrlen);
	fprintf(sniffer->logfile,"\n\n***********************UDP Packet*************************\n");
  
	print_ip_header(buf, size, sniffer);
    
	/* 把 udp 头信息写入日志文件中 */
	fprintf(sniffer->logfile,"\nUDP Header\n");
	fprintf(sniffer->logfile,"   |-Source Port      : %d\n" , ntohs(udph->source));
	fprintf(sniffer->logfile,"   |-Destination Port : %d\n" , ntohs(udph->dest));
	fprintf(sniffer->logfile,"   |-UDP Length       : %d\n" , ntohs(udph->len));
	fprintf(sniffer->logfile,"   |-UDP Checksum     : %d\n" , ntohs(udph->check));
  
	fprintf(sniffer->logfile,"\n");
	fprintf(sniffer->logfile,"IP Header\n");
	PrintData(buf , iphdrlen, sniffer);
  
	fprintf(sniffer->logfile,"UDP Header\n");
	PrintData(buf+iphdrlen, sizeof(udph), sniffer);
  
	fprintf(sniffer->logfile,"Data Payload\n");

	/* 把用户数据写入日志文件 */
	PrintData(buf + iphdrlen + sizeof udph,
		(size - sizeof udph - iph->ihl * 4),
		sniffer);
  
	fprintf(sniffer->logfile,"\n###########################################################");
}

/* 写 ICMP 数据包到日志文件 */
void print_icmp_packet(unsigned char *buf , int size, t_sniffer *sniffer)
{
	unsigned short iphdrlen;
	struct iphdr	*iph;
	struct icmphdr *icmph;
  
	iph = (struct iphdr *)buf;
	iphdrlen = iph->ihl * 4;
	icmph = (struct icmphdr *)(buf + iphdrlen);

	/* 把 icmp 头信息写入日志文件中 */
	fprintf(sniffer->logfile,"\n\n***********************ICMP Packet*************************\n");  
	print_ip_header(buf , size, sniffer);
	fprintf(sniffer->logfile,"\n");
	fprintf(sniffer->logfile,"ICMP Header\n");
	fprintf(sniffer->logfile,"   |-Type : %d",(unsigned int)(icmph->type));  
	if((unsigned int)(icmph->type) == 11) 
	fprintf(sniffer->logfile,"  (TTL Expired)\n");
	else if((unsigned int)(icmph->type) == ICMP_ECHOREPLY) 
	fprintf(sniffer->logfile,"  (ICMP Echo Reply)\n");
	fprintf(sniffer->logfile,"   |-Code : %d\n",(unsigned int)(icmph->code));
	fprintf(sniffer->logfile,"   |-Checksum : %d\n",ntohs(icmph->checksum));
	fprintf(sniffer->logfile,"\n");
	fprintf(sniffer->logfile,"IP Header\n");
	PrintData(buf, iphdrlen, sniffer);
	fprintf(sniffer->logfile,"UDP Header\n");
	PrintData(buf + iphdrlen , sizeof(icmph), sniffer);
  
	fprintf(sniffer->logfile,"Data Payload\n");  

	/* 最后将用户数据写入日志文件中 */
	PrintData(buf + iphdrlen + sizeof(icmph),
		(size - sizeof(icmph) - iph->ihl * 4),
		sniffer);
  
	fprintf(sniffer->logfile,"\n###########################################################");
}

/* 写用户数据到日志文件 */
void PrintData(unsigned char *buf, int size, t_sniffer *sniffer)
{
  int i;

  for(i = 0 ; i < size ; i++)
	{
		if(i % 16 == 0)
		fprintf(sniffer->logfile, "\n");
		fprintf(sniffer->logfile, " %02X",(unsigned int)buf[i]);
      
		if( i == size - 1)
			fprintf(sniffer->logfile, "\n");
	}
}

4.tools.h和tools.c定义了颜色、信号处理函数:

#ifndef	__COLOR_H__
#define	__COLOR_H__

#include <stdio.h>

#define	CLEARSCREEN() printf("\033[H\033[2J")
#define	INITCOLOR(color) printf("\033[%sm", color)
#define	RED_COLOR "31"
#define	GREEN_COLOR	"32"
#define	YELLOW_COLOR "33"
#define	BLUE_COLOR "34"
#define	ZERO_COLOR "0"

#endif
#include <signal.h>
#include <stdio.h>

void signal_white_now(int signum)
{
	printf("Bye Bye !\n");
}

三、实验截图

1.运行结果

2.log.txt中的一个UDP数据包

四、题外话

源码中有一个.sh文件,不知是什么?查资料后知道是shell脚本文件,shell脚本就是一些命令的集合。如果实现这样的操作:

  • 进入到/tmp/目录;
  • 列出当前目录中所有的文件名;
  • 把所有当前的文件拷贝到/root/目录下;
  • 删除当前目录下所有的文件。

简单的4步在shell窗口中需要敲4次命令,按4次回车。这样是不是很麻烦?当然这4步操作非常简单,如果是更加复杂的命令设置需要几十次操作呢?那样的话一次一次敲键盘会很麻烦。所以不妨把所有的操作都记录到一个文档中,然后去调用文档中的命令,这样一步操作就可以完成。这个文档就是shell脚本。shell脚本能很方便的去管理服务器,因为可以指定一个任务计划定时去执行某一个shell脚本实现需求。

posted @ 2017-12-17 13:27  20179202杨晓桐  阅读(313)  评论(2编辑  收藏  举报