0.前言背景
当前,网络技术发展突飞猛进,网络已经渗透到生产、生活学习的各个方面,并且,人们开始越来越依赖网络技术,当前,网络技术发展突飞猛进,网络已经渗透到生产、生活学习的各个方面,并且,人们开始越来越依赖网络技术。如何较好的保护计算机中的重要数据信息不受不法分子的侵害,保护用户的计算机系统是当前网络安全方向的研究的重要问题。
学习网络中数据检测和数据分析过程,不仅有助于网络管理员排除网络中的漏洞和故障,主要是能有效的认识网络攻击的原理,常采用的技术,这样就可以有针对性的破坏网络黑客的侵入和对资料的窃取。网络探测器可以检测网络的流量,实现网络数据的检测的捕获,已经逐渐成为网络分析过程中的重要工具,对于维护网络安全和网络信息管理有着重要的意义。
1.需求分析
网络嗅探是一种利用计算机的网络接口截获其它计算机数据报文的工具,可以检测网络的流量,实现网络数据的检测的捕获,已经逐渐成为网络分析过程中的重要方法。嗅探器作为一种嗅探工具,是通过对网卡的编程来实现网络通讯的,对网卡的编程是使用通常的套接字(socket)方式来进行。但是,通常的套接字程序只能响应与自己硬件地址相配的或是以广播形式发出的数据帧,对于其他形式的数据帧比如已达到网络接口但却不是发给此地址的数据帧,网络接口在验证投递地址并非自身地址之后将不引起响应,也就是说应用程序无法收取到达的数据包。
1.1 用户需求
使用网络嗅探工具的主要人群是黑客或网络安全技术人员,从攻击的角度,黑客可以使用网络嗅探程序非法获取网络中传输的大量敏感信息,如账号和口令等,对网络安全极具威胁;从防守的角度,网络嗅探技术是居于网络的入侵检测系统的最底层环节,是整个系统的数据来源,为技术人员提供重要的依据。
无论是黑客还是安全人员,他们对抓包技术的利用途径都是一样的,即对网络上传输的数据包进行捕获与分析,获取重要的信息,但是他们的目的是不一样的,前者是专门利用计算机网络搞破坏或者恶作剧,而后者是通过对这些信息的分析利用。维护网络安全与稳定。
因此用户提出了以下要求:
1.能够捕获网络数据包,并能对数据包进行简单的分析;
2.精确的设置捕捉和过滤策略,能使用户方便、准确地捕获所需要的信息;
3.能够对网络中捕捉的数据包解码,用于故障分析;
4.用户能够自定义过滤规则,增强软件功能以及交互能力;
1.2 功能需求
对于基于Raw Socket的网络嗅探器设计与实现,网络嗅探作为网络安全方面最常见的工具被广泛使用,本次设计实现的功能有:
1.能够抓取ARP数据包
2.能够抓取TCP数据包
3.能够抓取IP数据包
4.能够抓取UDP数据包
5.能够抓取ICMP数据包
6.对抓取的数据包进行解码
1.3 性能需求
一个程序除了能正常运行并且达到相应的功能外,对程序性能也有严格的要求,性能要求决定了整个系统的性能档次、所采用的技术和设备档次,本程序除了达到常用软件对响应时间以及差错控制的要求外,还提出以下要求:
(1) 数据精确性。捕获到的数据包并不仅仅是单纯的数据信息,而是包含有IP头、TCP头等信息头的最原始的数据信息,这些信息保留了它在网络传输时的原貌,为分析网络信息提供了重要资料。
(2) 适应范围。优秀的抓包工具能够分析几百种协议。一般情况下,大多数的抓包工具至少能够分析以下的协议:IP、TCP、UDP等。
2.Raw Socket及相关知识介绍
2.1 Linux Raw Socket简介
raw socket即原始套接字,可以通过创建socket时,设置参数SOCK_RAW来创建。相比平时使用SOCK_STREAM创建的应用层的套接字,raw socket可以直接处理ip首部和tcp首部,并且可以监听抓取经过本机的ip数据包和mac帧,可以处理ICMP和IGMP等网络控制报文。如下图IP、TCP、UDP的首部示意图
2.2 原始套接字
Linux系统套接字主要分为3类: TCP套接字、UDP套接字和原始套接字。TCP套接字又称流式套接字,是建立在传输层TCP协议上的套接字;UDP套接字又称数据报套接字,是建立在传输层UDP协议上的套接字;原始套接字是一种比较特殊的套接字,虽然创建原始套接字的方法和创建TCP、UDP套接字的方法基本相同,但是其功能和TCP、UDP套接字相比却存在很大的差异。
TCP/UDP套接字只能接收和操作传输层或者传输层之上的数据报,因为当IP层把数据报往上传给传输层的时候,下层的数据报头部信息(如IP数据报头部和Ethernet 帧头部)都已经去除了。而原始套接字可以直接对数据链路层的数据报进行操作。
2.2.1 原始套接字的相关操作
(1)创建原始套接字(代码如下)
int rawsock;
rawsock = socket(domain, SOCK_RAW, protocol);
第1个参数domain表示地址族或者协议族,一般来说地址族用AF_ INET,协议族用PF_INET。在功能上,PF_INET和AF_INET是没有区别的。PF_INET中的PF代表protocol family,而AF_代表address family ,在最初设计的时候是计划让一个protocol family包含多个address family, 但是这个计划并没有实现。在套接字的头文件中有#define PF INET AF INET这样一个宏定义,因此这两个宏在数值上相等,在功能上也没有区别。如果需要原始套接字在链路层上捕获数据报,需要将该参数设置为PF_PACKET。
第2个参数为套接字类型,原始套接字对应的类型为SOCK_ RAW。
第3个参数protocol是一个常量定义,可以根据程序的需求选择相应的protocol,形式如IPPROTO_XXX。这些宏在头文件<netinet/in.h>中都有相应的定义。如果想要通过原始套接字来捕获TCP和UDP包,创建原始套接字的时候,要将该参数指定为htons (ETH_P_IP)或者htons(ETH _P_ALL)。 创建原始套接字需要超级用户权限,否则socket函数将不能成功创建原始套接字,返回-1值,同时会将errno置为EACCES。
(2)绑定和连接操作 通常情况下,原始套接字是不需要绑定操作的,但是也可以将原始套接字绑定在一个本地的地址上。原始套接字的绑定操作只是针对IP地址,而不会涉及端口。即原始套接字不会绑定在一个 固定的端口上。调用bind()函数之后,在接收数据报时,内核只会将目的IP地址为本机IP地址的数据报传递给该原始套接字。在原始套接字发送数据报时,数据报的源IP地址会自动设置为绑定的IP地址。 如果没有调用bind()函数,在接收数据报时,内核会把所有的数据报传递给该原始套接字,原始套接字发送数据报时,数据报的源IP地址会自动设置为发送接口的主IP地址。
原始套接字可以通过调用connect()函数连接套接字。同样connect()函数也只涉及IP地址,而不涉及端口号。调用connect()函数,连接成功后,原始套接字就可以通过write()和send()函数发送数据报。
(3)读写原始套接字 原始套接字的写操作通常使用sendto( )和sendmsg()函数来完成。默认情况下,系统内核将自动填充IP头部。但是如果通过setsockopt()函数设置了IP_ HDRINCL选项,则必须在程序中手动填充IP头部,内核只负责填充后的IP头部的校验和。当数据报的长度大于链路中最大传输单元MTU(Maximum Transmission Unit)时,系统内核会自动将该数据报进行分片。
原始套接字的读操作通常使用recvfrom()和recvmsg()函数。如果调用connect()函数连接成功后就可以使用recv()和read()函数来接收数据报,同时内核也只会将源地址为connect()函数连接的IP地址的数据报传递给这个原始套接字。
2.2.2 数据报的处理
当系统收到一个数据报并且需要将其传递给原始套接字时,内核将检查所有进程的所有套接字,按照以下原则寻找匹配的原始套接字,然后将数据报拷贝给所有匹配的原始套接字。
(1)原始套接字的协议域和数据报的协议域完全匹配并且为非0值,内核将数据报传递给该原始套接字。
(2) 如果原始套接字通过bind()函数绑定到一个本地的IP地址上,则内核只会将目的IP地址和套接字绑定的IP地址匹配的数据报传递给该原始套接字。
(3)如果原始套接字调用connect()函数和远程的IP地址连接,则内核只将源IP地址和该远程IP地址匹配的数据报传递给该原始套接字。
如果一个原始套接字在创建时协议域为0(即socket()函数的第3个参数为0),并且没有调用bind()和connect()函数进行绑定和连接操作,则该原始套接字将接收所有内核传递给其他原始套接字的数据报。
另外如前所述,原始套接字不能接收TCP/UDP包,只能接收ICMP包、IGMP包以及一些协议域不被系统内核理解的数据报。所以如果想要通过原始套接字来捕获TCP/UDP包,在创建原始套接字的时候,必须将第1个参数设置为PF_ PACKET,第3个参数设置为htons(ETH _P_IP)或者htons(ETH_P_ALL)。
2.3 TCP/IP网络协议栈结构
TCP/IP协议栈模型中常见的协议及其所在的位置如下图所示:
2.4 数据的封装与解析
当应用程序通过协议栈向网络传送数据时,应用层的数据报要依次传递给运输层、互联层、数据链路层,最后通过物理层进入网络。在数据报经过每一层的时候,每一层都要在数据报中增加该层的头部(和尾部)信息,以此来实现层次控制,这个过程称为数据的封装。同样,当物理层收到数据报时,需要依次传递给数据链路层、网络层、传输层,最后送至应用层。在数据报经过每一层时,每一层都需要对对应于该层的头部(和尾部)信息进行解析,最终从报文中解析出应用层数据后交给应用程序进行处理。该过程称为数据拆包或解析。下图给出了数据的封装与解析过程的示意图:
3.程序设计与实现
3.1 程序运行环境
操作系统:ubuntu 12.04(32位)
3.2 程序设计
3.2.1 程序整体框架
现代操作系统通常都提供了对底层网络数据报捕获的机制。虽然不同的操作系统实现的底层数据报捕获机制可能不一样,但是从功能上来说却是大同小异。数据报从网络传递到主机应用程序的路径依次为网卡、设备驱动层、数据链路层、网络层、传输层,最后到达应用程序。捕获的数据报是内核对该数据报的一份拷贝,这样的数据报捕获机制不会影响操作系统对数据报进行网络协议栈的处理操作。整个程序的主体框架如下图所示。
本程序主体结构主要分成3个部分,由上而下,分别是数据捕获模块、数据报过滤模块和协议解析模块。首先由数据捕获模块从网络中捕获数据报,然后在数据报过滤模块中按照设定的过滤规则将数据报过滤,最后再将过滤后的数据报通过协议解析模块解析显示出来。
原始套接字不仅可以捕获数据报,还可以发送指定的数据报。但是本程序中只需要用到套接字捕获数据报的功能。通过原始套接字捕获数据报的流程如下图
3.2.2 程序具体设计
3.2.2.1 inetheader.h关键代码
在这个头文件中,主要定义了以太帧中不同类型数据包的结构,后续的程序设计将会以这些结构体为基础,进行流量分析。关键代码包括:
帧首部结构体
typedef struct ether_header_t{
BYTE des_hw_addr[6]; //目的地址
BYTE src_hw_addr[6]; //源地址
WORD frametype; //帧格式
} ether_header_t;
各种协议的结构体,这里以arp为例
typedef struct arp_header_t{
WORD hw_type;
WORD prot_type;
BYTE hw_addr_len;
BYTE prot_addr_len;
WORD flag;
BYTE send_hw_addr[6];
DWORD send_prot_addr;
BYTE des_hw_addr[6];
DWORD des_prot_addr;
} arp_header_t;
arp数据包的结构体
typedef struct arp_packet_t{
ether_header_t etherheader;
arp_header_t arpheader;
} arp_packet_t;
3.2.2.2 rawsocket.h关键代码
定义了一个rawsocket类,实现初始化,抓包,设置混杂模式等功能。
class rawsocket
{
private:
int sockfd;
public:
rawsocket(const int protocol);
~rawsocket() ;
bool dopromisc(char *nif); //设置混杂模式
int receive(char *recvbuf,int buflen,struct sockaddr_in *from,int *addrlen); //这里是rawsocket抓包的关键
};
3.2.2.3 rawsocsniffer.h 关键代码
过滤器结构体
typedef struct filter
{
unsigned long sip;
unsigned long dip;
unsigned int protocol;
} filter;
嗅探器结构体
class rawsocsniffer:public rawsocket
{
private:
filter simfilter;
char *packet;
const int max_packet_len;
public:
rawsocsniffer(int protocol);
~rawsocsniffer();
bool init();
void setfilter(filter myfilter);
bool testbit(const unsigned int p, int k);
void setbit(unsigned int &p,int k);
void sniffer();
void analyze();
void ParseARPPacket();
3.2.2.4 rawsocket.c 关键代码
rawsocket函数的实现:
rawsocket::rawsocket(const int protocol)
{
sockfd=socket(PF_PACKET,SOCK_RAW,protocol);
if(sockfd<0)
{
perror("socket error: ");
}
}
混杂模式函数dopromisc()的实现:
bool rawsocket::dopromisc(char*nif)
{
struct ifreq ifr;
strncpy(ifr.ifr_name, nif,strlen(nif)+1);
if((ioctl(sockfd, SIOCGIFFLAGS, &ifr) == -1))
{
perror("ioctlread: ");
return false;
}
ifr.ifr_flags |= IFF_PROMISC;
if(ioctl(sockfd, SIOCSIFFLAGS, &ifr) == -1 )
{
perror("ioctlset: ");
return false;
}
return true;
}
接收消息receive()函数的实现:
int rawsocket::receive(char *recvbuf,int buflen, struct sockaddr_in *from,int *addrlen)
{
int recvlen;
recvlen=recvfrom(sockfd,recvbuf,buflen,0,(struct sockaddr *)from,(socklen_t *)addrlen);
recvbuf[recvlen]='\0';
return recvlen;
3.2.2.5 rawsocsniffer.c 关键代码
rawsocsniffer()嗅探函数关键代码
rawsocsniffer::rawsocsniffer(int protocol):rawsocket(protocol),max_packet_len(2048)
{
packet=new char[max_packet_len];
memset(&simfilter,0,sizeof(simfilter));
}
设置网卡混杂模式,进行嗅探器初始化。
bool rawsocsniffer::init()
{
dopromisc("eth0");
}
设置嗅探器接收数据包,然后分析数据包。
void rawsocsniffer::sniffer()
{
struct sockaddr_in from;
int sockaddr_len=sizeof(struct sockaddr_in);
int recvlen=0;
while(1)
{
recvlen=receive(packet,max_packet_len,&from,&sockaddr_len);
if(recvlen>0)
{
analyze();
}
else
{
continue;
}
}
}
analyze()分析函数,对数据包进行初步分析,以arp数据包为例,介绍初步分析,与详细分析。其他协议的数据包分析与之类似。
void rawsocsniffer::analyze()
{
ether_header_t *etherpacket=(ether_header_t *)packet;
if(simfilter.protocol==0)
simfilter.protocol=0xff;
switch (ntohs(etherpacket->frametype))
{
case 0x0800:
...
case 0x0806:
if(testbit(simfilter.protocol,1))
{
cout<<"\n\n/*--------------arp packet--------------------*/"<<endl;
ParseARPPacket();
}
break;
case 0x0835:
...
default:
cout<<"\n\n/*--------------Unknown packet----------------*/"<<endl;
cout<<"Unknown ethernet frametype!"<<endl;
break;
}
}
ParseARPPacket()函数,对捕获到的ARP数据包的各个字段进行解析
void rawsocsniffer::ParseARPPacket()
{
arp_packet_t *arppacket=(arp_packet_t *)packet;
print_hw_addr(arppacket->arpheader.send_hw_addr);
print_hw_addr(arppacket->arpheader.des_hw_addr);
cout<<endl;
print_ip_addr(arppacket->arpheader.send_prot_addr)
print_ip_addr(arppacket->arpheader.des_prot_addr);
cout<<endl;
cout<<setw(15)<<"Hardware type: "<<"0x"<<hex<<ntohs(arppacket->arpheader.hw_type);
cout<<setw(15)<<" Protocol type: "<<"0x"<<hex<<ntohs(arppacket->arpheader.prot_type);
cout<<setw(15)<<" Operation code: "<<"0x"<<hex<<ntohs(arppacket->arpheader.flag);
cout<<endl;
}
3.2.2.6 main.c关键代码
switch-case结构判断用户输入参数,执行相应的代码
switch (ch)
{
case 'h':
...
exit(0);
case 's':
myfilter.sip=inet_addr(optarg);
break;
case 'd':
myfilter.dip=inet_addr(optarg);
break;
case 'a':
sniffer.setbit((myfilter.protocol),1);
break;
case 't':
sniffer.setbit((myfilter.protocol),2);
break;
case 'u':
sniffer.setbit((myfilter.protocol),3);
break;
case 'i':
sniffer.setbit((myfilter.protocol),4)
break;
default:
break;
}
设置嗅探器及嗅探器初始化
sniffer.setfilter(myfilter);
if(!sniffer.init())
{
cout<<"sniffer initialize error!"<<endl;
exit(-1);
}
sniffer.sniffer();
3.2.2.7 makefile文件
OBJECT=main.o rawsocket.o rawsocsniffer.o
CC=g++
main:$(OBJECT)
$(CC) -o $@ $^
%.o: %.c
$(CC) -c $<
clean:
rm $(OBJECT)
4.测试
4.1 编译
基于RawSocket的数据包过滤程序,共包含7个文件:inetheader.h、rawsocsniffer.h、rawsocket.h、rawsocket.c、rawsocsniffer.c、main.c、makefile。 编译:命令行模式下,切换到代码所在目录,输入make即可编译。编译后将生成一个可执行文件main,以及一些obj文件。 运行:命令行下输入 ./main [-s 源IP] [-d 目的IP] [-atui] 即可运行程序,可以通过设置不同的参数可以实现简单的过滤功能。参数功能如下:
s是根据源IP地址过滤数据包
d是根据目的IP地址过滤数据包
a是过滤ARP包
t是过滤TCP包
u是过滤UDP包
i是过滤ICMP包
h是显示帮助菜单
命令行模式下,切换到代码所在目录,输入make即可编译。编译后将生成一个可执行文件main,以及一些obj文件。如下图所示:可以看到生成了main文件
4.2 测试运行
4.2.1 测试一
命令行下输入 ./main -h查看用法
4.2.2 测试二
局域网内捕获ARP包,在此模拟主机受到ARP攻击时候的情形。攻击机使用kali Linux
可以在主机上也就是ubuntu上,使用命令 ./main -a,捕获arp数据包,并解析
可以看到,过滤出的协议以及ip地址是与实际情况相符的。
4.2.3 测试三
局域网内指定源IP和目的IP,进行抓包,同时用两台主机,对目标机进行ping操作,过滤出其中的一种ICMP包。以下是ip地址为192.168.112.129使用ping命令,查看主机信息。
以下是ip地址为192.168.112.128使用ping命令,查看主机信息。
现在在终端输入./main -s 192.168.112.135 -d 192.168.112.129 -i,捕获从ip地址192.168.112.135到192.168.112.129的ICMP的数据包。可以看到,捕获了两种ICMP是过滤出来只有一种,而且是从ip地址192.168.112.135到192.168.112.129的ICMP的数据包
此外其他的测试与上述提到的类似,在此为避免冗余,我将其录制了一个视频,可以在视频中看到一些功能测试的结果。视频地址https://www.bilibili.com/video/av33616430/
5.展望
随着网络技术的迅猛发展,网络安全将被人们越来越重视,嗅探技术作为网络安全攻防中最基础的技术,嗅探工具的发展将向着集成化和易用型发展这两个方向发展,我们相信这也是绝大部分软件的发展趋势。
需求是创新的动力,内在的驱动更是创新的源泉,没有任何事物是至善的,此程序还有一些可以增强的地方,除了上面提到的集成化和易用型外,还可以将界面美化,程序的功能也可以添加鼠标响应事件更加个性化,此外还可以增加自动搜索局域网内的主机功能,通过直接点击特定主机图标达到设置过滤条件的目的,简化操作。
6.参考文献
[1] 吴功宜 . 网络安全高级软件编程技术. 北京. 清华大学出版社,2010
[2] 倪继利 . Linux安全体系分析与编程. 北京. 电子工业出版社,2007
[3] 刘永华 . 网络信息安全技术[M].第2版. 北京. 科学出版社,2011
[4] Atul Kahate. Cryptography And Network Serurity (2nd Edition).McGraw-hill,2008