第一篇:数据包必读!
数据包须知
1.价格问题:数据包里的价格为销售价。
数据包里excel形式的表格为成本单。
注意:鞋子重量稍重 有些可能超重需要补价邮费请注意!
2.缺断货信息问题:请以软件更新为准或咨询在线客服(断缺货可能出现更新不及时还请谅解)
3.新增产品问题:新增加的产品会在官网中增加数据包,请留意第五代官网及公告。
4数据包下载以后-利用淘宝助理上传(修改宝贝名称)上传店铺—设置运费模板—进行销售
5;邮费的收取标准
数据包数量较多,若出现扣分降权等问题请及时联系在线客服,如果出现价格不对的问题请及时联系在线客服以便修正
注意:商品退换货问题请联系在线客服
甘肃省,青海省,宁夏,黑龙江省,吉林省,辽宁省,北京市,上海市,天津市,重庆市,安徽省,福建省,广
顺丰快递 东省,广西,贵州省,河北省,河南省,湖北省,湖南省,20 1 20
江苏省,江西省,山东省,山西省,陕西省,四川省,云
南省,浙江省,海南省,新疆,内蒙古,西藏
甘肃省,青海省,宁夏,黑龙江省,吉林省,辽宁省,北京市,上海市,天津市,重庆市,安徽省,福建省,广
EMS 东省,广西,贵州省,河北省,河南省,湖北省,湖南省,25 0.5 20
江苏省,江西省,山东省,山西省,陕西省,四川省,云
南省,浙江省,海南省,新疆,内蒙古,西藏
甘肃省,青海省,宁夏,黑龙江省,吉林省,辽宁省,北京市,上海市,天津市,重庆市,安徽省,福建省,广
fedex 东省,广西,贵州省,河北省,河南省,湖北省,湖南省,30 0.5 30
江苏省,江西省,山东省,山西省,陕西省,四川省,云
南省,浙江省,海南省,新疆,内蒙古,西藏0.5 0.5
第二篇:计算机网络 课程设计 发送TCP数据包
课设名称:发送TCP数据包
班级:
学号:
姓名:
指导老师: 日期: 2012.6.15
计算机网络课程设计报告
目录
一.设计题目与要求........................................................................................................2
1.设计题目................................................................................................................2
2.设计要求................................................................................................................2
二.需求分析...................................................................................................................2三.详细设计...................................................................................................................2
1.创建一个原始套接字,并设置IP头选项.................................................................3
2.构造IP头和TCP头...............................................................................................3
3.计算校验和的子函数..............................................................................................4
4.流程图...................................................................................................................6
四.调试分析...................................................................................................................7
五.运行结果...................................................................................................................7
六.总结..........................................................................................................................8
七.源程序......................................................................................................................9
发送TCP数据包
一.设计题目与要求
1.设计题目
发送TCP数据包 2.设计要求
本设计的功能是填充一个TCP数据包,并发送给目的主机。
1)以命令行形式运行:TCP source_ip source_port dest_ip dest_port,其中SendTCP是程序名,source_ip为源端IP地址,source_port为源端口号,dest_ip为目的地址,dest_port为目的端口号。
2)其他的TCP头部参数请自行设定。3)数据字段为“hello”。
4)成功发送后在屏幕上输出“send OK”。
二.需求分析
1.本程序需完成发送一个TCP数据包给目的主机
2.程序的输入:TCP source_ip source_port dest_ip dest_port,然后根据提示输入要发送的数据,回车即可。
3.程序的输出:Send OK!
4.测试数据 TCP 192.168.1.100 200 192.168.1.101 200
三.详细设计
本课程设计的目标是发送一个TCP数据包,可以利用原始套接字来完成这个工作。整个程序由初始化原始套接字和发送TCP数据包两个部分组成。
2发送TCP数据包
1.创建一个原始套接字,并设置IP头选项
SOCKET sock;sock = socket(AF_INET,SOCK_RAW,IPPROTO_IP);或者:
sock=WSASoccket(AF_INET,SOCK_RAW,IPPROTO_IP,NULL,0,WSA_FLAG_OVERLAPPED);这里,设置了SOCK_RAW标志,表示我们声明的是一个原始套接字类型。为使用发送接收超时设置,必须将标志位置位置为WSA_FLAG_OVERLAPPED。在本课程设计中,发送TCP包时隐藏了自己的IP地址,因此我们要自己填充IP头,设置IP头操作选项。其中flag设置为ture,并设定 IP_HDRINCL 选项,表明自己来构造IP头。
setsockopt(sock, IPPROTO_IP, IP_HDRINCL,(char *)&Flag, sizeof(Flag));int timeout=1000; setsockopt(sock, sizeof(timeout));在这里我们使用基本套接字SOL_SOCKET,设置SO_SNDTIMEO表示使用发送超时设置,超时时间设置为1000ms。2.构造IP头和TCP头
这里,IP头和TCP头以及TCP伪部的构造请参考下面它们的数据结构。
typedef struct _iphdr //定义IP首部 { UCHAR h_lenver;//4位首部长度+4位IP版本号 UCHAR tos;//8位服务类型TOS
USHORT total_len;//16位总长度(字节)USHORT ident;//16位标识
USHORT frag_and_flags;//3位标志位 UCHAR ttl;//8位生存时间 TTL
UCHAR proto;//8位协议(TCP, UDP 或其他)
SOL_SOCKET,SO_SNDTIMEO,(char*)&timeout,3发送TCP数据包
USHORT checksum;//16位IP首部校验和 ULONG sourceIP;//32位源IP地址
ULONG destIP;//32位目的IP地址
}IP_HEADER;typedef struct psd_hdr //定义TCP伪首部 { ULONG saddr;ULONG daddr;//源地址 //目的地址
UCHAR mbz;//没用 UCHAR ptcl;
USHORT tcpl;//协议类型 //TCP长度
}PSD_HEADER;typedef struct _tcphdr //定义TCP首部 { USHORT th_sport;//16位源端口 USHORT th_dport;//16位目的端口 ULONG th_seq;//32位序列号 ULONG th_ack;//32位确认号
UCHAR th_lenres;//4位首部长度/6位保留字 UCHAR th_flag;//6位标志位 USHORT th_win;//16位窗口大小 USHORT th_sum;//16位校验和 USHORT th_urp;//16位紧急数据偏移量
}TCP_HEADER;
3.计算校验和的子函数
在填充数据包的过程中,需要调用计算校验和的函数checksum两次,分别用于校验IP头和TCP头部(加上伪头部),其实现代码如下:
USHORT checksum(USHORT *buffer, int size)
4发送TCP数据包
{ unsigned long cksum=0;while(size >1){ cksum+=*buffer++;size-=sizeof(USHORT);} if(size){ cksum += *(UCHAR*)buffer;} cksum =(cksum >> 16)+(cksum & 0xffff);cksum +=(cksum >>16);return(USHORT)(~cksum);}
5发送TCP数据包
4.流程图
开始构造原始套接字并初始化填充IP首部计算IP首部校验和构造TCP伪首部填充TCP首部计算TCP首部校验和填充发送缓冲区填入目的地址发送数据包结束
6发送TCP数据包
四.调试分析
本程序流程简单,调试过程中没有出现大的问题。
调试过程中没有出现重大的语法错误,主要是运行的结果不理想,和预期的结果有差距。填充数据包的程序部分是不容易出错的。至于数据包的发送,由于是利用函数sendto()来实现的,而sendto()是面向UDP的,将协议类型修改为UDP(Header.proto=IPPROTO_UDP;)后,调试运行成功,问题得到解决。
调试时,要添加 #include
五.运行结果
1.输入:TCP 192.168.1.100 200 192.168.1.101 200
7发送TCP数据包
2.输入要发送的字符串:“hello”按Ctrl+Z发送
六.总结
通过本次课程设计,我对发送TCP数据包的原理有了一定的了解。理解了TCP数据报的报文格式、TCP连接时的三次握手和TCP连接结束时的四次握手的过程及它们的作用。此外,在设计过程中,通过查阅资料,也让我对TCP的三大特点(流量控制、差错控制、拥塞控制)有了认识。,在课程设计过程中,由于编程知识的欠缺,使我在课程设计过程中不是很顺利,编程知识的欠缺是我的最大障碍,不过,这也给了我动力,我会努力去学好编程的相关知识,为以后的学习和工作打下基础。最后,感谢老师安排了此次课程设计。
发送TCP数据包
七.源程序
#include
#define IPVER 4 //IP协议预定 #define MAX_BUFF_LEN 65500 //发送缓冲区最大值
typedef struct ip_hdr //定义IP首部 {
UCHAR h_verlen;//4位首部长度,4位IP版本号 UCHAR tos;//8位服务类型TOS USHORT total_len;//16位总长度(字节)USHORT ident;//16位标识 USHORT frag_and_flags;//3位标志位 UCHAR ttl;//8位生存时间 TTL UCHAR proto;//8位协议(TCP, UDP 或其他)USHORT checksum;//16位IP首部校验和 ULONG sourceIP;//32位源IP地址 ULONG destIP;//32位目的IP地址
发送TCP数据包
}IP_HEADER;
typedef struct tsd_hdr //定义TCP伪首部 {
ULONG saddr;//源地址 ULONG daddr;//目的地址 UCHAR mbz;//没用 UCHAR ptcl;//协议类型 USHORT tcpl;//TCP长度
}PSD_HEADER;
typedef struct tcp_hdr //定义TCP首部 {
USHORT th_sport;//16位源端口 USHORT th_dport;//16位目的端口 ULONG th_seq;//32位序列号 ULONG th_ack;//32位确认号
UCHAR th_lenres;//4位首部长度/6位保留字 UCHAR th_flag;//6位标志位 USHORT th_win;//16位窗口大小 USHORT th_sum;//16位校验和
USHORT th_urp;//16位紧急数据偏移量
}TCP_HEADER;
//CheckSum:计算校验和的子函数
USHORT checksum(USHORT *buffer, int size){ unsigned long cksum=0;while(size >1)
发送TCP数据包
{ cksum+=*buffer++;size-=sizeof(USHORT);} if(size){ cksum += *(UCHAR*)buffer;} cksum =(cksum >> 16)+(cksum & 0xffff);cksum +=(cksum >>16);return(USHORT)(~cksum);}
int ReadData(char *str,int maxlen){ int readlen=0;char ch=NULL;if(str==NULL||maxlen<=0){
printf(“ReadData Error!!n”);
return 0;//failed } printf(“Input Data(End By Ctrl+Z): n”);while(maxlen){
ch=getchar();
if(ch==EOF)break;
str[readlen++]=ch;maxlen--;
发送TCP数据包
} } str[readlen]=NULL;return readlen;int main(int argc, char* argv[]){ WSADATA WSAData;SOCKET sock;
IP_HEADER ipHeader;TCP_HEADER tcpHeader;PSD_HEADER psdHeader;
char Sendto_Buff[MAX_BUFF_LEN];//发送缓冲区
unsigned short check_Buff[MAX_BUFF_LEN];//检验和缓冲区 char tcp_send_data[1000];
BOOL flag;int rect,nTimeOver;if(argc!= 5){ printf(“Usage: SendTcp soruce_ip source_port dest_ip dest_port n”);return false;} read_data_len=ReadData(tcp_send_data,1000);int read_data_len=0;
发送TCP数据包
if(read_data_len<=0)return 1;
if(WSAStartup(MAKEWORD(2,2), &WSAData)!=0){ printf(“WSAStartup Error!n”);return false;} if((sock=WSASocket(AF_INET,SOCK_RAW,IPPROTO_RAW,NULL,0, WSA_FLAG_OVERLAPPED))==INVALID_SOCKET){ printf(“Socket Setup Error!n”);return false;} flag=true;if(setsockopt(sock,IPPROTO_IP,IP_HDRINCL,(char*)&flag,sizeof(flag))==SOCKET_ERROR){ printf(“setsockopt IP_HDRINCL error!n”);return false;} nTimeOver=1000;if(setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO,(char*)&nTimeOver, sizeof(nTimeOver))==SOCKET_ERROR){ printf(“setsockopt SO_SNDTIMEO error!n”);return false;}
发送TCP数据包
//填充IP首部
ipHeader.h_verlen=(IPVER<<4 | sizeof(ipHeader)/sizeof(unsigned long));ipHeader.tos=(UCHAR)0;ipHeader.total_len=htons((unsigned short)sizeof(ipHeader)+sizeof(tcpHeader)+read_data_len);ipHeader.ident=0;//16位标识 ipHeader.frag_and_flags=0;//3位标志位 ipHeader.ttl=128;//8位生存时间 ipHeader.proto=IPPROTO_TCP;//协议类型 ipHeader.checksum=0;//检验和暂时为0 ipHeader.sourceIP=inet_addr(argv[1]);//32位源IP地址 ipHeader.destIP=inet_addr(argv[3]);//32位目的IP地址
//计算IP头部检验和
memset(check_Buff,0,MAX_BUFF_LEN);memcpy(check_Buff,&ipHeader,sizeof(IP_HEADER));ipHeader.checksum=checksum(check_Buff,sizeof(IP_HEADER));
//构造TCP伪首部
psdHeader.saddr=ipHeader.sourceIP;psdHeader.daddr=ipHeader.destIP;psdHeader.mbz=0;psdHeader.ptcl=ipHeader.proto;psdHeader.tcpl=htons(sizeof(TCP_HEADER)+read_data_len);
//填充TCP首部
tcpHeader.th_dport=htons(atoi(argv[4]));//16位目的端口号 tcpHeader.th_sport=htons(atoi(argv[2]));//16位源端口号
发送TCP数据包
tcpHeader.th_seq=0;//SYN序列号 tcpHeader.th_ack=0;//ACK序列号置为0 //TCP长度和保留位
tcpHeader.th_lenres=(sizeof(tcpHeader)/sizeof(unsigned long)<<4|0);tcpHeader.th_flag=2;//修改这里来实现不同的标志位探测,2是SYN,1是//FIN,16是ACK探测 等等
tcpHeader.th_win=htons((unsigned short)16384);//窗口大小 tcpHeader.th_urp=0;//偏移大小 tcpHeader.th_sum=0;//检验和暂时填为0
//计算TCP校验和
memset(check_Buff,0,MAX_BUFF_LEN);memcpy(check_Buff,&psdHeader,sizeof(psdHeader));
memcpy(check_Buff+sizeof(psdHeader),&tcpHeader,sizeof(tcpHeader));
//填充发送缓冲区
memset(Sendto_Buff,0,MAX_BUFF_LEN);memcpy(Sendto_Buff,&ipHeader,sizeof(IP_HEADER));
memcpy(Sendto_Buff+sizeof(IP_HEADER), &tcpHeader,sizeof(TCP_HEADER));memcpy(check_Buff+sizeof(PSD_HEADER)+sizeof(TCP_HEADER), tcp_send_data,read_data_len);tcpHeader.th_sum=checksum(check_Buff,sizeof(PSD_HEADER)+ sizeof(TCP_HEADER)+read_data_len);memcpy(Sendto_Buff+sizeof(IP_HEADER)+sizeof(TCP_HEADER), tcp_send_data,read_data_len);
发送TCP数据包
int datasize=sizeof(IP_HEADER)+sizeof(TCP_HEADER)+read_data_len;//发送数据报的目的地址 SOCKADDR_IN dest;memset(&dest,0,sizeof(dest));dest.sin_family=AF_INET;dest.sin_addr.s_addr=inet_addr(argv[3]);dest.sin_port=htons(atoi(argv[4]));
rect=sendto(sock,Sendto_Buff,datasize, 0,(struct sockaddr*)&dest, sizeof(dest));if(rect==SOCKET_ERROR){ printf(“send error!:%dn”,WSAGetLastError());return false;} else
closesocket(sock);WSACleanup();return 1;} printf(“nsend ok!n”);
第三篇:数据包捕获与协议实验报告
计算机网络实验(实习)报告
Ⅰ.实验(实习)名称 :数据包捕获与协议分析
实验(实习)日期
专业姓名:学号:(或使用青岛农业大学实验报告纸)
1、实验目的(1)掌握网络协议分析工具Ethereal的使用方法;
(2)截获数据包并对它们观察和分析,了解协议的运行机制;
2、实验内容:
(1)设计一个捕获HTTP实现的完整过程,并对捕获的结果进行分析和统计。
要求:
(2)设计一个捕获TCP实现的完整过程,并对捕获的结果进行分析和统计。
要求:给出捕获某一数据包后的屏幕截图。以16进制形式显示其包的内容,并分析
TCP报文(源端口、目的端口、序号、确认号,ACK、SYN、窗口等)。
(3)设计一个捕获ICMP实现的完整过程,并对捕获的结果进行分析和统计
要求:给出捕获某一数据包后的屏幕截图。以16进制形式显示其包的内容,并分析
该ICMP报文。
(4)设计一个捕获IP数据包的过程,并对捕获的结果进行分析和统计
要求:给出捕获某一数据包后的屏幕截图。以16进制形式显示其包的内容,并分析
在该数据包中的内容:版本首部长度、服务类型、总长度、标识、片偏移、寿命、协议、源Ip地址、目的地址
3.实验总结(掌握了哪些内容?遇到了什么问题?如何解决的?你的体会或收获如何?)
第四篇:计算机网络 课程设计 IP数据包解析
课设名称:IP数据包解析
班
级:
学
号:
姓
名:
指导老师:
日期: 2012.6.15
计算机网络课程设计报告
目录
1.课程设计目的..............................................................1
2.课程设计要求..............................................................1
3.程序设计分析..............................................................1
3.1 网卡设置................................................................1
3.2 使用套接字..............................................................2
3.2.2 接收数据包.............................................................2
3.3 定义IP头部的数据结构....................................................3
3.4 IP包的解析..............................................................3
3.5 协议的定义..............................................................4
3.6捕获处理.................................................................4
4.运行结果..................................................................5
5.总结......................................................................5
6.源程序代码................................................................6
Ip数据包解析
1.课程设计目的
本课程设计的目的就是设计一个捕获并解析IP数据包的程序,并根据这个程序,说明IP数据包的结构及IP协议的相关问题,从而对IP层的工作原理有更好的理解和认识。
2.课程设计要求
本设计的目标是捕获网络中的IP数据包,解析数据包的内容,将结果显示在标准输出上,并同时写入日志文件。程序的具体要求如下:
1)以命令行形式运行:ipparse logfile,其中ipparse是程序名, 而logfile则代表记录结果的日志文件。
2)在标准输出和日志文件中写入捕获的IP包的版本、头长度、服务类型、数据包总长度、数据包标识、分段标志、分段偏移值、生存时间、上层协议类型、头校验和、源IP地址和目的IP地址等内容。
3)当程序接收到键盘输入Ctrl+C时退出
3.程序设计分析
3.1 网卡设置
为了获取网络中的IP数据包,必须对网卡进行编程,在这里使用套接字(socket)进行编程。但是,在通常情况下,网络通信的套接字程序只能响应与自己硬件地址相匹配的数据包或是以广播形式发出的数据包。对于其他形式的数据包,如已到达网络接口,但却不是发送到此地址的数据包,网络接口在骓投递地
Ip数据包解析
址并非自身地址之后将不引起响应,也就是说应用程序无法收取与自己无关的数据包。我们要想获取网络设备的所有数据包,就是需要将网卡设置为混杂模式。
3.2 使用套接字
套接字分为三种,即流套接字(Stream socket)、数据报套接字(Datagram Socket)和原始套接字(Raw Socket)。要进行IP层数据包的接收和发送,应使用原始套接字。创建原始套接字的代码如下: Socket sock: Sock=wsasocket(af_inet,sock_raw,ipproto-ip,null,0,wsa-flag-overlapped): 本设计不用考虑超时情况。
创建套接后,IP头就会包含在接收数据包中。然后,我可以设置IP头操作选项,调用setsockopt函数。其中flag设置为true,并设定IP-HDRINCL选项,表明用户可以亲自对IP头进行处理。最后使用bind()函数将socket绑定到本地网卡上。绑定网卡后,需用WSAIoctl()函数把网卡设置为混杂模式,使网卡能够接收所有的网络数据。如果接收的数据包中的协议类型和定义的原始套接字匹配,那么接收的数据就拷贝到套接字中,因此,网卡就可以接收所有经过的IP包。
3.2.2 接收数据包
在程序中可使用recv()函数接收经过的IP包。该函数有四个参数,第一个参数接收操作所用的套接字描述符;第二个参数接收缓冲区的地址;第三个参数接收缓冲区的大小,也就是所要接收的字节数;第四个参数是一个附加标志,如
Ip数据包解析
果对所发送的数据没特殊要求,直接设为0。因为IP数据包的最大长度是65535B,因此缓冲区的大小不能小于65535B。设置缓冲区后,可利用循环来反复监听接收IP包,用recv()函数实现接收功能。
3.3 定义IP头部的数据结构
程序需要定义一个数据结构表示IP头部。其代码如下: struct IP_HEADER { unsigned short ip_version, /*IP的版本号 */ ip_hdr_len; /*IP包头的长度*/ ip_tos; /*IP包的服务类型*/ ip_total_len; /*IP包的总长度*/ ip_id; /*IP包的分段标识*/ ip_flags; /*IP包的分段标志*/ ip_frag_offset; /*IP包的分段偏移*/ ip_ttl; /*IP包的生存时间*/ ip_proto; /*IP包的高层协议*/ ip_hdr_chksum;/*IP包的校验和*/ struct IPADDRESS ip_src_addr; /*IP包的源IP地址*/ ip_dest_addr; /*IP包的目的IP地址*/ }ipheader;3.4 IP包的解析
Ip数据包解析
解析IP包的字段有两种策略。针对长度为8位、16位和32位的字段(或子字段)时,可以利用IP-HEADER的成员直接获取。要解析长度不是8位倍数的字段(或子字段)时,可以利用C语言中的移位以人、及与、或操作完成。
3.5 协议的定义
(包含相应的头文件#include #include):
DWORD dwIoControlCode=SIO_RCVALL, /*接收所有的IP包*/ dwProtocol=IPPROTO_IP;/*协议类型为IP*/
3.6捕获处理
1.加载 Winsock;
2.创建一个接收原始IP包的socket连接; 3.绑定到一个接口;
4.进行WSAIoctl设置,接收所有的IP数据包。代码如下:
if(WSAIoctl(s, dwIoControlCode, &optval, sizeof(optval), NULL, 0, &dwBytesRet, NULL, NULL)== SOCKET_ERROR)5.接着设定一个线程进行捕获:(1)创建一个接收IP包的链表头;
(2)设置一个标识,为真,则不断进行IP包的捕获;(3)建立一个新的结点,将捕获的数据包加入到该结点;
(4)如果链表的长度达到指定的长度,创建一个线程对该链表的IP包进行解析;再设置一个在IP数据包链表不足给定的长度,而又中止IP捕获时,对链表的处理;
Ip数据包解析
(5)为下一个IP包链表创建一个链表头。
6.建立一个进行IP包解析并显示的线程,进行解析IP数据包,然后显示IP数据包。
4.运行结果
截获IP数据包程序运行结果如下:
5.总结
在本次课程设计中,通过多次上机的实践,充分利用所学的计算机网络以及socket编程与C语言编程的知识,并上网搜索一部分相当资料,粗略设计出该程序。
通过本次课程设计,充分运用了所学的计算机网络知识,设计出了如何解析IP数据包,从而更加深刻的了解到了IP数据包的结构及IP协议的相关问题,从而对IP层的工作原理有更好的理解和认识。
Ip数据包解析
在课程设计的过程也碰到的不少问题。例如:对IP数据包的结构不了解、IP层工作原理也不熟悉、C语言编程基础差等一系列问题。让我认识到了自己的很大不足,在以后的学习过程中还将努力提高。
6.源程序代码
#include “winsock2.h” #include “ws2tcpip.h” #include “iostream.h” #include “stdio.h”
#pragma comment(lib, “ws2_32.lib”)
#define IO_RCVALL _WSAIOW(IOC_VENDOR,1)#define BUFFER_SIZE 65535
/* 定义IP头部数据结构 */ typedef struct _IP_HEADER{
union{
};BYTE ServiceType;//服务类型 WORD TotalLen;//总长度 WORD ID;//标识 union{
};BYTE TimeToLive;WORD Flags;WORD FragOff;BYTE Version;//版本(前4位)
BYTE HdrLen;//报头标长(后四位),IP头长度
Ip数据包解析
BYTE Protocol;WORD HdrChksum;DWORD SrcAddr;DWORD DstAddr;BYTE Options;}IP_HEADER;
//逐位解析IP头中的信息,获取版本号 void getVersion(BYTE b,BYTE &version){ }
void getIHL(BYTE b,BYTE &result){ }
//解析服务类型
char * parseServiceType_getProcedence(BYTE b){
version = b>>4;result =(b & 0x0f)*4;switch(b>>5){
case 7: return “Network Control”;case 6: return “Internet work Control”;case 5: return “CRITIC/ECP”;case 4: return “Flash Override”;
Ip数据包解析
}
} case 3: return “Falsh”;case 2: return “Immediate”;case 1: return “Priority”;case 0: return “Routine”;default: return “Unknown”;char * parseServiceType_getTOS(BYTE b){
b=(b>>1)&0x0f;switch(b){ case 0: return “Normal service”;
case 1:
return “Minimize monetary cost”;case 2: return “Maximize reliability”;case 4: return “Maximize throughput”;case 8: return “Minimize delay”;case 15: return “Maximize security”;default: return “Unknown”;
Ip数据包解析
} } /* 获取禁止分片标志和分片标志 */ void getFlags(WORD w,BYTE &DF, BYTE &MF){
}
/* 获取分片偏移量 */ void getFragoff(WORD w,WORD &fragoff){ }
//获取协议
char * getProtocol(BYTE Protocol){
DF=(w>>14)&0x01;MF=(w>>13)&0x01;fragoff=w&0x1ffff;switch(Protocol){
case 1: return “ICMP”;case 2: return “IGMP”;case 3: return “GGP”;case 4: return “IP in IP ”;case 6: return “TCP”;case 8: return “EGP”;case 17:
Ip数据包解析
}
} return “UDP”;case 41: return “IPv6”;case 46: return “OSPF”;default: return “UNKNOWN”;/* 解析IP数据包 */ void ipparse(FILE * file,char *buffer){
IP_HEADER ip = *(IP_HEADER *)buffer;fseek(file,0,SEEK_END);BYTE version;getVersion(ip.Version,version);fprintf(file,“版本=IPV%drn”,version);BYTE headerLen;getIHL(ip.HdrLen,headerLen);fprintf(file,“头长度=%d(BYTE)rn”,headerLen);fprintf(file,“服务类型=%s,%srn”, parseServiceType_getProcedence(ip.ServiceType), parseServiceType_getTOS(ip.ServiceType));fprintf(file,“数据报长度=%d(BYTE)rn”,ip.TotalLen);fprintf(file,“数据报ID=%drn”,ip.ID);/* DF表示禁止分片标志,MF表示分片标记 */ BYTE DF,MF;getFlags(ip.Flags,DF,MF);fprintf(file,“分段标志 DF=%d,MF=%drn”,DF,MF);WORD fragOff;
Ip数据包解析
} getFragoff(ip.FragOff,fragOff);fprintf(file,“分段偏移值=%drn”,fragOff);fprintf(file,“生存期=%d(hops)rn”,ip.TimeToLive);fprintf(file,“协议=%srn”,getProtocol(ip.Protocol));fprintf(file,“头校验和=0x%0xrn”,ip.HdrChksum);fprintf(file,“源IP地址=%srn”,inet_ntoa(*(in_addr*)&ip.SrcAddr));fprintf(file,“目的IP地址=%srn”,inet_ntoa(*(in_addr*)&ip.DstAddr));fprintf(file,“__________________________________rn”);
/* 程序入口 */ int main(int argc,char *argv[]){
/* cmd参数 */ if(argc!=2){
} printf(“usage error!n”);return-1;FILE *file;/* 以读写的方式建立一个文本文件logfile.txt */ if((file=fopen(argv[1],“w+”))==NULL){
}
WSAData wsData;/* 启动2.2版本的Socket,并将Socket版本信息保存到wsData中 */ if(WSAStartup(MAKEWORD(2,2),&wsData)!=0){ printf(“WSA startup failed!n”);return-1;printf(“fail to open file %s”,“logfile.txt”);return-1;
Ip数据包解析
} fprintf(file,“Socket初始化...rn”);fprintf(file,“==================================rn”);fprintf(file,“描述:%srn”,wsData.szDescription);fprintf(file,“状态:%srn”,wsData.szSystemStatus);fprintf(file,“==================================rn”);SOCKET sock;/* 创建原始套接字 */ if((sock=socket(AF_INET,SOCK_RAW,IPPROTO_IP))==INVALID_SOCKET){
}
BOOL flag=true;/* 设置IP头操作选项 */ if(setsockopt(sock,IPPROTO_IP,IP_HDRINCL,(CHAR*)&flag,sizeof(flag))==SOCKET_Eprintf(“Can not create socket!n”);return-1;RROR){
} printf(“setsockopt failed!n”);return-1;char hostName[128];/* 获取本地主机名 */ if(gethostname(hostName,100)==SOCKET_ERROR){
}
hostent *pHostIP;printf(“gethostname failed!n”);return-1;
Ip数据包解析
R){
/* 根据主机名获取主机信息 */ if((pHostIP=gethostbyname(hostName))==NULL){
} printf(“Hostname: %srn”,pHostIP->h_name);printf(“IPAddress: %srn”,inet_ntoa(*((struct in_addr *)pHostIP->h_addr)));printf(“gethostbyname failed!n”);return-1;/* 封装IP地址信息 */ sockaddr_in addr_in;addr_in.sin_addr=*(in_addr*)pHostIP->h_addr_list[0];addr_in.sin_family=AF_INET;addr_in.sin_port=htons(6000);//监听的端口号 /* 把Socket绑定到本地网卡 */ if(bind(sock,(PSOCKADDR)&addr_in,sizeof(addr_in))==SOCKET_ERROR){
}
DWORD dwBufferLen[10];DWORD dwBufferInLen=1;DWORD dwBytesReturned=0;/* 设置网卡为混杂模式 */ if(WSAIoctl(sock,IO_RCVALL,&dwBufferInLen,sizeof(dwBufferInLen), printf(“bind failed”);return-1;&dwBufferLen,sizeof(dwBufferLen),&dwBytesReturned,NULL,NULL)==SOCKET_ERRO
} printf(“icotlsocket failedn”);return-1;
Ip数据包解析
}
char buffer[BUFFER_SIZE];printf(“=============开始解析=============rn”);while(true){
} /* 解除Socket绑定 */ if(WSACleanup()==SOCKET_ERROR){
} /* 关闭文件 */ fclose(file);return 0;printf(“WSACleanup failed!n”);return-1;/* 从套接字接收IP数据报 */ int size=recv(sock,buffer,BUFFER_SIZE,0);if(size>0){
} ipparse(stdout,buffer);ipparse(file,buffer);
第五篇:防火墙的数据包拦截方式小结
防火墙的数据包拦截方式小结
网络防火墙都是基于数据包的拦截技术之上的。在 Windows 下,数据包的拦截方式有很多种,其原理和实现方式也千差万别。总的来说,可分为“用户级”和“内核级”数据包拦截两大类。
用户级下的数据包拦截方式有:
* Winsock Layered Service Provider(LSP)。
* Win2K 包过滤接口(Win2K Packet Filtering Interface)。* 替换 Winsock 动态链接库(Winsock Replacement DLL)。
内核级下的数据包拦截方式有:
* TDI过滤驱动程序(TDI-Filter Driver)。
* NDIS中间层驱动程序(NDIS Intermediate Driver)。* Win2K Filter-Hook Driver。* Win2K Firewall-Hook Driver。* NDIS-Hook Driver。
在这么多种方式面前,我们该如何决定采用哪一种作为自己项目的实现技术?这需要对每一种 方式都有一个大致的了解,并清楚它们各自的优缺点。技术方案的盲目选用往往会带来一些技术 风险。以自己为例,我需要在截包的同时得到当前进程文件名,也就是说,需向用户报告当前是 哪个应用程序要访问网络。在选用 Win2K Filter-Hook Driver 这一方案之后(很多小型开源项
目都采用这一方案),便开始编码。但之后发现 Win2K Filter-Hook Driver 的截包上下文处于内
核进程中,即 IRQL >= DISPATCH_LEVEL,根本无法知道当前应用程序的名字。相比之下,TDI-Filter Driver 和 NDIS-Hook Driver 则可以得知这些信息。其中 TDI-Filter Driver 比 NDIS-Hook Driver 更能准确地获知当前应用程序文件名,后者的接收数据包和少数发送数据
包的场景仍然处于内核进程中。
下面列出了各种截包方式的特点:
* Winsock Layered Service Provider(LSP)该方式也称为 SPI(Service Provider Interface)截包技术。SPI是由 Winsock2 提供的一个
接口,它需要用户机上安装有 Winsock 2.0。Winsock2 SPI 工作在 API 之下的 Driver 之上,可以截获所有基于 Socket 的网络数据包。
优点:
* 以DLL形式存在,编程方便,调试简单。* 数据封包比较完整,未做切片,便于做内容过滤。
缺点: * 拦截不够严密,对于不用 Socket 的网络通讯则无法拦截(如 ICMP),木马病毒很容易绕过。
* Win2K Packet Filtering Interface
这是 Win2K 中一组 API 提供的功能(PfCreateInterface, PfAddFiltersToInterface,...)。
优点:
* 接口简单,实现起来没什么难度。
缺点:
* 功能过于简单,只能提供IP和端口的过滤,可能无法满足防火墙的复杂需求。* 处于 API 层,木马病毒容易绕过。* 只能在 Win2K 以上(含)系统中使用。
* Winsock Replacement DLL 这种方法通过替换系统 Winsock 库的部分导出函数,实现数据报的监听和拦截。
缺点:
* 由于工作在 Winsock 层,所以木马病毒容易绕过。
* TDI-Filter Driver
TDI 的全称是 Transport Driver Interface。传输层过滤驱动程序通过创建一个或多个设备对象
直接挂接到一个现有的驱动程序之上。当有应用程序或其它驱动程序调用这个设备对象时,会首
先映射到过滤驱动程序上,然后由过滤驱动程序再传递给原来的设备对象。
优点:
* 能获取到当前进程的详细信息,这对开发防火墙尤其有用。
缺点:
* 该驱动位于 tcpip.sys 之上,所以没有机会得到那些由 tcpip.sys 直接处理的包,比如ICMP。
* TDI驱动需要重启系统方能生效。
* NDIS Intermediate Driver
也称之为 IM Driver。它位于协议层驱动和小端口驱动之间,它主要是在网络层和链路层之间对
所有的数据包进行检查,因而具有强大的过滤功能。它能截获所有的数据包。
可参考DDK中附带的例子 Passthru。
优点:
* 功能非常强大,应用面广泛,不仅仅是防火墙,还可以用来实现VPN,NAT 和 VLan 等。
缺点:
* 编程复杂,难度较大。
* 中间层驱动的概念是在 WinNT SP4 之后才有的,因此 Win9X 无法使用。* 不容易安装,自动化安装太困难。
* Win2K Filter-Hook Driver 这是从 Win2K 开始提供的一种机制,该机制主要利用 ipfiltdrv.sys 所提供的功能来拦截网络
数据包。Filter-Hook Driver 的结构非常简单,易于实现。但是正因为其结构过于简单,并且
依赖于 ipfiltdrv.sys,微软并不推荐使用。
可参考 CodeProject 上的例子:http://www.xiexiebang.com/KB/IP/drvfltip.aspx
优点:
* 结构简单,易于实现。
* 能截获所有的IP包(包括ICMP包)。
缺点:
* 工作于内核进程中,无法取得当前应用程序进程的信息。
* 虽能截获所有IP包,但无法取得数据包的以太帧(Ethernet Frame)。* 只能在 Win2K 以上(含)系统中使用。
* Win2K Firewall-Hook Driver 这是一种和 Win2k Filter-Hook Driver 差不多的机制,所不同的是,Firewall-Hook Driver 能在 IP Driver 上挂接多个回调函数,所以和前者相比,它引起冲突的可能性更小一些。
可参考 CodeProject 上的例子:http://www.xiexiebang.com/KB/IP/FwHookDrv.aspx
这种方式的优缺点和 Win2K Filter-Hook Driver 基本相同。
* NDIS-Hook Driver
这是一种要重点讲述的截包方式。它是目前大多数网络防火墙所使用的方法。这种方式的做法
是安装钩子到 ndis.sys 中,替换其中的某些关键函数,从而达到截包的目的。在下一节中我
们将详细地介绍它的实现方法。
优点:
* 安装简单,可即时安装和卸载驱动,无需重启系统。
* 能截获所有的IP包,同时能取得数据包的以太帧(Ethernet Frame)。* 安全性高,木马病毒不容易穿透。
* 在大多数情况下,能获取到当前应用程序的进程信息。* 能在 Win98 以上(含)系统中使用。
缺点:
* 接收数据包、或偶尔发送数据包时,驱动工作在内核进程中,无法获得应用程序进程信息。
◎ NDIS-Hook 技术
微软和 3COM 公司在1989年制定了一套开发 Windows 下网络驱动程序的标准,称为 NDIS。
NDIS 的全称是 Network Driver Interface Specification。NDIS为网络驱动的开发提供了一套
标准的接口,使得网络驱动程序的跨平台性更好。
NDIS提供以下几个层次的接口:
1.NDIS 小端口驱动(NDIS Miniport Driver)。这也就是我们常说的网卡驱动。
2.NDIS 协议驱动(NDIS Protocol Driver)。用来实现某个具体的协议栈,如 TCP/IP 协议栈,并向上层导出 TDI 接口。3.NDIS 中间层驱动(NDIS Intermediate Driver)。
这是位于小端口驱动和协议驱动之间的驱动。
NDIS为了给出上述三种接口,提供了一个系统的、完整的 Wrapper。这个 Wrapper 即 ndis.sys。
上面提到的 Miniport Driver、Protocol Driver、Intermediate Driver 均属于插入到这个 Wrapper 中的“模块”,它们调用 Wrapper 提供的函数,同时也向 Wrapper 注册回调函数。
在简单了解了NDIS的机制之后,不难得知,网络防火墙只需要将自己的函数挂钩(Hook)到 ndis.sys
中即可截获网络数据包。NDIS-Hook 技术有两种实现方案:
1.修改 ndis.sys 的 Export Table。
在 Win32 下,可执行文件(EXE/DLL/SYS)都遵从PE格式。所有提供接口的驱动都有 Export Table,因此只要修改 ndis.sys 的 Export Table,就可实现对关键函数的挂接。在实现步骤中,首先
需要得到 ndis.sys 的内存基址,再根据PE格式得到DOS头部结构(IMAGE_DOS_HEADER),进一步得
到NT头部结构(IMAGE_NT_HEADER),最后从头部结构中查得 Export Table 的地址。
由于协议驱动程序(NDIS Protocol Driver)在系统启动时会调用 NdisRegisterProtocol()来向
系统注册协议,因此这种方法关键在于修改 ndis.sys 所提供的 NdisRegisterProtocol、NdisDeRegisterProtocol、NdisOpenAdapter、NdisCloseAdapter、NdisSend 这几个函数的地址。
对于处于系统核心的 ndis.sys 而言,要修改它的内存区域,只有驱动程序才能做到,所以我们
必须编写驱动程序来达到这个目的。
该方案的缺点是加载或卸载驱动后无法立即生效,必须重启系统。且挂钩方法较为复杂。早期凡
使用 NDIS-Hook 的防火墙都采用这一方法,包括著名的费尔防火墙的早期版本(v2.1)。
直到 2004 年,www.xiexiebang.com 上一名黑客公布了一种全新的 NDIS-Hook 技术(即下文即将提
到的第2种方法),诸多防火墙产品才都悄悄对自己的核心技术进行了升级。由于新的挂钩技术更
好,故本文不打算详述修改 Export Table 这一方法的具体细节。
2.向系统注册假协议(Bogus Protocol)。NDIS提供了一个API: NdisRegisterProtocol(),这个API的职责是向系统注册一个协议(如TCPIP),并将该协议作为一个链表节点插入到“协议链表”的头部,最后返回该链表头节点(即新节点)的
地址。正常情况下,只有NDIS协议驱动程序(NDIS Protocol Driver)才会调用这个API。
既然如此,如果我们也调用 NdisRegisterProtocol()向系统注册一个新的协议,我们也就能轻
易地得到“协议链表”的首地址,通过走访这个链表,就能修改其中的某些关键信息,比如关键
函数的地址。修改完毕后,再调用 NdisDeRegisterProtocol()注销掉新协议。这看似一切都没
发生,但事实上目的已经达到了。这个新协议我们称之为假协议(Bogus Protocol)。
通过这种方法,我们可以不用重启系统就能轻松挂接截包函数。当今大多数网络防火墙都采用了
这一方法。近来网上又有人提出了获取协议链表首地址的新的怪异途径,比如获取 tcpip.sys 中全局变量 _ARPHandle 值的方法。不管怎样,相比之下,注册假协议仍不失为一种经典且简单的方法。
本文将详细叙述第2种方案的内部原理和实现细节,即通过注册假协议获取协议链表首地址,遍历
链表并修改其中的函数地址,挂钩自己的函数,从而实现网络截包。在这么做之前,需要先对NDIS
内部维护的几个结构有清楚的认识。另外,由于历史原因,NDIS存在诸多并不完全向下兼容的版本,不同的版本中关键数据域的偏移地址也不尽相同。微软并没有以文档形式提供这些变化的列表。本
文稍后给出这些变化。
* NDIS_PROTOCOL_BLOCK 和 NDIS_OPEN_BLOCK
在NDIS中,所有已注册的协议是通过一个单向的协议链表来维护的。这个单向链表保存了所有已注册 的协议,每个协议对应一个节点。链表节点由 NDIS_PROTOCOL_BLOCK 结构来描述,在这个结构中保存
了注册协议驱动时所指定的各种信息,如支持协议即插即用的回调函数地址等。同时,每个协议驱动
还对应一个 NDIS_OPEN_BLOCK 节点结构的单向链表来维护其所绑定的网卡信息,协议驱动发送和接收
数据包的回调函数地址就保存在这个结构中,是我们要重点修改的对象。
协议与网卡绑定的示意图如下: ┌───┐
│ Head │
└─┬─┘
↓
┌──────────────┐ ┌───────────┐
│ TCPIP Protocol Block ├──→│ RTL8168 Open Block │
└──────┬───────┘ └─────┬─────┘
↓ ↓
┌──────────────┐ ┌───────────┐
│ TCPIP_WANARP Protocol Block│ │ Wireless Open Block │
└──────┬───────┘ └─────┬─────┘
↓ ↓ ┌───┐ ┌───┐
│ NULL │ │ NULL │
└───┘ └───┘ * 得到 NDIS_PROTOCOL_BLOCK 链表的首地址
上文已提到,通过向系统注册假协议,我们即可得到协议链表的首地址。从DDK中可查到 NdisRegisterProtocol()的原型:
EXPORT VOID NdisRegisterProtocol(OUT PNDIS_STATUS Status, OUT PNDIS_HANDLE NdisProtocolHandle, IN PNDIS_PROTOCOL_CHARACTERISTICS ProtocolCharacteristics, IN UINT CharacteristicsLength);
可以看出,我们在调用它时需要传入一个结构 NDIS_PROTOCOL_CHARACTERISTICS,这个结构是我们
在注册协议时必须填写的一张表格,这个表格描述了协议的相关信息。不过既然我们注册的是一个
假协议,所以可以尽量简单地填写它。
NDIS_STATUS
DummyNdisProtocolReceive(IN NDIS_HANDLE ProtocolBindingContext, IN NDIS_HANDLE MacReceiveContext, IN PVOID HeaderBuffer, IN UINT HeaderBufferSize, IN PVOID LookAheadBuffer, IN UINT LookAheadBufferSize, IN UINT PacketSize){ return NDIS_STATUS_NOT_ACCEPTED;}
NDIS_HANDLE
RegisterBogusNdisProtocol(void){ NTSTATUS Status = STATUS_SUCCESS;NDIS_HANDLE hBogusProtocol = NULL;NDIS_PROTOCOL_CHARACTERISTICS BogusProtocol;NDIS_STRING ProtocolName;NdisZeroMemory(&BogusProtocol, sizeof(NDIS_PROTOCOL_CHARACTERISTICS));BogusProtocol.MajorNdisVersion = 0x04;BogusProtocol.MinorNdisVersion = 0x0;
NdisInitUnicodeString(&ProtocolName, L“BogusProtocol”);BogusProtocol.Name = ProtocolName;BogusProtocol.ReceiveHandler = DummyNdisProtocolReceive;NdisRegisterProtocol(&Status, &hBogusProtocol, &BogusProtocol, sizeof(NDIS_PROTOCOL_CHARACTERISTICS));
if(Status == STATUS_SUCCESS)return hBogusProtocol;else return NULL;}
函数 RegisterBogusNDISProtocol()的返回值即是我们想要的协议链表首地址。不过须注意的是,在函数挂钩完成后,应调用 NdisDeregisterProtocol()将假协议注销。另外,在遍历协议链表
进行函数挂钩时,应从首节点的下一个节点开始,因为首节点是我们的假协议节点。
* 修改原有函数地址值实现函数挂钩
上文已提到了和NDIS相关的三个结构: NDIS_PROTOCOL_BLOCK, NDIS_OPEN_BLOCK, NDIS_PROTOCOL_CHARACTERISTICS。
那么我们要替换的函数在哪儿呢?答案是在 NDIS_OPEN_BLOCK 和 NDIS_PROTOCOL_CHARACTERISTICS
这两个结构中,而且重点是前者,因为前者是协议驱动和网卡绑定的纽带。现在的主流网卡都只 调用 NDIS_OPEN_BLOCK 中的收发函数进行发送和接收数据包。但据试验,虚拟机 VMware 有时会
调用 NDIS_PROTOCOL_CHARACTERISTICS 中的函数进行数据包收发。所以为了严谨,我们应该对两
个结构中的函数进行替换。关于这两个结构的定义,读者可以自行查阅DDK文档和头文件。
下面给出示意性代码。简单起见,下列代码均假设当前NDIS的版本为5.0。
BOOLEAN InstallHook(void){ NDIS_STATUS nStatus;NDIS_HANDLE hBogusProtocol = NULL;BYTE *pProtocolChain;
// Get the address of the first NDIS_PROTOCOL_BLOCK node.hBogusProtocol = RegisterBogusNDISProtocol();if(hBogusProtocol == NULL)return FALSE;pProtocolChain =(BYTE*)hBogusProtocol;while(TRUE){ // Get the address of the next node.DWORD dwOffset = 0x10;// for NDIS 5.0 pProtocolChain =((BYTE **)(pProtocolChain + dwOffset))[0];if(!pProtocolChain)break;
HookNdisProtocolBlock(pProtocolChain);}
NdisDeregisterProtocol(&nStatus, hBogusProtocol);return TRUE;} void
HookNdisProtocolBlock(IN BYTE *pProtocolBlock){ PNDIS_PROTOCOL_CHARACTERISTICS pProtoChar;PNDIS_OPEN_BLOCK pOpenBlock;
pProtoChar =(PNDIS_PROTOCOL_CHARACTERISTICS)(pProtocolBlock + 0x14);HookNdisProc(MyReceive,(PVOID *)&pProtoChar->ReceiveHandler);HookNdisProc(MyReceivePacket,(PVOID *)&pProtoChar->ReceivePacketHandler);HookNdisProc(MyBindAdapter,(PVOID *)&pProtoChar->BindAdapterHandler);
pOpenBlock =((PNDIS_OPEN_BLOCK *)pProtocolBlock)[0];while(pOpenBlock){ HookNdisProc(MySend,(PVOID *)&pOpenBlock->SendHandler);HookNdisProc(MyReceive,(PVOID *)&pOpenBlock->ReceiveHandler);HookNdisProc(MyReceivePacket,(PVOID *)&pOpenBlock->ReceivePacketHandler);HookNdisProc(MySendPackets,(PVOID *)&pOpenBlock->SendPacketsHandler);
pOpenBlock = pOpenBlock->ProtocolNextOpen;} } void HookNdisProc(IN PVOID pMyProc, IN PVOID *ppOrgProc){ // TODO: Save the address of the original proc.*ppOrgProc = pMyProc;}
InstallHook()首先得到协议链表的首地址,接着遍历链表,针对系统中的每个(第一个除外)NDIS_PROTOCOL_BLOCK 调用 HookNdisProtocolBlock()函数。HookNdisProtocolBlock()对 NDIS_PROTOCOL_BLOCK 中 NDIS_PROTOCOL_CHARACTERISTICS 和
NDIS_OPEN_BLOCK 链表的每个节点进行函数挂接。
HookNdisProc()用于替换函数地址。给出的代码中它只是简单地替换函数地址,在实际应用中,它还应当保存原始函数的地址值,以供新的函数调用。
* 关键数据域在不同NDIS版本中的差异
由于 NDIS-Hook 并非受微软官方支持的技术,所以相关文档非常缺乏。不仅如此,操作系统的 每次升级,都会同时升级NDIS,而NDIS中的某些数据结构并没有保持向下兼容。最需要注意的
是 NDIS_PROTOCOL_BLOCK。
在 Win9x/Me/NT 的DDK中,NDIS_PROTOCOL_BLOCK 都有明确的定义,但在 Win2K/XP 的DDK中,并没有该结构的详细定义,也就是说该结构在 Win2K 以后(含)的系统中是非公开的。因此开发
人员只能利用各种调试工具来发掘该结构的详细定义。也正是因为如此,NDIS-Hook 方法对平台 的依赖性比较大,需要在程序中判断不同的操作系统版本而使用不同的结构定义。
NDIS_PROTOCOL_BLOCK 的定义可大致认为是这个样子:
typedef struct _NDIS_PROTOCOL_BLOCK { PNDIS_OPEN_BLOCK OpenQueue;REFERENCE Ref;UINT Length;NDIS_PROTOCOL_CHARACTERISTICS ProtocolChars;struct _NDIS_PROTOCOL_BLOCK* NextProtocol;ULONG MaxPatternSize;//...} NDIS_PROTOCOL_BLOCK, *PNDIS_PROTOCOL_BLOCK;
其中 OpenQueue 为 PNDIS_OPEN_BLOCK 链表的首节点地址,NextProtocol 指向下一个
NDIS_PROTOCOL_BLOCK 节点。
在不同的NDIS版本中,该结构中的某些域的偏移地址是不同的,现列于下:
┌───────┬───────────┬───────────┐
│ NDIS Version │ ProtocolChars offset │ NextProtocol offset │
├───────┼───────────┼───────────┤
│ 3.XX │ 0x14 │ 0x04 │
│ 4.XX │ 0x14 │ 0x60 │
│ 4.01 │ 0x14 │ 0x8C │
│ 5.XX │ 0x14 │ 0x10 │
└───────┴───────────┴───────────┘ * 如何在驱动中得到当前NDIS版本? 有两种方法可得到当前NDIS版本。一种是先取得当前操作系统的版本信息,在根据操作系统 的版本得到NDIS的版本。操作系统版本和NDIS版本有一个映射关系,读者可在DDK帮助中查到。
┌───────┬───────┐
│ OS Version │ NDIS Version │
├───────┼───────┤
│ Win95 │ 3.1 │
│ Win95 OSR2 │ 4.0 │
│ Win98 │ 4.1 │
│ Win98 SE │ 5.0 │
│ WinMe │ 5.0 │
│ WinNT 3.5 │ 3.0 │
│ WinNT 4.0 │ 4.0 │
│ WinNT 4.0 SP3│ 4.1 │
│ Win2K │ 5.0 │
│ WinXP │ 5.1 │
│ WinVista │ 6.0 │
└───────┴───────┘
还有一种方法,通过调用 NdisReadConfiguration()直接获取NDIS版本。代码如下:
BOOLEAN GetNdisVersion(OUT DWORD *pMajorVersion, OUT DWORD *pMinorVersion){ NDIS_STATUS nStatus;NDIS_STRING VersionStr = NDIS_STRING_CONST(“NdisVersion”);PNDIS_CONFIGURATION_PARAMETER ReturnedValue;BOOLEAN bResult;NdisReadConfiguration(&nStatus, &ReturnedValue, NULL, &VersionStr, NdisParameterInteger);
bResult =((nStatus == NDIS_STATUS_SUCCESS)? TRUE : FALSE);if(bResult){ //
// The returned value has the NDIS version of the form // 0xMMMMmmmm, where MMMM is major version and mmmm is minor // version so 0x00050000 is 5.0 //
DWORD dwVersion = ReturnedValue->ParameterData.IntegerData;if(pMajorVersion)*pMajorVersion = dwVersion >> 16;if(pMinorVersion)*pMinorVersion = dwVersion & 0xFFFF;}
return bResult;}
须注意的是,GetNdisVersion()必须在 PASSIVE_LEVEL 下运行。所以此函数适合于在 驱动的 DriverEntry()中调用,因为 DriverEntry()一定是处于 PASSIVE_LEVEL 的。