嗅探器.docx
《嗅探器.docx》由会员分享,可在线阅读,更多相关《嗅探器.docx(24页珍藏版)》请在冰豆网上搜索。
嗅探器
1实验题目
基于TCP/IP的嗅探器设计
2实验目的
●掌握有关网络通信基本原理,请参照有关书籍。
●掌握嗅探器的程序设计。
3实验条件和环境
●WindowsXPSP3
●VisualC++6.0
4实验方法(系统功能、结构设计,软件流程图等)
●嗅探器原理
嗅探器作为一种网络通讯程序,也是通过对网卡的编程来实现网络通讯的,对网卡的编程也是使用通常的套接字(socket)方式来进行。
目前大型的企业以及网吧,大部分用的都是hub(集线器)或者Switch(交换机)组建的,我们分别对这两种环境进行介绍。
1)Hub环境
Hub(集线器)网络是基于共享原理的,当一台机器向另一台机器发送数据,共享Hub会先收到此信息,然后把它接收到的数据再发给其他的(来的那个端口不发了)每一个端口。
也就是说,局域网内除了发送数据的那台计算机以外的所有计算机的网卡都接受相同的数据包,计算机的网卡先接受此数据包的目的MAC地址,根据网卡的驱动程序的接收模式判断数据包是不是发给自己的,如果是就接收,如果不是则丢弃,即被网卡截断了,从而计算机也就收不到这个不属于自己的数据包,这是“老实人”的行为。
但是,黑客通常不是我们所说的“老实人”,他们会先将网卡设置为“混杂模式”,这样网卡接收到的数据包不管是不是自己的都会接收,然后再进行分析,这样就构成了嗅探环境,并开始嗅探。
2)Switch环境
用交换机(Switch)组建的网络,顾名思义是基于“交换”原理的。
交换机和集线器不同,上面我们讲到集线器是把数据包发给一个域内的所有机器的端口上,然后通过网卡进行识别;而交换机没有把数据包发送到所有的端口上,而是直接发送到目的网卡所在的端口,这样看来,交换所组建的网络是比较安全的,而对于黑客来说这样嗅探起来会麻烦一些;但是聪明的黑客利用带有“ARP欺骗”的嗅探程序,通过改变MAC地址等手段,欺骗交换机将数据包发给自己,分析完毕再转发出去,这也同样实现了上面所说的“不是老实人”的做法。
嗅探工具可以是软件,也可以是硬件,是软件就要分平台,有基于Windows下的,也有基于UNIX下的,不同的嗅探程序可以生存在不同的操作系统上;硬件的Sniffer成为“网络分析仪”软件。
总之不管硬件还是软件,目标只有一个,就是获取在网络上传输的各种信息,也就是接收不属于自己的部分数据。
基于交换环境的嗅探的工作原理是什么呢?
在以太网中,所有的通信都是通过广播的形式进行的,也就是说通常在同一个网段的所有网络接口都可以访问在域中(物理媒体上)传输的所有数据,而每一个网络接口都有一个唯一的硬件地址,这个硬件地址也就是网卡的MAC地址(大多数系统使用48比特的地址),这个地址用来表示网络中的每一台设备。
一般来说,每一块网卡上的MAC地址都是不同的,每个网卡生产厂商在获得一段地址后,再将这段地址分配给其生产的每个网卡一个地址。
在硬件地址和IP地址间使用ARP和RARP协议进行相互交换。
具体到编程实现上,这种对网卡混杂模式的设置是通过原始套接字(rawsocket)来实现的,这也有别于通常经常使用的数据流套接字和数据报套接字。
在创建了原始套接字后,需要通过setsockopt()函数来设置IP头操作选项,然后再通过bind()函数将原始套接字绑定到本地网卡。
为了让原始套接字能接受所有的数据,还需要通过ioctlsocket()来进行设置,而且还可以指定是否亲自处理IP头。
至此,实际就可以开始对网络数据包进行嗅探了,对数据包的获取仍像流式套接字或数据报套接字那样通过recv()函数来完成。
但是与其他两种套接字不同的是,原始套接字此时捕获到的数据包并不仅仅是单纯的数据信息,而是包含有IP头、TCP头等信息头的最原始的数据信息,这些信息保留了它在网络传输时的原貌。
通过对这些在低层传输的原始信息的分析可以得到有关网络的一些信息。
由于这些数据经过了网络层和传输层的打包,因此需要根据其附加的帧头对数据包进行分析。
数据包的总体结构:
数据包
IP头
TCP头(或其他信息头)
数据
数据在从应用层到达传输层时,将添加TCP数据段头,或是UDP数据段头。
其中UDP数据段头比较简单,由一个8字节的头和数据部分组成,具体格式如下:
16位
16位
源端口
目的端口
UDP长度
UDP校验和
而TCP数据头则比较复杂,以20个固定字节开始,在固定头后面还可以有一些长度不固定的可选项,下面给出TCP数据段头的格式组成:
16位
16位
源端口
目的端口
顺序号
确认号
TCP头长
保留7位
URG
ACK
PSH
RST
SYN
FIN
窗口大小
校验和
紧急指针
可选项(0或更多的32位字)
数据(可选项)
对于此TCP数据段头的分析在编程实现中可通过数据结构_TCP来定义:
typedefstruct_tcphdr//定义TCP首部
{
USHORTth_sport;//16位源端口
USHORTth_dport;//16位目的端口
unsignedintth_seq;//32位序列号
unsignedintth_ack;//32位确认号
unsignedcharth_lenres;//4位首部长度/6位保留字
unsignedcharth_flag;//6位标志位
USHORTth_win;//16位窗口大小
USHORTth_sum;//16位校验和
USHORTth_urp;//16位紧急数据偏移量
}TCP_HEADER;
在网络层,还要给TCP数据包添加一个IP数据段头以组成IP数据报。
IP数据头以大端点机次序传送,从左到右,版本字段的高位字节先传输(SPARC是大端点机;Pentium是小端点机)。
如果是小端点机,就要在发送和接收时先行转换然后才能进行传输。
IP数据段头格式如下:
16位
16位
版本
IHL
服务类型
总长
标识
标志
分段偏移
生命期
协议
头校验和
源地址
目的地址
选项(0或更多)
同样,在实际编程中也需要通过一个数据结构来表示此IP数据段头,下面给出此数据结构的定义:
typedefstruct_iphdr//定义IP头部
{
unsignedcharh_lenver;//4位首部长度+4位IP版本号
unsignedchartos;//8位服务类型TOS
unsignedshorttotal_len;//16位总长度(字节)
unsignedshortident;//16位标识
unsignedshortfrag_and_flags;//3位标志位
unsignedcharttl;//8位生存时间TTL
unsignedcharproto;//8位协议(TCP,UDP或其他)
unsignedshortchecksum;//16位IP首部校验和
unsignedintsourceIP;//32位源IP地址
unsignedintdestIP;//32位目的IP地址
}IP_HEADER;
在明确了以上几个数据段头的组成结构后,就可以对捕获到的数据包进行分析了。
●网络嗅探器的设计
1)系统需求
有于水平有限,不可能实现像sniffer或者影音神探那样复杂的设置和分析,所以我们只对抓取到的本机在网络中的通信数据(如协议类型,源、目的地址和端口、数据包的大小等)加以分析,实现一个简单的网络嗅探器。
一个窗体显示主页面,另一个页面显示详细的包信息。
详细信息页面,我们显示一下信息:
源端口:
源目的IP地址+端口号;
目的端口:
目的IP地址+端口号;
协议类型;
版本信息;
生存时间;
报头大小:
报文报首部大小;
报文总长:
整个数据报的大小;
2)分析程序交互
主要任务是:
对问题领域进行抽象,提出解决方案:
对未来的系统进行需求分析,确定系统的职责范围、功能需求、性能需求、应用环境及假设条件等。
主要交互图如下所示:
3)程序流程
·打开文件描述字。
打开网络接口、网络套接字,得到一个文件描述字,以后所有的控制和读、写都将针对该文件描述字。
·设置混杂模式。
将网卡接口设置为混杂模式,使之接收所有流经网络介质的信息包。
·设置缓冲区、取样时间、抓取长度等。
缓冲区用来存放从内核缓冲区拷贝过来的网络包,设置它的大小。
取样时间指如果内核缓冲区有数据待读但没有满,系统等待多长时间才向用户进程发送“就绪”通知,并且用户进程把数据从内核缓冲区拷贝到用户缓冲区。
这样可能造成过于频繁的通知和拷贝,增加系统负担,降低效率,所以要适当设置取样时间。
抓取长度指从内核拷贝空间到用户空间的最大网络包长,超过该长度的包被截短,其目的是提高处理效率。
·设置过滤器:
过滤器使用内核只取那些攻击者感兴趣的网络包,而不是网络介质的网络包,可以减少不必要的拷贝和处理。
·读取包:
从文件描述字中读取数据,通常指数据链路层的帧,即以太网帧。
过滤、分析、解释、输出:
如果内核没有提供过滤功能,只能把所有的网络包从内核空间拷贝到用户空间,然后由用户进程分析和过滤,主要是分析以太包头和TCP/IP包头中的信息,如:
数据长度、源IP、目的IP、协议类型(TCP、UDP、ICMP)、源端口、目标端口等,选择用户感兴趣的网络数据包,然后对应用层协议级的数据进行解释,把原始数据转化成用户可理解的方式输出。
流程图如下:
4)原理分析
底层的数据获取可以通过两种方式实现:
一种是利用以太网的广播特性,另一种是通过设置路由器的监听端口来实现。
现在采用第一种方式实现。
以太网的数据传输是通过广播实现的,在局域网中的所有网络端口都有访问在物理媒体上传输的所有数据的能力,在一般情况下,应用程序只能接收到到达本机的数据。
要捕获到流经网卡但不属于本机的数据,必须绕过这一正常的方式,直接访问网络底层。
首先要将网卡的工作模式设置为“混杂模式”,当网卡工作在这种模式下,就具有了接收所有到达网卡数据的能力,它对所有接收到的数据帧都产生硬件中断,提醒操作系统处理流经网卡的所有报文。
操作系统直接访问数据链路层,获取相关数据,这样就可以获取到流经网卡的所有数据。
Windows中对数据链路层的访问机制是基于NDIS(NetworkDriverInterfaceSpecification,网络驱动接口规范)的。
NDIS是由Microsoft和3Com公司开发的用于通信协议程序和网络设备驱动程序相互通信的Windows规范。
NDIS为传输层提供了一种通用的接口函数,所有的传输层驱动程序都要通过NDIS访问网络。
应用程序对NIC的访问必须经过调用NDIS接口实现,NIDS向上层提供一个protocol接口,向下提供一个miniport接口。
NDIS驱动程序通常需要向NDIS注册一组进程,NDIS在适当的时候能调用驱动程序注册的例程,而驱动程序可以在这些进程里面进行相应的处理。
一般来说,如果驱动程序需要在上边缘与NDIS通信,那么它需要向NDIS库注册一组MiniportXxx例程,使得该驱动程序对于更上层的驱动程序来说表项得好像一个MiniportDriver;如果驱动程序要做下边缘与NDIS通信,那么它需要向NDIS库注册一组ProtocolXxx进程,使得该驱动对于下面的驱动程序来说表现得好像一个ProtocolDriver。
处于NDIS最高层的是传输驱动程序(Protocol),Windows允许多个ProtocolDriver并存于这一层。
典型的TCP/IP实现模块tcpip.sys和其他一些协议的实现模块也是位于这一层的。
传输驱动程序可以注册为传输提供者,从而为上层的TDI客户提供服务。
TDI客户与传输驱动之间采用特定的机制进行通信。
WIN32平台不提供直接的网络底层访问接口,必须通过VxD(VirtualDeviceDriver,虚拟设备驱动)实现侦听的功能,在侦听应用程序和NDIS之间插入一个VxD来获取数据。
VxD提供外部程序和网卡NIC之间的接口。
Windows平台下地数据捕获原理如下图:
5实验结果及结论
程序运行在windowsXP的环境下
捕获TCP包过程:
捕获UDP包过程:
捕获ICMP包过程:
6附录:
程序清单及说明
程序代码如下:
#include
#include
#include
#include"mstcpip.h"
#pragmacomment(lib,"ws2_32.lib")
#defineSTATUS_FAILED0xFFFF//定义异常出错代码
#defineMAX_PACK_LEN65535//接收的最大IP报文
#defineMAX_ADDR_LEN16//点分十进制地址的最大长度
#defineMAX_PROTO_TEXT_LEN16//子协议名称(如"TCP")最大长度
#defineMAX_PROTO_NUM12//子协议数量
#defineMAX_HOSTNAME_LAN255//最大主机名长度
#defineCMD_PARAM_HELPtrue
typedefstruct_iphdr//定义IP头部
{
unsignedcharh_lenver;//4位首部长度+4位IP版本号
unsignedchartos;//8位服务类型TOS
unsignedshorttotal_len;//16位总长度(字节)
unsignedshortident;//16位标识
unsignedshortfrag_and_flags;//3位标志位
unsignedcharttl;//8位生存时间TTL
unsignedcharproto;//8位协议(TCP,UDP或其他)
unsignedshortchecksum;//16位IP首部校验和
unsignedintsourceIP;//32位源IP地址
unsignedintdestIP;//32位目的IP地址
}IP_HEADER;
typedefstruct_tcphdr//定义TCP首部
{
USHORTth_sport;//16位源端口
USHORTth_dport;//16位目的端口
unsignedintth_seq;//32位序列号
unsignedintth_ack;//32位确认号
unsignedcharth_lenres;//4位首部长度/6位保留字
unsignedcharth_flag;//6位标志位
USHORTth_win;//16位窗口大小
USHORTth_sum;//16位校验和
USHORTth_urp;//16位紧急数据偏移量
}TCP_HEADER;
typedefstruct_udphdr//定义UDP首部
{
unsignedshortuh_sport;//16位源端口
unsignedshortuh_dport;//16位目的端口
unsignedshortuh_len;//16位长度
unsignedshortun_sum;//16位校验和
}UDP_HEADER;
typedefstruct_icmphdr//定义ICMP首部
{
BYTEi_type;//8位类型
BYTEi_code;//8位代码
USHORTi_cksum;//16位校验和
USHORTi_id;//识别号(一般用进程号作为识别号)
USHORTi_seq;//报文序列号
ULONGtimestamp;//时间戳
}ICMP_HEADER;
typedefstruct_protomap//定义子协议映射表
{
intProtoNum;
charProtoText[MAX_PROTO_TEXT_LEN];
}PROTOMAP;
PROTOMAPProtoMap[MAX_PROTO_NUM]={//为子协议映射表赋值
{IPPROTO_IP,"IP"},
{IPPROTO_ICMP,"ICMP"},
{IPPROTO_IGMP,"IGMP"},
{IPPROTO_GGP,"GGP"},
{IPPROTO_TCP,"TCP"},
{IPPROTO_PUP,"PUP"},
{IPPROTO_UDP,"UDP"},
{IPPROTO_IDP,"IDP"},
{IPPROTO_ND,"NP"},
{IPPROTO_RAW,"RAW"},
{IPPROTO_MAX,"MAX"},
{NULL,""}};
SOCKETSockRaw;
charTcpFlag[6]={'F','S','R','P','A','U'};//定义TCP标志位
boolParamTcp=false;//关注TCP报文
boolParamUdp=false;//关注UDP报文
boolParamIcmp=false;//关注ICMP报文
boolParamDecode=true;//对协议进行解码
char*strFromIpFilter=NULL;//源IP地址过滤
char*strDestIpFilter=NULL;//目的地址过滤
intDecodeIpPack(char*,int);//IP解码函数
intDecodeTcpPack(char*);//TCP解码函数
intDecodeUdpPack(char*);//UDP解码函数
intDecodeIcmpPack(char*);//ICMP解码函数
voidGetFtp(char*);
voidCheckSockError(int,char*);//错误检测函数
char*CheckProtocol(int);//协议检测函数
voidusage(void);//帮助函数
boolGetCmdLine(int,char**);//获取命令行函数
voidmain(intargc,char**argv)
{
intiErrorCode;
charRecvBuf[MAX_PACK_LEN]={0};//接收缓冲区
usage();//调用使用说明
if(GetCmdLine(argc,argv)==CMD_PARAM_HELP)exit(0);
//初始化SOCKET,判断命令行接收函数
WSADATAwsaData;
iErrorCode=WSAStartup(MAKEWORD(2,1),&wsaData);
CheckSockError(iErrorCode,"WSAStartup");
//建立Socket。
属性必须是AF_INET(Ipv4协议)、
//SOCK_RAW(原始套接字,即底层IP)、IPPROTO_IP(自己构造IP头),
//否则不能设子SIP_RCVALL(混杂模式)属性
SockRaw=socket(AF_INET,SOCK_RAW,IPPROTO_IP);
CheckSockError(SockRaw,"socket");
//获取本机IP地址,并判断socket版本,建立原始套接字
charFARname[MAX_HOSTNAME_LAN];
iErrorCode=gethostname(name,MAX_HOSTNAME_LAN);//得到本地主机名
CheckSockError(iErrorCode,"gethostname");
structhostentFAR*pHostent;
pHostent=(structhostent*)malloc(sizeof(structhostent));
pHostent=gethostbyname(name);//获得给定主机名信息
SOCKADDR_INsa;
sa.sin_family=AF_INET;//设置协议族
sa.sin_port=htons(6000);//设置端口
memcpy(&sa.sin_addr.S_un.S_addr,pHostent->h_addr_list[0],pHostent->h_length);
//绑定本机IP地址。
首先获得本机主机名,在根据本地名获得本地IP地址。
//注意不能使用参数INADDR_ANY,而是PSOCKADDR。
iErrorCode=bind(SockRaw,(PSOCKADDR)&sa,sizeof(sa));//绑定
CheckSockError(iErrorCode,"bind");
//设置SOCK_RAW为SIO_RCVALL,以便接收所有的IP包
DWORDdwBufferLen[10];
DWORDdwBufferInLen=1;
DWORDdwBytesReturned=0;
//设置混杂模式。
下面使用WSAIcotl函数给一个SOCK_RAW类型的socket设置
//SIO_RCVALL混杂模式,这样该socket就可以收到所以经过本机的数据
iErrorCode=WSAIoctl(SockRaw,SIO_RCVALL,&dwBufferInLen,sizeof(dwBufferInLen),&dwBufferLen,sizeof(dwBufferLen),&dwBytesReturned,NULL,NULL);
CheckSockError(iErrorCode,"Ioctl");
//侦听IP报文
while
(1)//1永远为真,不断循环抓包
{
memset(RecvBuf,0,sizeof(RecvBuf));//强制RecvBuf缓冲区数据为0
//获取数据包,开始接收缓冲区数据
iErrorCode=recv(SockRaw,RecvBuf,sizeof(RecvBuf),0);
CheckSockError(iErrorCode,"recv");
iErrorCode=DecodeIpPack(RecvBuf,iErrorCode);//开始从缓冲区解码
CheckSockError(iErrorCode,"Decode");
}
}
//IP解包程序
intDecodeIpPack(char