Brokenner
掌握嗅探器的工作原理
熟悉 WinPcap 的使用
掌握基于 WinPcap 网络嗅探器的开发过程
开发出一个 Windows 平台上的网络嗅探工具,能显示所捕获的数据包,并能做相应的分析和统计。主要内容如下:
列出监测主机的所有网卡,选择一个网卡,设置为混杂模式进行监听
捕获所有流经网卡的数据包,并利用 WinPcap 函数库设置过滤规则
分析捕获到的数据包的包头和数据,按照各种协议的格式进行格式化显示
将所开发工具的捕获和分析结果与常用的嗅探器,如 Wireshark,进行比较,完善程序代码
WinPcap 支持库
Visual C++ 语言,MFC 支持库
Visual Studio 2008 集成开发环境
Windows 2007 操作系统
本次课程设计主要完成基于 WinPcap 的网络嗅探器软件的设计与实现。通过WinPcap 支持库我们可以获取本机网卡信息,并读取经过一个网卡的所有数据包。
通过对数据包的结构化分析,我们可以获得所有数据包的类型及具体内容,然后通过 MFC 可视化控件显示到软件界面上。系统流程图如图 1 所示。
本次课程设计所实现的嗅探器主要分为四个模块:WinPcap 抓包模块、数据包分析模块、程序界面模块、交互设计模块。各模块的实现如下。
该模块主要包括:获取本机网卡信息、设置数据包过滤规则、抓取数据包三个功能。其程序框架图如图 2 所示。
该模块是软件实现的重点模块。我们捕获的数据包是以太网帧,通过对帧结构中部分字段进行判别,我们可以识别出该数据包具体属于哪种协议的数据包。
在本次试验中,我们具体分析 TCP,UDP,ICMP,ARP,HTTP 数据包。部分数据包首部定义如下:
//Mac头部,总长度14字节
typedef struct ethernet_header
{
u_char dstmac[6]; //目标mac地址
u_char srcmac[6]; //源mac地址
u_short eth_type; //以太网类型
}ethernet_header;
/* 4 bytes IP address */
typedef struct ip_address{
u_char byte1; //IP地址第1个字段
u_char byte2; //IP地址第2个字段
u_char byte3; //IP地址第3个字段
u_char byte4; //IP地址第4个字段
}ip_address;
//IP头部,总长度20字节
typedef struct ip_header
{
#if LITTLE_ENDIAN
u_char ihl:4; //首部长度
u_char version:4;//版本
#else
u_char version:4;//版本
u_char ihl:4; //首部长度
#endif
u_char tos; //服务类型
u_short tot_len; //总长度
u_short id; //标识号
#if LITTLE_ENDIAN
u_short frag_off:13;//分片偏移
u_short flag:3; //标志
#else
u_short flag:3; //标志
u_short frag_off:13;//分片偏移
#endif
u_char ttl; //生存时间
u_char protocol; //协议
u_short chk_sum; //检验和
struct ip_address srcaddr; //源IP地址
struct ip_address dstaddr; //目的IP地址
}ip_header;
//TCP头部,总长度20字节 TCP头部与TCP数据包不是一个概念;
typedef struct tcp_header
{
u_short src_port; //源端口号
u_short dst_port; //目的端口号
u_int seq_no; //序列号
u_int ack_no; //确认号
#if LITTLE_ENDIAN
u_char reserved_1:4; //保留6位中的4位首部长度
u_char offset:4; //tcp头部长度
u_char flag:6; //6位标志
u_char reserved_2:2; //保留6位中的2位
#else
u_char offset:4; //tcp头部长度
u_char reserved_1:4; //保留6位中的4位首部长度
u_char reserved_2:2; //保留6位中的2位
u_char flag:6; //6位标志
#endif
u_short wnd_size; //16位窗口大小
u_short chk_sum; //16位TCP检验和
u_short urgt_p; //16为紧急指针
}tcp_header;
//UDP头部,总长度8字节
typedef struct udp_header
{
u_short src_port; //远端口号
u_short dst_port; //目的端口号
u_short uhl; //udp头部长度
u_short chk_sum; //16位udp检验和
}udp_header;
//ICMP头部,总长度4字节
typedef struct icmp_header
{
u_char type; //类型
u_char code; //代码
u_short chk_sum; //16位检验和
}icmp_header;
在捕获数据包之后,我们需要对特定字段进行识别,以判断该数据包具体属于哪个协议,分析数据包流程如图 3 所示:
界面上方的 button : 用于用户交互,控制整个软件的运行
界面上方的 Combo Box : 用于选择监听的网口
左上方的 Group Box :用户统计不同数据包的个数
左方中间的 Group Box : 用于设置数据包过滤器
中间主体部分 List Control: 结构化显示捕获数据包的信息
左下方的 Tree-view Control: 分析数据包各个字段代表的含义
右下方的 Edit Control: 显示数据包的内容
结构化显示数据包:
// 显示选中的网卡数据包pkt信息到List Control列表框中
void CYGSnifferDlg::DispSelectedtoListCtrl(packet *tmp_pkt)
{
packet *pkt = new packet;
const struct pcap_pkthdr *header = new pcap_pkthdr;
const u_char *pkt_data = new u_char;
pkt = tmp_pkt;
header = pkt->header;
pkt_data = pkt->pkt_data;
//No
int iNoCount = m_listAdapterInfo.GetItemCount();
int iNoDisp = iNoCount + 1;
TCHAR strNo[10];
_itow_s(iNoDisp,strNo,10);
//TimeStamp
struct tm lTime = {0,0,0,0,0,0,0,0,0};
struct tm *plTime = &lTime;
char strTime[16];
time_t local_tv_sec;
local_tv_sec = header->ts.tv_sec;
localtime_s(plTime,&local_tv_sec);
strftime(strTime,sizeof strTime,"%H:%M:%S",plTime);
//Length
int iLength = header->len;
TCHAR strLength[10];
_itow_s(iLength,strLength,10);
//Ethernet - Mac
ethernet_header *eth_hdr = (ethernet_header *)pkt_data;
TCHAR eth_srcMac[18];
TCHAR eth_dstMac[18];
CString eth_strType = NULL;
GetMacAddress(eth_srcMac,eth_hdr->srcmac);
GetMacAddress(eth_dstMac,eth_hdr->dstmac);
GetMacType(eth_strType,ntohs(eth_hdr->eth_type),true); // ntohs() is to swap network to host
// IP
ip_header *ip_hdr = (ip_header *)(pkt_data+14); // get ip pos
TCHAR ip_srcAddr[16];
TCHAR ip_dstAddr[16];
CString ip_strProtocol = NULL;
GetIPAddress(ip_srcAddr,&ip_hdr->srcaddr);
GetIPAddress(ip_dstAddr,&ip_hdr->dstaddr);
GetIPType(ip_strProtocol,ip_hdr->protocol,true);
IsHTTP(pkt_data);
//=========== show ============
m_listAdapterInfo.InsertItem(iNoCount,strNo);
USES_CONVERSION;
m_listAdapterInfo.SetItemText(iNoCount,1,A2W(strTime));
m_listAdapterInfo.SetItemText(iNoCount,2,strLength);
m_listAdapterInfo.SetItemText(iNoCount,3,eth_strType);
m_listAdapterInfo.SetItemText(iNoCount,4,eth_srcMac);
m_listAdapterInfo.SetItemText(iNoCount,5,eth_dstMac);
m_listAdapterInfo.SetItemText(iNoCount,6,ip_strProtocol);
m_listAdapterInfo.SetItemText(iNoCount,7,ip_srcAddr);
m_listAdapterInfo.SetItemText(iNoCount,8,ip_dstAddr);
if(pkt)
{
delete pkt;
pkt = NULL;
}
if(header)
{
delete header;
header = NULL;
}
if(pkt_data)
{
delete pkt_data;
pkt_data = NULL;
}
}
显示数据包详细信息(以 IP 协议为例):
// 显示IP协议的详细信息
void CYGSnifferDlg::ShowIPDetail(HTREEITEM &hItem,const u_char *pkt_data)
{
// =================== IP ======================
ip_header *ip_hdr = (ip_header *)(pkt_data+14);
hItem = m_treeDetailInfo.InsertItem(TEXT("IP LAYER"));
CString str = NULL;
// Version
u_char ip_version = ip_hdr->version;
str.Format(TEXT("Version = %d"),ip_version);
m_treeDetailInfo.InsertItem(str, hItem);
// Header Length
u_char ip_length = ip_hdr->ihl;
str.Format(TEXT("Header Length = %d"),ip_length);
m_treeDetailInfo.InsertItem(str, hItem);
// Type of service
u_char ip_tos = ip_hdr->tos;
str.Format(TEXT("Service Type = %0X"),ip_tos);
m_treeDetailInfo.InsertItem(str, hItem);
// Total Length
u_short ip_totalLen = ip_hdr->tot_len;
str.Format(TEXT("Total Length = %d"),ntohs(ip_totalLen));
m_treeDetailInfo.InsertItem(str, hItem);
// Identification
str.Format(TEXT("Identification = %d"),ntohs(ip_hdr->id));
m_treeDetailInfo.InsertItem(str, hItem);
// Flags
TCHAR ip_strFlag[4];
u_short ip_flag = (ip_hdr->flag);
_itow_s(ip_flag,ip_strFlag,4,2);
str.Format(TEXT("Flag = %03s"),ip_strFlag);// 填充字符串方法:CString szTemp; szTemp.Format("%06d", n); //n=123(000123)|456(000456)
m_treeDetailInfo.InsertItem(str, hItem);
// Flagment offset
u_short ip_flagoff = ip_hdr->frag_off;
str.Format(TEXT("Flagment offset = %d"),ip_flagoff);
m_treeDetailInfo.InsertItem(str, hItem);
// Time to live
u_char ip_ttl = ip_hdr->ttl;
str.Format(TEXT("Time to live = %d"),ip_ttl);
m_treeDetailInfo.InsertItem(str, hItem);
// IP Protocol
CString ip_strProtocol = NULL;
u_char ip_protocol = ip_hdr->protocol;
GetIPType(ip_strProtocol,ip_protocol,false); // get ip protocol by call function -> GetIPType()
str.Format(TEXT("IP Protocol = %s"),ip_strProtocol);
m_treeDetailInfo.InsertItem(str, hItem);
// Header CheckSum
u_short ip_chksum = ip_hdr->chk_sum;
str.Format(TEXT("Header CheckSum = %0X"),ntohs(ip_chksum));
m_treeDetailInfo.InsertItem(str, hItem);
// Source IP
TCHAR ip_srcAddr[16];
TCHAR ip_dstAddr[16];
GetIPAddress(ip_srcAddr,&ip_hdr->srcaddr);
GetIPAddress(ip_dstAddr,&ip_hdr->dstaddr);
str.Format(TEXT("Source IP = %s"),ip_srcAddr);
m_treeDetailInfo.InsertItem(str, hItem);
str.Format(TEXT("Dest IP = %s"),ip_dstAddr);
m_treeDetailInfo.InsertItem(str, hItem);
}
显示数据包内容:
// 显示Packet Data数据信息,最下方控件显示;
void CYGSnifferDlg::GetDataInfo(CEdit & eText, packet *pkt)
{
const struct pcap_pkthdr *header = pkt->header;
const u_char *pkt_data = pkt->pkt_data;
u_int pkt_dataLen = header->len; // 得到单个Packet_Data(注意:不是packet)数据包的长度
CString strText = NULL;
CString chrAppend = NULL;
u_int eRows = 0;
for(u_short i=0; i<pkt_dataLen; i++)
{
CString strAppend = NULL;
if(0 == (i%16)) // 取余,换行
{
eRows++;
if(0 == i)
{
strText +=chrAppend;
strAppend.Format(TEXT(" 0X%04X -> "),eRows);
strText += strAppend;
}
else
{
strText +=TEXT("==>> ") +chrAppend;
strAppend.Format(TEXT("\x0d\x0a 0X%04X -> "),eRows); //0x0d:回车; 0x0a:换行;0X:表示16进制显示;%04x表示以4位的16进制显示并以0填充空位; eRows即显示行数(16进制格式显示)
strText += strAppend;
}
chrAppend = ""; // reset null
}
strAppend.Format(TEXT("%02x "),pkt_data[i]);
strText += strAppend;
if(i>2 && pkt_data[i-1]==13 && pkt_data[i]==10)//如果遇到回车、换行,则直接继续,以免使显示字符换行
continue;
strAppend.Format(TEXT("%c"),pkt_data[i]);
chrAppend += strAppend;
}
if(chrAppend !="")
strText +=TEXT("==>> ") +chrAppend;
eText.SetWindowTextW(strText);
}
将过滤器设置为选择 IP 和 UDP,程序运行结果如图 5 所示。
本次实验主要遇到的问题有两点:数据包的分析和 MFC 编程。
由于开始对网络协议及数据包结构不清楚,不知道如何解析数据包,也不知道怎样区分不同协议的数据包。于是在网上查了很多关于网络协议分层、数据包分析的资料,才终于明白各层协议之间的关系,并知道了怎样区分不同协议,并成功完成数据包分析工作。
问题二在于 MFC 编程,本科阶段我主攻 JAVA,C++ MFC 只是略有涉及。本次实验为了实现可视化,我找了很多资料,自学 MFC 编程,遇到问题的时候就像周围擅长 MFC 的同学请教,最终完成该软件的编写,虽然还有一些不足的地方,但也是一次成功的尝试。
通过本次实验,我对网络协议有了更深的理解,同时学会了如何处理数据包。对整个计算机网络知识的理解有了质的提升。同时,我还自学了 MFC 编程,编程水平有较大提高。总而言之,这次实验让我收获良多!
keyboard_arrow_left上一篇 : 基于JAVA的停车场管理系统 基于OpenCV的基本图形识别 : 下一篇keyboard_arrow_right