目 录
1实验要求… 1
2实验内容 …1
2.1背景… 1
2.2基本概念和原理 …2
3设计思路 …5
3.1 设计流程图 …5
3.2 主要代码… 5
4运行与测试… 10
5总结 …11
参考文献…13
附录(代码)…14
在理解网络分层协议的基础上,设计实现一个网络嗅探器。能够实现网络层抓包,并对获得的数据包的源和目的地址、端口、协议等进行分析,并给出简单明了的分析统计结果,软件界面设计友好。
该软件能够以下基本功能:能列出监测主机的所有网卡,选择一个网卡,设为混杂工作模式;能捕获所有流经网卡的数据包,并利用Wincap函数库设置过滤规则;能分析捕获的数据包的包头和数据,按照各种协议格式进行格式化显示;将个人所开发工具的捕获和分析结果与常用的wireshark或sniffer进行比较,完善程序代码。
2.1背景
为了使不同体系结构的计算机网络都能进行互联,国际标准化组织ISO于1997年成立了专门的结构研究这个问题。不久他们就提出了一个试图使各种计算机在世界范围内都能互联的成网的标准框架,即著名的OSI/RM(Open Systems Interconnection Reference Model , 开放系统互联基本参考模型),简称为OSI。它的主要目标是:只要遵循OSI标准,一个系统就可以和位于世界任何地方的,也遵循同一标准的其他任何系统进行通信。
OSI采用分层结构,每一层完成不同的功能,较好地从理论上解决了不同系统之间互联的难题。但是由于OSI模型归于复杂以及种种原因,当它被制定出来时,TCP/IP已经成为事实上的国际标准。
从事网络安全的技术人员和相当一部分准黑客(指那些使用现成的黑客软件进行攻击而不是根据需要去自己编写代码的人)都一定不会对网络嗅探器(sniffer)感到陌生,网络嗅探器无论是在网络安全还是在黑客攻击方面均扮演了很重要的角色。通过使用网络嗅探器可以把网卡设置于混杂模式,并可实现对网络上传输的数据包的捕获与分析。此分析结果可供网络安全分析之用,但如为黑客所利用也可以为其发动进一步的攻击提供有价值的信息。可见,嗅探器实际是一把双刃剑。 虽然网络嗅探器技术被黑客利用后会对网络安全构成一定的威胁,但嗅探器本身的危害并不是很大,主要是用来为其他黑客软件提供网络情报,真正的攻击主要是由其他黑软来完成的。而在网络安全方面,网络嗅探手段可以有效地探测在网络上传输的数据包信息,通过对这些信息的分析利用是有助于网络安全维护的。权衡利弊,有必要对网络嗅探器的实现原理进行介绍。
2.2基本原理和概念
一.TCP/IP 的4层结构
通常意义上的TCP/IP结构共有4层协议,从下到上依次是网络协议层,互联协议层,传输层和应用层,不同的层次上运行的协议也不同。
1.网络协议层
网络协议层工作在TCP/IP模型的最底层,它负责将数据通过电缆传送出去,并将接收到的信号通过网卡驱动转换成计算机能够识别的数据。主要是管理网络数据包的接收很发送,在操作系统中主要体现在设备驱动程序和网络接口。对应各种网络通信介质,不同的通信网络有其不同的访问协议,例如通常用的以太网和PPP等。
运行至这一层的协议主要有:Ethernet 802.3等等。
2.互联协议层
互联协议层是十分重要的一层,网卡驱动将解析后的数据递交给互联协议层,互联协议层的工作主要就是对这些混乱的数据进行解析,然后分门别类地传递给不同的上层协议。
运行在这一层的协议主要由:IP( Internet Protocol ),ICMP( Internet Control Message Protocol ),ARP( Address Resolution Protocol , 地址转换协议),RARP( Reverse Address Resolution Protocol .反向地址解析协议)
3.传输层协议
传输层协议有两个具有代表性的协议,分别是TCP( Transmission Control Protocol , 传输控制协议) 和UDP( User Datagram Protocol ,用户数据报协议)。
TCP的面向连接的通信协议,通过三次握手建立连接,通信完后要断开连接。由于它是面向连接的协议,那么它只能用于点对点之间的通信。TCP是一种可靠的数据流服务,采用“重传”和“肯定确认”机制来实现传输的可靠性。TCP还采用一种滑动窗口的方式进行流量的控制。所谓的窗口表示的是接收能力,用于限制发送方的发送速度。
UDP是面向无连接的通信协议,UDP数据包括目的端口号和源端口号信息,由于通信不需要连接,所以它可以实现广播发送。UDP通信时不需要接收方的确认,属于不可靠的传输,可能会出现丢包的现象,实际应用中要求在程序员编程验证。
4.应用层协议
应用层协议一般是面向用户的服务,如FTP,Telnet,DNS,SMTP,POP3等。
二、相关概念详解
1.ARP/RARP
ARP/RARP协议是进行IP地址和MAC地址相互转换的协议。在网络数据通信中,链路层使用MAC地址进行实际的数据通信,而在网络层使用IP地址进行机器定位寻址。ARP协议把IP地址映射为MAC地址,而RARP协议是通过MAC地址映射为IP地址。
ARP请求应答报文是一种比较常用的数据包,它的主要目的是找到IP地址对应的MAC地址。利用ARP请求报文来获得其MAC地址。这个过程是这样的:
首先发送ARP请求报文,它是以广播的方式发送到网络上的,每一台机器都会收到这个ARP请求报文,它是以广播方式发送到网络上的,每个机器都会收到这个ARP请求报文。在这个ARP请求报文中,发送端以太网地址字段是发送ARP请求报文机器的MAC地址,而且发送端IP字段内容是要查询的MAC的主机的IP地址。当某个主机检查到自己的IP地址是目的IP地址时,它会返回一个ARP应答报文,在此应答报文中,其发送端以太网地址字段内容就是要查询的MAC地址。
一个主机里面一般有ARP缓存,里面存储了IP地址和MAC地址对。
2.交换网络
在共享网络中,把网卡设为混杂模式就可以监听所有的网络数据包,但是在交换网络中,情况就发生了变化。
以太网分为共享式以太网和交换式以太网,共享式以太网通常以集线器作为网络设备,交换式以太网通常使用交换机作为网络连接设备。共享式以太网中数据帧以广播方式传送到每个以太网端口,网内每台主机的网卡能接收到网内的所有数据帧。因此只要把网卡设置成为混杂模式就可以获取到这本地网卡的所有数据帧。
最典型的交换网络使用交换机连接,在交换机中可以设为一个端口一个MAC地址,形成一个端口一个MAc地址对。这样当网络数据包到达端口时,而不是转发给所有的端口,只是转发MAC对应的端口。这样,其他端口的通讯不受干扰,所以其他端口上的主机就无法接收到网络上的数据包了。
可以利用ARP重定向技术来实现交换网络的数据包捕获,其主要原理用到了ARP欺骗技术,但此方法有一定的危险性。另外有的交换机上有一个用于调试的端口,任何其他端口的通信数据都会出现在此端口上,所以也可以在此端口上进行网络数据包的监听工作。
3.数据包在局域网内的传输方式
众所周知,数据包在互联网上的传播是根据IP地址进行寻址的,但是完整的过程并非如此。数据包通过IP地址可以达到的最远的地点就是目标主机所在的子网,而在该子网内的寻址却是使用物理地址的,即MAC地址。
数据包被传送到目标主机所在的子网时,它将被交给路由器,路由器在该子网内使用广播方式传播出去,这意味着该子网内的虽有主机都可以接收到该数据包。不过主机接收到数据包后通常会先检查其目的地址,如果目的地址不是自己,那么就是丢弃,只有目的地址为自己的数据包才会将其交付给上一层处理。
Sniffer将网卡设置为混杂模式,这样就可以接收到所有的数据包了,达到了嗅探了目的。
网卡对接收到的数据包经过一步步的解析之后就得到了最终的应用程序报文,此时就可以清晰易懂地看出它所包含的内容。
三、 网络抓包用到的技术
1.原始套接字编程
2.Winpcap编程
使用Winpcap来生成数据包比使用原始套接字更加灵活,功能更加丰富。在基于原始套接字方法中,由于原始套接字的某些限制,其构造的数据包最底层协议的数据包只能是IP数据包不能够构造IP层以下的协议数据包,例如链路层数据包就不能构造,而使用Winpcap可以构造基于链路层的数据包。
使用WinPcap构造数据包与使用原始套接字构造数据包是不同的。在原始套接字里面构造的数据包是从IP层开始的,而在WinPcap中是从链路层开始构造的。也就是说,在WinPcap中药构造一个Ip数据包,需要先构造链路层首部,在这里是以太网首部,然后再构造Ip首部,最后才是IP负载数据。而在原始套接字中直接从IP层开始构造,所以只需要构造IP首部和负载数据就可以了。
3.2主要代码
嗅探器设计原理
嗅探器作为一种网络通讯程序,也是通过对网卡的编程来实现网络通讯的,对网卡的编程也是使用通常的套接字(socket)方式来进行。但是,通常的套接字程序只能响应与自己硬件地址相匹配的或是以广播形式发出的数据帧,对于其他形式的数据帧比如已到达网络接口但却不是发给此地址的数据帧,网络接口在验证投递地址并非自身地址之后将不引起响应,也就是说应用程序无法收取到达的数据包。而网络嗅探器的目的恰恰在于从网卡接收所有经过它的数据包,这些数据包即可以是发给它的也可以是发往别处的。显然,要达到此目的就不能再让网卡按通常的正常模式工作,而必须将其设置为混杂模式。
具体到编程实现上,这种对网卡混杂模式的设置是通过原始套接字(raw socket)来实现的,这也有别于通常经常使用的数据流套接字和数据报套接字。在创建了原始套接字后,需要通过setsockopt()函数来设置IP头操作选项,然后再通过bind()函数将原始套接字绑定到本地网卡。为了让原始套接字能接受所有的数据,还需要通过ioctlsocket()来进行设置,而且还可以指定是否亲自处理IP头。至此,实际就可以开始对网络数据包进行嗅探了,对数据包的获取仍象流式套接字或数据报套接字那样通过recv()函数来完成。但是与其他两种套接字不同的是,原始套接字此时捕获到的数据包并不仅仅是单纯的数据信息,而是包含有 IP头、 TCP头等信息头的最原始的数据信息,这些信息保留了它在网络传输时的原貌。通过对这些在低层传输的原始信息的分析可以得到有关网络的一些信息。由于这些数据经过了网络层和传输层的打包,因此需要根据其附加的帧头对数据包进行分析。下面先给出结构.数据包的总体结构:
数据在从应用层到达传输层时,将添加TCP数据段头,或是UDP数据段头。其中UDP数据段头比较简单,由一个8字节的头和数据部分组成,具体格式如下:
而TCP数据头则比较复杂,以20个固定字节开始,在固定头后面还可以有一些长度不固定的可选项,下面给出TCP数据段头的格式组成:
对于此TCP数据段头的分析在编程实现中可通过数据结构_TCP来定义:
typedef struct _TCP{ WORD SrcPort; // 源端口
WORD DstPort; // 目的端口
DWORD SeqNum; // 顺序号
DWORD AckNum; // 确认号
BYTE DataOff; // TCP头长
BYTE Flags; // 标志(URG、ACK等)
WORD Window; // 窗口大小
WORD Chksum; // 校验和
WORD UrgPtr; // 紧急指针
} TCP;
typedef TCP *LPTCP;
typedef TCP UNALIGNED * ULPTCP;
在网络层,还要给TCP数据包添加一个IP数据段头以组成IP数据报。IP数据头以大端点机次序传送,从左到右,版本字段的高位字节先传输(SPARC是大端点机;Pentium是小端点机)。如果是小端点机,就要在发送和接收时先行转换然后才能进行传输。IP数据段头格式如下:
16位 16位
版本 IHL 服务类型 总长
标识 标志 分段偏移
生命期 协议 头校验和
源地址
目的地址
选项(0或更多)
同样,在实际编程中也需要通过一个数据结构来表示此IP数据段头,下面给出此数据结构的定义:
typedef struct _IP{
union{ BYTE Version; // 版本
BYTE HdrLen; // IHL
};
BYTE ServiceType; // 服务类型
WORD TotalLen; // 总长
WORD ID; // 标识
union{ WORD Flags; // 标志
WORD FragOff; // 分段偏移
};
BYTE TimeToLive; // 生命期
BYTE Protocol; // 协议
WORD HdrChksum; // 头校验和
DWORD SrcAddr; // 源地址
DWORD DstAddr; // 目的地址
BYTE Options; // 选项
} IP;
typedef IP * LPIP;
typedef IP UNALIGNED * ULPIP;
在明确了以上几个数据段头的组成结构后,就可以对捕获到的数据包进行分析了。
嗅探器的具体实现
根据前面的设计思路,不难写出网络嗅探器的实现代码,下面就给出一个简单的示例,该示例可以捕获到所有经过本地网卡的数据包,并可从中分析出协议、IP源地址、IP目标地址、TCP源端口号、TCP目标端口号以及数据包长度等信息。由于前面已经将程序的设计流程讲述的比较清楚了,因此这里就不在赘述了,下面就结合注释对程序的具体是实现进行讲解,同时为程序流程的清晰起见,去掉了错误检查等保护性代码。主要代码实现清单为:
// 检查 Winsock 版本号,WSAData为WSADATA结构对象
WSAStartup(MAKEWORD(2, 2), &WSAData);
// 创建原始套接字
sock = socket(AF_INET, SOCK_RAW, IPPROTO_RAW));
// 设置IP头操作选项,其中flag 设置为ture,亲自对IP头进行处理
setsockopt(sock, IPPROTO_IP, IP_HDRINCL, (char*)&flag, sizeof(flag));
// 获取本机名
gethostname((char*)LocalName, sizeof(LocalName)-1);
// 获取本地 IP 地址
pHost = gethostbyname((char*)LocalName));
// 填充SOCKADDR_IN结构
addr_in.sin_addr = *(in_addr *)pHost->h_addr_list[0]; //IP
addr_in.sin_family = AF_INET;
addr_in.sin_port = htons(57274);
// 把原始套接字sock 绑定到本地网卡地址上
bind(sock, (PSOCKADDR)&addr_in, sizeof(addr_in));
// dwValue为输入输出参数,为1时执行,0时取消
DWORD dwValue = 1;
// 设置 SOCK_RAW 为SIO_RCVALL,以便接收所有的IP包。其中SIO_RCVALL
// 的定义为: #define SIO_RCVALL _WSAIOW(IOC_VENDOR,1)
ioctlsocket(sock, SIO_RCVALL, &dwValue);
前面的工作基本上都是对原始套接字进行设置,在将原始套接字设置完毕,使其能按预期目的工作时,就可以通过recv()函数从网卡接收数据了,接收到的原始数据包存放在缓存RecvBuf[]中,缓冲区长度BUFFER_SIZE定义为65535。然后就可以根据前面对IP数据段头、TCP数据段头的结构描述而对捕获的数据包进行分析:
while (true)
{
// 接收原始数据包信息
int ret = recv(sock, RecvBuf, BUFFER_SIZE, 0);
if (ret > 0)
{
// 对数据包进行分析,并输出分析结果
ip = *(IP*)RecvBuf;
tcp = *(TCP*)(RecvBuf + ip.HdrLen);
TRACE("协议: %s/r/n",GetProtocolTxt(ip.Protocol));
TRACE("IP源地址: %s/r/n",inet_ntoa(*(in_addr*)&ip.SrcAddr));
TRACE("IP目标地址: %s/r/n",inet_ntoa(*(in_addr*)&ip.DstAddr));
TRACE("TCP源端口号: %d/r/n",tcp.SrcPort);
TRACE("TCP目标端口号:%d/r/n",tcp.DstPort);
TRACE("数据包长度: %d/r/n/r/n/r/n",ntohs(ip.TotalLen));
}
}
其中,在进行协议分析时,使用了GetProtocolTxt()函数,该函数负责将IP包中的协议(数字标识的)转化为文字输出,该函数实现如下:
#define PROTOCOL_STRING_ICMP_TXT "ICMP"
#define PROTOCOL_STRING_TCP_TXT "TCP"
#define PROTOCOL_STRING_UDP_TXT "UDP"
#define PROTOCOL_STRING_SPX_TXT "SPX"
#define PROTOCOL_STRING_NCP_TXT "NCP"
#define PROTOCOL_STRING_UNKNOW_TXT "UNKNOW"
……
CString CSnifferDlg::GetProtocolTxt(int Protocol)
{
switch (Protocol){
case IPPROTO_ICMP : //1 /* control message protocol */
return PROTOCOL_STRING_ICMP_TXT;
case IPPROTO_TCP : //6 /* tcp */
return PROTOCOL_STRING_TCP_TXT;
case IPPROTO_UDP : //17 /* user datagram protocol */
return PROTOCOL_STRING_UDP_TXT;
default:
return PROTOCOL_STRING_UNKNOW_TXT;
}
本次的课题是用c开发一个网络嗅探器的程序设计,设计完成对流经本地网卡的所有数据包捕获,分析协议类型,并根据不同的协议类型对数据包进行进一步的分析,包括分析数据包的源IP地址,目的IP地址,源端口号,目的端口号,大小等等。设计完成后经测试能实现预期要求的功能。但是仍然存在不足之处,例如:
1.由于时间和所学知识有限,只对数据包进行了简单分析,就归类到日志文件中。这样会占用很大的内存空间去存储这些数据。并且只能在日志文件中对其进一步的分析,操作量大。
2.本文中的嗅探器使用于基于广播包的网络,而对于诸如交换机这类设备,由于它能阻止广播,所以就不能够对子网内其他的机器进行监听。
3 . 通过本次的课程设计,学会了数据包的抓取得相关环节以及一些设计大的系统函数的使用,这在一定条件下,为以后的编程留下了一定的印象和基础。
4 . 实现了在vs2012的开发的平台下进行界面化的编程设计,对一些常用的控件有了一定的了解。
5 . 本次课程设计的最大的锻炼自己的地方在于查阅文档的相关能力,与此同时,在查阅玄关的英语文档时,对自身的英语水平有了一定的提高(学到了很多的专业英语词汇)。 通过对该课题的研究,让我对嗅探器技术有了进一步的了解,对其在网络中产生的影响有了更深的认识。
参考文献
[1]. 《数据结构》 主编:严蔚敏 李冬梅 吴伟民 2013年9月第1版 人民邮电出版社
[2]. 《C语言程序设计》 主编:贾宗璞 许合利 2017年1月第1版 中国铁道出版社
[3]. 《算法竞赛入门经典》 主编:刘汝佳 2014年6月第2版 清华大学出版社
[4]. 《算法竞赛入门经典——训练之南》 主编:刘汝佳 陈锋 2012年10月第1版 清华大学出版社
[5]. 《数据结构课程设计》 主编:滕国文 2010年9月第1版 清华大学出版社
[6]. 《数据结构课程设计案例精编》 主编:李建学 2010年1月第1版 清华大学出版社
附录(代码)
#include
#include
#include
#include
#include
typedef struct _TCP{ WORD SrcPort; // 源端口
WORD DstPort; // 目的端口
DWORD SeqNum; // 顺序号
DWORD AckNum; // 确认号
BYTE DataOff; // TCP头长
BYTE Flags; // 标志(URG、ACK等)
WORD Window; // 窗口大小
WORD Chksum; // 校验和
WORD UrgPtr; // 紧急指针
} TCP;
typedef TCP *LPTCP;
typedef TCP UNALIGNED * ULPTCP;
typedef struct _IP{
union{ BYTE Version; // 版本
BYTE HdrLen; // IHL
};
BYTE ServiceType; // 服务类型
WORD TotalLen; // 总长
WORD ID; // 标识
union{ WORD Flags; // 标志
WORD FragOff; // 分段偏移
};
BYTE TimeToLive; // 生命期
BYTE Protocol; // 协议
WORD HdrChksum; // 头校验和
DWORD SrcAddr; // 源地址
DWORD DstAddr; // 目的地址
BYTE Options; // 选项
} IP;
typedef IP * LPIP;
typedef IP UNALIGNED * ULPIP;
// 检查 Winsock 版本号,WSAData为WSADATA结构对象
WSAStartup(MAKEWORD(2, 2), &WSAData);
// 创建原始套接字
sock = socket(AF_INET, SOCK_RAW, IPPROTO_RAW));
// 设置IP头操作选项,其中flag 设置为ture,亲自对IP头进行处理
setsockopt(sock, IPPROTO_IP, IP_HDRINCL, (char*)&flag, sizeof(flag));
// 获取本机名
gethostname((char*)LocalName, sizeof(LocalName)-1);
// 获取本地 IP 地址
pHost = gethostbyname((char*)LocalName));
// 填充SOCKADDR_IN结构
addr_in.sin_addr = *(in_addr *)pHost->h_addr_list[0]; //IP
addr_in.sin_family = AF_INET;
addr_in.sin_port = htons(57274);
// 把原始套接字sock 绑定到本地网卡地址上
bind(sock, (PSOCKADDR)&addr_in, sizeof(addr_in));
// dwValue为输入输出参数,为1时执行,0时取消
DWORD dwValue = 1;
// 设置 SOCK_RAW 为SIO_RCVALL,以便接收所有的IP包。其中SIO_RCVALL
// 的定义为: #define SIO_RCVALL _WSAIOW(IOC_VENDOR,1)
ioctlsocket(sock, SIO_RCVALL, &dwValue);
while (true)
{
// 接收原始数据包信息
int ret = recv(sock, RecvBuf, BUFFER_SIZE, 0);
if (ret > 0)
{
// 对数据包进行分析,并输出分析结果
ip = *(IP*)RecvBuf;
tcp = *(TCP*)(RecvBuf + ip.HdrLen);
TRACE("协议: %s/r/n",GetProtocolTxt(ip.Protocol));
TRACE("IP源地址: %s/r/n",inet_ntoa(*(in_addr*)&ip.SrcAddr));
TRACE("IP目标地址: %s/r/n",inet_ntoa(*(in_addr*)&ip.DstAddr));
TRACE("TCP源端口号: %d/r/n",tcp.SrcPort);
TRACE("TCP目标端口号:%d/r/n",tcp.DstPort);
TRACE("数据包长度: %d/r/n/r/n/r/n",ntohs(ip.TotalLen));
}
}
#define PROTOCOL_STRING_ICMP_TXT "ICMP"
#define PROTOCOL_STRING_TCP_TXT "TCP"
#define PROTOCOL_STRING_UDP_TXT "UDP"
#define PROTOCOL_STRING_SPX_TXT "SPX"
#define PROTOCOL_STRING_NCP_TXT "NCP"
#define PROTOCOL_STRING_UNKNOW_TXT "UNKNOW"
……
CString CSnifferDlg::GetProtocolTxt(int Protocol)
{
switch (Protocol){
case IPPROTO_ICMP : //1 /* control message protocol */
return PROTOCOL_STRING_ICMP_TXT;
case IPPROTO_TCP : //6 /* tcp */
return PROTOCOL_STRING_TCP_TXT;
case IPPROTO_UDP : //17 /* user datagram protocol */
return PROTOCOL_STRING_UDP_TXT;
default:
return PROTOCOL_STRING_UNKNOW_TXT;
}