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脚本实现需求。

2017-2018-1 20179202《Linux内核原理与分析》第十二周作业的更多相关文章

  1. 2018-2019-1 20189221 《Linux内核原理与分析》第八周作业

    2018-2019-1 20189221 <Linux内核原理与分析>第八周作业 实验七 编译链接过程 gcc –e –o hello.cpp hello.c / gcc -x cpp-o ...

  2. 2018-2019-1 20189221 《Linux内核原理与分析》第七周作业

    2018-2019-1 20189221 <Linux内核原理与分析>第七周作业 实验六 分析Linux内核创建一个新进程的过程 代码分析 task_struct: struct task ...

  3. 2018-2019-1 20189221 《Linux内核原理与分析》第六周作业

    2018-2019-1 20189221 <Linux内核原理与分析>第六周作业 实验五 实验过程 将Fork函数移植到Linux的MenuOS fork()函数通过系统调用创建一个与原来 ...

  4. 2018-2019-1 20189221《Linux内核原理与分析》第五周作业

    2018-2019-1 20189221<Linux内核原理与分析>第五周作业 实验四 实验过程 当用户态进程调用一个系统调用时,cpu切换到内核态并开始执行一个内核函数. 在Linux中 ...

  5. 2018-2019-1 20189221《Linux内核原理与分析》第三周作业

    2018-2019-1 20189221<Linux内核原理与分析>第三周作业 实验二 完成一个简单的时间片轮转多道程序内核代码 实验过程 在实验楼中编译内核 编写mymain.c函数和m ...

  6. 2019-2020-1 20199329《Linux内核原理与分析》第十三周作业

    <Linux内核原理与分析>第十三周作业 一.本周内容概述 通过重现缓冲区溢出攻击来理解漏洞 二.本周学习内容 1.实验简介 注意:实验中命令在 xfce 终端中输入,前面有 $ 的内容为 ...

  7. 2019-2020-1 20199329《Linux内核原理与分析》第十一周作业

    <Linux内核原理与分析>第十一周作业 一.本周内容概述: 学习linux安全防护方面的知识 完成实验楼上的<ShellShock 攻击实验> 二.本周学习内容: 1.学习& ...

  8. 2019-2020-1 20199329《Linux内核原理与分析》第八周作业

    <Linux内核原理与分析>第八周作业 一.本周内容概述: 理解编译链接的过程和ELF可执行文件格式 编程练习动态链接库的两种使用方式 使用gdb跟踪分析一个execve系统调用内核处理函 ...

  9. 2019-2020-1 20199329《Linux内核原理与分析》第七周作业

    <Linux内核原理与分析>第七周作业 一.本周内容概述: 对Linux系统如何创建一个新进程进行追踪 分析Linux内核创建一个新进程的过程 二.本周学习内容: 1.学习进程的描述 操作 ...

  10. 2019-2020-1 20199329《Linux内核原理与分析》第六周作业

    <Linux内核原理与分析>第六周作业 一.本周内容概述: 学习系统调用的相关理论知识,并使用库函数API和C代码中嵌入汇编代码两种方式使用getpid()系统调用 学习系统调用syste ...

随机推荐

  1. highCharts使用记录

    公司的架构师让我做一个mockup,要用到highCharts,,以前想接触的,没时间学习,也没有用过,正好工作可以用上了,可以边学边做了. 环境 <script src="./js/ ...

  2. 另类之将ipython notebook嵌入blog方法

    另类之将ipython notebook嵌入blog方法 ipynb文件很强大. 可是一直苦于没有找到好的方法把它直接嵌入到博文里. 现在得到一个另类的方法: 就是利用github集成了nbviewe ...

  3. Arcgis10.1 Arcobject连接Oracel数据库

    原来使用Arcgis9.3的版本,现在升级到了10.1遇到不少问题,原来初始化工作空间的代码无法正常运行了,修改后的代码如下: static void Test() { IPropertySet sd ...

  4. HDU 6096 String (AC自动机)

    题目链接 Problem Description Bob has a dictionary with N words in it. Now there is a list of words in wh ...

  5. php Only variables should be passed by reference 报错问题

    这个错误是变量引用引起的非致命错误,可修改php.ini文件的error_reporting = E_ALL & E_NOTICE 使其屏蔽此错误

  6. 树形dp(B - Computer HDU - 2196 )

    题目链接:https://cn.vjudge.net/contest/277955#problem/B 题目大意:首先输入n代表有n个电脑,然后再输入n-1行,每一行输入两个数,t1,t2.代表第(i ...

  7. equals方法变量和常量位置区别

    对于字符串比较,我的习惯用法是   变量.equals(常量) 比如:     a.equals("a") 今天看视频才知道变量在前面与后面有很大影响,正确的写法是常量放前面(可以 ...

  8. aarch64_g2

    ghc-cryptonite-devel-0.21-1.fc26.aarch64.rpm 2017-02-28 01:28 3.1M fedora Mirroring Project ghc-css- ...

  9. c# List 所有操作方法例子

    using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Ref ...

  10. Python基础:内置类型(未完待续)

    本文根据Python 3.6.5的官文Built-in Types而写. 目录 1.真值测试 2.布尔操作 -- and, or, not 3.比较 4.数字型 -- int, float, comp ...