(2008-07-15 13:59:40) 转载 标签:
杂谈
本文背景:
TCP/IP模型很成功,其设计已经经得起多年的磨练。无奈,TCP/IP协议族是很繁杂的一个模型,为了全面理解它,宜采取先全局后局部的庖丁解牛式。本文从应用的角度试着去理解TCP/IP的全貌,配合例子加以讲解。
本文目的:
巩固自己这方面的知识,作为深入TCP/IP协议族的基础。
本文内容:
1. TCP/IP协议族组成
从字面上理解,TCP/IP协议族只有TCP、IP协议,其实不然。其真正的名字是Internet协议族 (Internet Protocol Suite) 。和大型软件一样,其分为四层:应用层、传输层、网络层、链路层。
每一层的功能和目的都是不一样的,每一层上服务的协议也不是有区别的。从上往下看: 应用层(产生|利用数据)
协议:FTP、HTTP、SNMP(网管)、SMTP(Email)等常用协议;
职责:利用应用层协议发送用户的应用数据,比如利用FTP发送文件,利用SMTP发送Email;由系统调用交给运输层处理。 运输层(发送|接收数据)
协议:TCP(有连接)、UDP(无连接);
职责:负责建立连接、将数据分割发送;释放连接、数据重组或错误处理。 网络层(分组|路由数据)
协议:IP、ICMP(控制报文协议)、IGMP(组管理协议); 职责:负责数据的路由,即数据往哪个路由器发送。 链路层(按位发送|接收数据)
协议:以太网卡设备驱动、令牌网卡驱动程序、ARP、RARP等; 职责:负责传输校验二进制用户数据。
从可靠性角度看各层区别:
网络层IP协议是不可靠的协议,为此,如果其上面的层也不做任何特殊处理,也将是不可靠的。于是,运输层的TCP协议弥补了这个空缺,提供有连接的、可校验的数据传输服务。 应用层的话可对数据进行加密之类的处理,增强的是传输数据的安全性,如https。 链路层可对数据进行校验。
从运行进程态看各层区别:
应用层运行在用户程序进程中,属性用户态; 其他层则在系统内核进程运行,属于核心态; 从通信方式上看各层区别:
传输层是端对端的通信,也就是说,处理的是进程与进程之间的通信,如两个TCP进程; 网络层是点对点的通信,也就是说,处理的是机器之间的 逻辑连接。
从传输数据单元上看区别:
传输层上形成的是TCP或UDP报文段; 网络层形成的是IP数据报; 数据链路层形成的是帧(Frame)。
从寻址方式上看各层区别: 网络层通过IP寻址; 链路层通过MAC寻址。 注解:
ICMP: 供IP用于发送错误报文,也可由应用层直接调用;
IGMP: 用于多播(Multicast),比如,UDP可用多播IP地址往多个目标主机发送数据报,就是依靠它。 ARP&RARP: 用户在IP地址与MAC地址互相转换。 2. TCP/IP模型基础设施 IP地址
共分五类地址,分别如下:
A类:0. 0. 0. 0——127.255.255.255(单播) B类:128.0.0.0——191.255.255.255(单播)
C类:192.0.0.0——223.255.255.255(单播) D类:224.0.0.0——239.255.255.255(多播) E类:240.0.0.0——247.255.255.255(待用) 附加类:255.255.255.255 (传输层UDP广播)
MAC地址
每个网卡的MAC地址世界唯一,不可变;计算机通信其实靠的是MAC地址,而不是IP地址,请看下面注解。 端口
端口在硬件里的名称为接口,跟网卡的入口一样;在软件概念里,可以理解为一些数据结构数据缓冲区。 端口可分为:
知名端口:0001——1023 (例如FTP 20,TCP 21,UDP 69) 临时端口:1024——5000 预留端口:5000——65535
假设你的应用程序需要端口,一般是从临时端口分配,只在应用程序运行时有效,故称临时端口。 传输层可将进程与端口进行绑定,当数据到来时,其知该往哪个进程缓冲区里送。 注解:
IP与MAC的区别:IP地址是基于网络拓扑结构的,是动态可变的。MAC地址是由网卡厂商定的,是终身不可变且唯一的。假设应用层利用MAC地址传输数据,那么其是不灵活的,因为它不能变。所以,应用层用IP寻址。
但是,硬件又必须用MAC才能找到机器,为此引入ARP及RARP来做两地址的查询与转换。
3. TCP/IP应用案例分析
场景:左边用户利用FTP客户端与右边FTP服务器端进行连接上传文件。
数据将从上往下流,每到一层都会加上层头,数据以类堆栈形式存储,到目标机器时,底层数据先得,由底向上,符合堆栈先进后出的特性。
图1 TCP/IP内部分解图
步骤1:应用层准备好数据文件,调用Windows API通知传输层TCP建立连接,传输层加入TCP包头,其中包含标识应用层协议的标识符——端口 21。
步骤2:网络层接收了传输层的TCP包,由于IP协议可接收ICMP(1)、IGMP(2)、TCP(6)、UDP(17)来的数据,其需要一个标识域来表明是那个协议发来的数据。此数据域将加于IP包头中。除此之外,还将赋以IP地址。
步骤3:数据链路层接收网络层来的数据后,加之标识域表明数据是从IP、ARP或RARP来。然后,加上MAC地址往外发送。
步骤4:将数据由网卡送出,送的过程中,ARP利用目标IP找到最近的路由器MAC地址,然后将包发往它,之后由它找到一个路由器,最终将数据包送到右边机器的网卡中。 步骤5:根据包头的标识域可知这是一个IP数据包,利用IP协议拆包。 步骤6:根据包头的标识域可知这是一个TCP包,利用TCP协议拆包。
步骤7:根据包头的端口号,将数据直接送入应用层的对应缓冲区中,应用程序负责解析数据包,做相应的业务逻辑处理。 注解:
RFC(Request for Comment):各种Internet的正式标准都以RFC文档形式发布。
各种协议文档:RFC 1122是链路层、网络层、传输层的文档;RFC 1123是应用层的文档;RFC 1600是各种Internet协议的标准化现状
TCP头结构
(2008-07-15 16:16:10) 转载
标签:
杂谈
TCP头结构 TCP头结构
TCP协议头最少20个字节,包括以下的区域(由于翻译不禁相同,文章中给出相应的英文单词):
TCP源端口(Source Port):16位的源端口其中包含初始化通信的端口。源端口和源IP地址的作用是标示报问的返回地址。 TCP目的端口(Destination port):16位的目的端口域定义传输的目的。这个端口指明报文接收计算机上的应用程序地址接口。
TCP序列号(序列码,Sequence Number):32位的序列号由接收端计算机使用,重新分段的报文成最初形式。当SYN出现,序列码实际上是初始序列码(ISN),而第一个数据字节是ISN+1。这个序列号(序列码)是可以补偿传输中的不一致。
TCP应答号(Acknowledgment Number):32位的序列号由接收端计算机使用,重组分段的报文成最初形式。,如果设置了ACK控制位,这个值表示一个准备接收的包的序列码。 数据偏移量(HLEN):4位包括TCP头大小,指示何处数据开始。
保留(Reserved):6位值域,这些位必须是0。为了将来定义新的用途所保留。
标志(Code Bits):6位标志域。表示为:紧急标志、有意义的应答标志、推、重置连接标志、同步序列号标志、完成发送数据标志。按照顺序排列是:URG、ACK、PSH、RST、SYN、FIN。
窗口(Window):16位,用来表示想收到的每个TCP数据段的大小。
校验位(Checksum):16位TCP头。源机器基于数据内容计算一个数值,收信息机要与源机器数值结果完全一样,从而证明数据的有效性。
优先指针(紧急,Urgent Pointer):16位,指向后面是优先数据的字节,在URG标志设置了时才有效。如果URG标志没有被设置,紧急域作为填充。加快处理标示为紧急的数据段。 选项(Option):长度不定,但长度必须以字节。如果 没有选项就表示这个一字节的域等于0。
填充:不定长,填充的内容必须为0,它是为了数学目的而存在。目的是确保空间的可预测性。保证包头的结合和数据的开始处偏移量能够被32整除,一般额外的零以保证TCP头是32位的整数倍。
<附图是用SNIFFER抓的一个包头结构>
00 50 07 45 9b d6 43 3c 47 fd 37 50 50 18 ff 1f 05 a5 00 00 48 54 54 50 2f 31 2e 31 20 32 30 30 20 4f 4b 0d 0a 53 65 72 76 65 72 3a 20 4d 69 63 72 6f 73 6f 66 74 2d 49 49 53 2f 35 2e 30 0d 0a 44 61 74 65 3a 20 57 65 64 2c 20 31 32 20 4e 6f 76 20 32 30 30 33 20 30 33 3a 33 37 3a 35 35 20 47 4d 54 0d 0a 43 6f 6e 6e 65 63 74 69 6f 6e 3a 20 63 6c 6f 73 65 0d 0a 48 54 54 50 2f 31 2e 31 20 32 30 30 20 4f 4b 0d 0a 53 65 72 76 65 72 3a 20 4d 69 63 72 6f 73 6f 66 74 2d 49 49 53 2f 35 2e 30 0d 0a 50 72 61 67 6d 61 3a 20 6e 6f 2d 63 61 63 68 65 0d 0a 43 6f 6e 74 65 6e 74 2d 74 79 70 65 3a 20 74 65 78 74 2f 70 6c 61 69 6e 3b 63 68 61 72 73 65 74 3d 67 62 32 33 31 32 0d 0a 0d 0a 解析:
源端口:00 50 目的端口:07 45 序列号:9b d6 43 3c 应答号:47 fd 37 50 数据偏移量:50 保留:
标志位:18 窗口:ff 1f 校验位:05 a5
优先指针:00 00 选项: 填充:(余下的205字节为TCP数据)
标志控制功能 URG:紧急标志
紧急(The urgent pointer) 标志有效。紧急标志置位,
ACK:确认标志确认编号(Acknowledgement Number)栏有效。大多数情况下该标志位是置位的。TCP报头内的确认编号栏内包含的确认编号(w+1,Figure:1)为下一个预期的序列编号,同时提示远端系统已经成功接收所有数据。 PSH:推标志
该标志置位时,接收端不将该数据进行队列处理,而是尽可能快将数据转由应用处理。在处理 telnet 或 rlogin 等交互模式的连接时,该标志总是置位的。 RST:复位标志复位标志有效。用于复位相应的TCP连接。
SYN:同步标志 同步序列编号(Synchronize Sequence Numbers)栏有效。该标志仅在三次握手建立TCP连接时有效。它提示TCP连接的服务端检查序列编号,该序列编号为TCP连接初始端(一般是客户端)的初始序列编号。在这里,可以把TCP序列编号看作是一个范围从0到4,294,967,295的32位计数器。通过TCP连接交换的数据中每一个字节都经过序列编号。在TCP报头中的序列编号栏包括了TCP分段中第一个字节的序列编号。 FIN:结束标志
带有该标志置位的数据包用来结束一个TCP回话,但对应端口仍处于开放状态,准备接收后续数据。服务端处于监听状态,客户端用于建立连接请求的数据包(IP packet)按照TCP/IP协议堆栈组合成为TCP处理的分段(segment)。
分析报头信息: TCP层接收到相应的TCP和IP报头,将这些信息存储到内存中。
检查TCP校验和(checksum):标准的校验和位于分段之中(Figure:2)。如果检验失败,不返回确认,该分段丢弃,并等待客户端进行重传。
查找协议控制块(PCB{}):TCP查找与该连接相关联的协议控制块。如果没有找到,TCP将该分段丢弃并返回RST。(这就是TCP处理没有端口监听情况下的机制) 如果该协议控制块存在,但状态为关闭,服务端不调用connect()或listen()。该分段丢弃,但不返回RST。客户端会尝试重新建立连接请求。
建立新的socket:当处于监听状态的socket收到该分段时,会建立一个子socket,同时还有socket{},tcpcb{}和pub{}建立。这时如果有错误发生,会通过标志位来拆除相应的socket和释放内存,TCP连接失败。如果缓存队列处于填满状态,TCP认为有错误发生,所有的后续连接请求会被拒绝。这里可以看出SYN Flood攻击是如何起作用的。 丢弃:如果该分段中的标志为RST或ACK,或者没有SYN标志,则该分段丢弃。并释放相应的内存。 发送序列变量
SND.UNA : 发送未确认 SND.NXT : 发送下一个 SND.WND : 发送窗口 SND.UP : 发送优先指针
SND.WL1 : 用于最后窗口更新的段序列号 SND.WL2 : 用于最后窗口更新的段确认号 ISS : 初始发送序列号 接收序列号
RCV.NXT : 接收下一个 RCV.WND : 接收下一个 RCV.UP : 接收优先指针 IRS : 初始接收序列号 当前段变量
SEG.SEQ :段序列号 SEG.ACK :段确认标记 SEG.LEN :段长 SEG.WND :段窗口 SEG.UP :段紧急指针 SEG.PRC :段优先级
CLOSED表示没有连接,各个状态的意义如下: LISTEN :监听来自远方TCP端口的连接请求。 SYN-SENT :在发送连接请求后等待匹配的连接请求。
SYN-RECEIVED : 在收到和发送一个连接请求后等待对连接请求的确认。 ESTABLISHED :代表一个打开的连接,数据可以传送给用户。
FIN-WAIT-1 :等待远程TCP的连接中断请求,或先前的连接中断请求的确认。 FIN-WAIT-2 :从远程TCP等待连接中断请求。 CLOSE-WAIT :等待从本地用户发来的连接中断请求。 CLOSING :等待远程TCP对连接中断的确认。
LAST-ACK :等待原来发向远程TCP的连接中断请求的确认。
TIME-WAIT :等待足够的时间以确保远程TCP接收到连接中断请求的确认。 CLOSED :没有任何连接状态。
TCP连接过程是状态的转换,促使发生状态转换的是用户调用:OPEN,SEND,RECEIVE,CLOSE,ABORT和STATUS。传送过来的数据段,特别那些包括以下标记的数据段SYN,ACK,RST和FIN。还有超时,上面所说的都会时TCP状态发生变化。
序列号:请注意,我们在TCP连接中发送的字节都有一个序列号。因为编了号,所以可以确认它们的收到。对序列号的确认是累积性的。TCP必须进行的序列号比较操作种类包括以下几种:
①决定一些发送了的但未确认的序列号。 ②决定所有的序列号都已经收到了。 ③决定下一个段中应该包括的序列号。
对于发送的数据TCP要接收确认,确认时必须进行的: SND.UNA = 最老的确认了的序列号。 SND.NXT = 下一个要发送的序列号。
SEG.ACK = 接收TCP的确认,接收TCP期待的下一个序列号。 SEG.SEQ = 一个数据段的第一个序列号。 SEG.LEN = 数据段中包括的字节数。
SEG.SEQ+SEG.LEN-1 = 数据段的最后一个序列号。
如果一个数据段的序列号小于等于确认号的值,那么整个数据段就被确认了。而在接收数据时下面的比较操作是必须的: RCV.NXT = 期待的序列号和接收窗口的最低沿。
RCV.NXT+RCV.WND:1 = 最后一个序列号和接收窗口的最高沿。 SEG.SEQ = 接收到的第一个序列号。
SEG.SEQ+SEG.LEN:1 = 接收到的最后一个序列号
ARP Frame 字段 硬件类型 长度(Byte) 2 2 1 1 2 6 4 6 4 18 填充值 1 0800 6 4 1 aaaaaaaaaaaa 192.168.0.1 上层协议类型 MAC地址长度 IP地址长度 操作码 发送方MAC 发送方IP 接收方MAC 接收方IP 填充数据
图4 ARP请求包中 ARP帧的内容
任意值 xxxxxxxxxxxx 192.168.0.99 0 如果我们构造一个这样的包发送出去,如果 192.168.0.99存在且是活动的,我们马上就会收到一个192.168.0.99发来的一个响应包,我们可以查看一下我们的ARP缓存列表,是不是多了一项类似这样的条目:
192.168.0.99 bb-bb-bb-bb-bb-bb 是不是很神奇呢?
我们再来看一下ARP响应包的构造
2) 响应包的填充
有了前面详细的解说,你肯定就能自己说出响应包的填充方法来了吧,所以我就不细说了,列两个表就好了
比如说给 192.168.0.99(MAC为 bb-bb-bb-bb-bb-bb)发一个ARP响应包,告诉它我们的MAC地址为 aa-aa-aa-aa-aa-aa,就是如此来填充各个字段
DLC Header 字段 长度(Byte) 6 6 2 填充值 接收方MAC 发送方MAC Ethertype
图5 ARP响应包中 DLC Header内容
bbbbbbbbbbbb aaaaaaaaaaaa 0x0806 ARP Frame 字段 硬件类型 上层协议类型 MAC地址长度 IP地址长度 操作码 发送方MAC 发送方IP 接收方MAC 接收方IP 填充数据
图6 ARP响应包中 ARP帧的内容
这样192.168.0.99的ARP缓存中就会多了一条关于我们192.168.0.1的地址映射。 好了,终于到了编程实现它的时候了^_^
我们主要是要用到 pcap_open_live 函数,不过这个函数winpcap的开发小组已经建议用 pcap_open 函数来代替,不过因为我的代码里面用的就是pcap_open_live,所以也不便于修改了,不过pcap_open_live使用起来也是没有任何问题的,下面是pcap_open_live的函数声明:
长度(Byte) 2 2 1 1 2 6 4 6 4 18 填充值 1 0800 6 4 2 aaaaaaaaaaaa 192.168.0.1 bbbbbbbbbbbb 192.168.0.99 0
虽然看起来比较复杂,不过用起来还是非常简单的,其实 1 行就OK了: pcap_t* adhandle;
char errbuf[PCAP_ERRBUF_SIZE]; // 打开网卡,并且设置为混杂模式 // pCardName是前面传来的网卡名字参数
adhandle = pcap_open_live(pCardName,65535,1,1000,errbuf);
C. 截获数据包并保存为文件:------------------------------------------------------
当然,不把数据包保存为文件也可以,不过如果不保存的话,只能在截获到数据包的那一瞬间进行分析,转眼就没了^_^ 所以,为了便于日后分析,所以高手以及我个人经常是把数据包保存下来的慢慢分析的。
但是注意网络流量,在流量非常大的时候注意硬盘空间呵呵,常常几秒中就有好几兆是很正常的事情。 下面首先来详细讲解一下,这个步骤中需要用到的winpcap函数:
下面给出一段完整的捕获数据包的代码,是在线程中写的,为了程序清晰,我去掉了错误处理代码以及线程退出的代码,完整代码可下载文后的示例源码,老规矩,重要的步骤用粗体字标出。
我们实际在捕获数据包的时候也最好是把代码放到另外的线程中。
UINT CaptureThread(LPVOID pParam) {
const char* pCardName=(char*)pParam; // 转换参数,获得网卡名字
pcap_t* adhandle;
char errbuf[PCAP_ERRBUF_SIZE]; // 打开网卡,并且设置为混杂模式
adhandle=pcap_open_live(pCardName,65535,1,1000,errbuf); {
pcap_dumper_t* dumpfile; // 建立存储截获数据包的文件
dumpfile=pcap_dump_open(adhandle, \"Packet.dat\"); int re;
pcap_pkthdr* header; // Header u_char* pkt_data; // 数据包内容指针 // 从网卡或者文件中不停读取数据包信息
while((re=pcap_next_ex(adhandle,&header,(const u_char**)&pkt_data))>=0) {
// 将捕获的数据包存入文件
pcap_dump((unsigned char*)dumpfile,header,pkt_data); } return 0; }
将个线程加入到程序里面启动以后。。。等等,如何来启动这个线程就不用我说了吧,类似这样的代码就可以 ::AfxBeginThread(CaptureThread,chNIC); // chNIC是网卡的名字,char* 类型
启动线程一段时间以后(几秒中就有效果了),可以看到数据包已经被成功的截获下来,并存储到程序目录下的Packet.dat文件中。 =====================================================
至此,数据包的截获方法就讲完了,大家看了这篇文章,其实你就一定也明白了,无论是raw socket的方法还是winpcap的方法,其实都很简单的,真的没有什么东西,只是会让不明白原理的人看起来很神秘而已,isn’t it?
呵呵,不过也不要高兴的太早,这个保存下来的数据包文件,你可以试着用UltraEdit打开这个文件看看,是不是大部分都是乱码?基本上没有什么可读性,这是因为:
此时捕获到的数据包并不仅仅是单纯的数据信息,而是包含有 IP头、 TCP头等信息头的最原始的数据信息,这些信息保留了它在网络传输时的原貌。通过对这些在低层传输的原始信息的分析可以得到有关网络的一些信息。由于这些数据经过了网络层和传输层的打包,因此需要根据其附加的帧头对数据包进行分析。 呵呵,所以我们要走的路还很长,这只是刚刚入门而已^_^
二. 发送ARP包的编程实现 1. 填充数据包
上面的那些关于ARP包各个字段的表格,对应在程序里就是结构体,对应于上面的表格,于是我们需要三个下面这样的结构体 // DLC Header
typedef struct tagDLCHeader {
unsigned char DesMAC[6]; unsigned char SrcMAC[6]; unsigned short Ethertype; } DLCHEADER, *PDLCHEADER; // ARP Frame
typedef struct tagARPFrame {
unsigned short HW_Type; unsigned short Prot_Type; unsigned char HW_Addr_Len; unsigned char Prot_Addr_Len; unsigned short Opcode;
unsigned char Send_HW_Addr[6]; unsigned long Send_Prot_Addr; unsigned char Targ_HW_Addr[6]; unsigned long Targ_Prot_Addr; unsigned char padding[18]; } ARPFRAME, *PARPFRAME;
// ARP Packet = DLC header + ARP Frame typedef struct tagARPPacket {
DLCHEADER dlcHeader; ARPFRAME arpFrame; } ARPPACKET, *PARPPACKET;
这些结构体一定能看懂吧,在程序中就是对号入座就好了 1. 填充数据包
下面我举个填充包头的例子,我首先定义个了一个转换字符的函数,如下
void formatStrToMAC(const LPSTR lpHWAddrStr, unsigned char *HWAddr) {
unsigned int i, index = 0, value, temp; unsigned char c;
_strlwr(lpHWAddrStr); // 转换成小写
for (i = 0; i < strlen(lpHWAddrStr); i++) {
c = *(lpHWAddrStr + i);
if (( c>=’0’ && c<=’9’ ) || ( c>=’a’ && c<=’f’ ))
{
if (c>=’0’ && c<=’9’) temp = c - ’0’; // 数字 if (c>=’a’ && c<=’f’) temp = c - ’a’ + 0xa; // 字母 if ( (index % 2) == 1 ) {
value = value*0x10 + temp; HWAddr[index/2] = value; }
else value = temp; index++; }
if (index == 12) break; } }
// 开始填充各个字段
ARPPACKET ARPPacket; // 定义ARPPACKET结构体变量
memset(&ARPPacket, 0, sizeof(ARPPACKET)); // 数据包初始化
formatStrToMAC(“DLC源MAC字符串”,ARPPacket.dlcHeader.SrcMAC); // DLC帧头 formatStrToMAC(“DLC目的MAC字符串”,ARPPacket.dlcHeader.DesMAC);
formatStrToMAC(“ARP源MAC字符串”,ARPPacket.arpFrame.Send_HW_Addr); // 源MAC ARPPacket.arpFrame.Send_Prot_Addr = inet_addr(srcIP); // 源IP
formatStrToMAC(“ARP目的MAC字符串”,ARPPacket.arpFrame.Targ_HW_Addr); // 目的MAC ARPPacket.arpFrame.Targ_Prot_Addr = inet_addr(desIP); // 目的IP
ARPPacket.arpFrame.Opcode = htons((unsigned short)arpType); // arp包类型
// 自动填充的常量
ARPPacket.dlcHeader.Ethertype = htons((unsigned short)0x0806); // DLC Header的以太网类型 ARPPacket.arpFrame.HW_Type = htons((unsigned short)1); // 硬件类型
ARPPacket.arpFrame.Prot_Type = htons((unsigned short)0x0800); // 上层协议类型
ARPPacket.arpFrame.HW_Addr_Len = (unsigned char)6; // MAC地址长度 ARPPacket.arpFrame.Prot_Addr_Len = (unsigned char)4; // IP地址长度
That’s all ! ^_^
填充完毕之后,我们需要做的就是把我们的ARPPACKET结构体发送出去
2.发送ARP数据包:
我们发送ARP包就要用到winpcap的api了,具体步骤及函数是这样的,为了简单易懂,我把错误处理的地方都去掉了,详见代码 void SendARPPacket()
{
char *AdapterDeviceName =GetCurAdapterName(); // 首先获得获得网卡名字
lpAdapter = PacketOpenAdapter(AdapterDeviceName); // 根据网卡名字打开网卡
lpPacket = PacketAllocatePacket(); // 给PACKET结构指针分配内存
PacketInitPacket(lpPacket, &ARPPacket, sizeof(ARPPacket)); //初始化PACKET结构指针 // 其中的ARPPacket就是我们先前填充的ARP包
PacketSetNumWrites(lpAdapter, 1); // 每次只发送一个包
PacketSendPacket(lpAdapter, lpPacket, true) // Send !!!!! ^_^
PacketFreePacket(lpPacket); // 释放资源 PacketCloseAdapter(lpAdapter); }
呵呵,至此,关于ARP包最关键的部分就讲完了,你现在就可以来随心所欲的发送自己的ARP包了
既然作为一篇“科普文章”,接下来我再讲一讲与整个项目有关的附加步骤以及说明
三.附加步骤以及说明
1. 如何在VC中使用winpcap驱动
虽然winpcap开发包使用起来非常简便,但是前期准备工作还是要费一番功夫的,缺一不可。^_^ 首先就是要安装它的驱动程序了,可以到它的主页下载,更新很快的
http://winpcap.polito.it/install/default.htm
下载WinPcap auto-installer (driver +DLLs),直接安装就好了,或者我提供的代码包里面也有。
希望以后用winpcap作开发的朋友,还需要下载 Developer’s pack,解压即可。
然后,需要设置我们工程的附加包含目录为我们下载Developer’s pack开发包的Inclulde目录,连接器的附加依赖库设置为Developer’s pack的lib目录。 当然,因为我们的工作比较简单,就是借用winpcap发送数据包而已,所以只用从 winpcap开发包的include文件夹中,拷贝Packet32.h,到我们的工程来,并且包含它就可 以,但是要注意,Packet32.h本身还要包含一个Devioctl.h,也要一并拷贝进来,当然还有运 行库Packet.lib,一共就是需要拷贝3个文件了,如果加入库不用我多说了吧,在工程里面设 置,或者是在需要它的地方加入 #pragma comment(lib, \"Packet.lib\")了。
整个项目其实可以分为四个部分,填充数据包、发送数据包、枚举系统网卡列表和 相关信息以及枚举系统ARP缓存列表,下面我再讲一下如何获得系统的网卡以及ARP列 表,这两个部分都要用到IP Helper的api,所以要包含以及库文件Iphlpapi.lib, 其实都是很简单的,只用寥寥几行就OK了 2. 枚举系统网卡以及信息
最好是先定义关于网卡信息的一个结构体,这样显得结构比较清晰 // 网卡信息
typedef struct tagAdapterInfo
{
char szDeviceName[128]; // 名字 char szIPAddrStr[16]; // IP
char szHWAddrStr[18]; // MAC
DWORD dwIndex; // 编号 }INFO_ADAPTER, *PINFO_ADAPTER;
************************** delete pIpNetTable; }
}
这样一来,我们基本上大功告成了,其他还有一些东西在这里就不讲了,大家可以下载我的代码看看就好了。 下面我们来用ARP包玩一些小把戏 ^_^。 四.ARP包的游戏
既然我们可以自己来填充数据包,那么来玩些ARP的“小游戏”欺骗就是易如反掌了,当然,是在没有安全防护的网络里,比如只有hub或者交换机把你们相连,而没有路由分段……^_^ 下面我就由浅入深的讲一些介绍一些关于ARP的小伎俩。 1. 小伎俩
1) 你可以试着发一个请求包广播,其中的ARP帧里关于你的信息填成这样: (为了节省篇幅,我只写需要特别指出的填充字段)
发送方MAC 6 随便乱填一个错误的 发送方IP 4 填上你的IP 出现什么结果?是不是弹出一个IP地址冲突的提示?呵呵,同样的道理,如果发送方IP填成别人的,然后每隔1秒发一次………..-_-b
2) 比如你们都靠一个网关192.168.0.1 上网 ,如果你想让192.168.0.77 上不了网,就可以伪装成网关给192.168.0.77发一个错误的ARP响应包, like this
发送方MAC 6 随便乱填一个错误的 发送方IP 接收方就填192.168.0.77的相关信息,发送之后,它还能上网不?
这样能折腾他好一阵子了,只要它的系统得不到正确的到网关的ARP映射表它就一直上不了网了
单片机实现TCP/IP(HTTP协议篇1)
HTTP工作在TCP/IP5层协议的最高层,是基于TCP协议的。
HTTP为不同类型的报文提供了不同的首部,当然首部有很多关键词,用来向服务器表明这个数据包是POST还是GET.
我们将需要显示的网页以字符串数组的形式保存在单片机内部存储器中,而一些关键词(比如要显示的温度)则用特殊字符串替代(比如:“TAG1”),需要发送响应的时候,扫描一遍html文档,查找到这些关键词,用自己的实时数据替代就好了。
关于http的一些知识是还没弄懂,先贴代码和注释: //----------------------------------------------------------------------------- // Net HTTP.C
//
// This module is the Web Server
// It currently serves a html text page and a jpeg image, or handles // a POST message to turn an LED on or off.
// The HTTP protocol specification is at http://www.w3.org/Protocols/ //----------------------------------------------------------------------------- #include #include #include 4 网关IP 192.168.0.1 #include \"ip.h\" #include \"tcp.h\" #include \"http.h\" //这些结构保存连接信息跟踪 extern CONNECTION xdata conxn[]; //TCB缓冲区,在其他文件里已经声明过,因此用extern声明 extern ULONG code my_ipaddr; extern char xdata text[]; extern UINT idata cpu_temperature; extern UINT idata air_temperature; extern UINT idata cpu_voltage; extern char code html_header[]; //html的首部 extern char code web_page[]; //web网页存储区 extern char code jpeg_header[]; //图像首部 extern UCHAR code photo1_jpeg[]; //图形的编码 extern UCHAR idata rcve_buf_allocated; //输入缓冲区 extern UCHAR idata debug; bit CONTROL_LED; // void LightONOFF(bit b); //灯控制位 void init_http(void) { CONTROL_LED = 0; LightONOFF(CONTROL_LED); } //------------------------------------------------------------------------ / /此函数是标准的字符串搜索。图书馆的Keil / /不提供。它将查找一字符串在另一个字符串 / /并返回一个指针,如果找到,否则返回NULL。 //------------------------------------------------------------------------ /******这个函数实现在一个字符串中查找另一个字符串的功能********/ char * strstr(char * haystack, char * needle) { char *ptr1, *ptr2; // Protect against NULL pointer if (*needle == 0) return(haystack); for( ; *haystack; haystack++ ) { // Look for needle in haystack. If there is a // match then this will continue all the way // until ptr1 reaches the NULL at the end of needle for(ptr1 = needle, ptr2 = haystack; *ptr1 && (*ptr1 == *ptr2); ++ptr1, ++ptr2); // If there is a match then return pointer to needle in haystack if(*ptr1 == 0) return(haystack); } return NULL; // no matching string found } //------------------------------------------------------------------------ // This sends an TCP segment to the ip layer. The segment is // is normally either a web page or a graphic. // See \"TCP/IP Illustrated, Volume 1\" Sect 17.3 //------------------------------------------------------------------------ /*****传送一个TCP数据包到IP层******/ void http_send(UCHAR xdata * outbuf, UINT len, UCHAR nr) { TCP_HEADER xdata * tcp; //TCP首部 IP_HEADER xdata * ip; //IP首部 ULONG idata sum; UINT idata result; //用于求校验和 // Fill in TCP segment header tcp = (TCP_HEADER xdata *)(outbuf + 34); //TCP首部截取输出缓冲区 ip = (IP_HEADER xdata *)(outbuf + 14); //IP首部截取输出缓冲区 tcp->source_port = HTTP_PORT; tcp->dest_port = conxn[nr].port; tcp->sequence = conxn[nr].my_sequence; tcp->ack_number = conxn[nr].his_sequence; // Header is always 20 bytes long tcp->flags = 0x5000 | FLG_ACK | FLG_PSH; tcp->window = 1024; tcp->checksum = 0; tcp->urgent_ptr = 0; // Compute checksum including 12 bytes of pseudoheader // Must pre-fill 2 items in ip header to do this ip->dest_ipaddr = conxn[nr].ipaddr; ip->source_ipaddr = my_ipaddr; // Sum source_ipaddr, dest_ipaddr, and entire TCP message sum = (ULONG)cksum(outbuf + 26, 8 + len); // Add in the rest of pseudoheader which is // protocol id and TCP segment length sum += (ULONG)0x0006; sum += (ULONG)len; // In case there was a carry, add it back around result = (UINT)(sum + (sum >> 16)); tcp->checksum = ~result; //以上为填充IP首部和TCP首部的相关字段 if (debug) serial_send(\"TCP: Sending msg to IP layer\\r\"); ip_send(outbuf, conxn[nr].ipaddr, TCP_TYPE, len); //发送Ip数据报 // (Re)start TCP retransmit timer conxn[nr].timer = TCP_TIMEOUT; //用于TCP数据包的超时重发 } //------------------------------------------------------------------------ // This searches a web page looking for a specified tag. If found, // it replaces the tag with the text in * sub. Tags are fixed length - // The first 4 chars of the tag is always \"TAG:\" and the rest of it // is always 4 chars for a total of 8 chars. //------------------------------------------------------------------------ /******搜索网页页面,寻找一些特殊的标签,用sub中的数据替代这些标签*****/ void replace_tag(UCHAR xdata * start, char * tag, char * sub) { UCHAR idata i, flg; UCHAR xdata * ptr; // Find tag. If not found - just return ptr = strstr(start, tag); //用上面的函数搜索是否存在tag字段 if (ptr == NULL) return; flg = TRUE; // Replace the 8 char tag with the substitute text // Pad on the right with spaces for (i=0; i < 8; i++) //替代tag字段所在的8个字节 { if (sub[i] == 0) flg = FALSE; if (flg) ptr[i] = sub[i]; else ptr[i] = SPACE; } } //------------------------------------------------------------------------ // This serves up either a HTML page, a JPEG image, or controls an // LED, depending what it gets from the browser. The received header // must contain the word \"GET\" or \"POST\" to be considered a valid request. // With HTTP 1.1 where the connection is left open, the header I send // should include content length. With HTTP 1.0 you can just close the // connection after sending the page and the browser knows its done. // // The HTTP protocol specification is at http://www.w3.org/Protocols/ //------------------------------------------------------------------------ /*********根据客户端浏览器的不同请求,处理服务器端的相关操作************/ UINT http_server(UCHAR xdata * inbuf, UINT header_len, UCHAR nr, UCHAR resend) { UCHAR i; UINT idata body_len, hhdr_len, jhdr_len, page_len, jpeg_len; UINT idata sent, remaining; UCHAR xdata * outbuf; UCHAR xdata * ptr; UCHAR xdata * tcp_data; UCHAR idata request; static UCHAR idata post_flg = FALSE; // Make sure this is a valid connection if (nr == NO_CONNECTION) return 0; //无连接,则返回 // Compute start of TCP data // Save first 20 chars and seq number just in case // we need to re-generate page // TODO: if post, then save switch state infomation if (!resend) { tcp_data = inbuf + 34 + header_len; //如果当前不是处于重发状态,那么将tcp_data定位到inbuf的特定位置(IP+以太网首部+TCP首部,三个偏移量) memcpy(conxn[nr].query, tcp_data, 20); //将20个字节拷贝到缓存 conxn[nr].old_sequence = conxn[nr].my_sequence; //更新old_sequence字段 } // If this is a resend, set sequence number to what it was // the last time we sent this else { tcp_data = inbuf; conxn[nr].my_sequence = conxn[nr].old_sequence; } //如果是重发状态,那么将自己的sequence设置为old sequence,此时inbuf里存的已经都是净荷数据了 // Start off with no request request = NONE; //此时没有请求 // TODO: Calling strstr() on a large buffer takes a lot of time // so perhaps we could speed things up by limiting the search // range to the portion of the buffer where the item is expected // to be found // If it is a POST, then set a flag to start looking for the post // data of interest, which is the string \"switch=\". It may arrive // in a later segment (Netscape seems to split up the POST message) if (strstr(tcp_data, \"POST\") != NULL) post_flg = TRUE; //如果TCP数据段里含有post,将post标志位置位 // See if this is a GET message else if (strstr(tcp_data, \"GET\") != NULL) //如果客户端向服务器索取资源 { post_flg = FALSE; //屏蔽资源索取标志位 if (strstr(tcp_data, \"photo1\") != NULL) request = GET_JPEG; //获取图片 else if (strstr(tcp_data, \"index\") != NULL) request = GET_PAGE; // else if (strstr(tcp_data, \"/ \") != NULL) request = GET_PAGE; //获取页面 } IP数据包的头 typedef struct _IP_HEADER { BYTE bVerAndHLen ; // 版本信息(前4位)和头长度(后4位) BYTE bTypeOfService ; // 服务类型 8bit USHORT nTotalLength ; // 数据包长度16bit USHORT nID ; // 数据包标识16bit USHORT nReserved ; // 保留字段16bit BYTE bTTL ; // 生成时间8bit BYTE bProtocol ; // 协议类型8bit USHORT nCheckSum ; // 校验和16bit UINT nSourIp ; // 源IP32bit UINT nDestIp ; // 目的IP32bit } IP_HEADER, *PIP_HEADER ; TCP数据包的头 typedef struct _TCP_HEADER { USHORT nSourPort ; // 源端口号16bit USHORT nDestPort ; // 目的端口号16bit UINT nSequNum ; // 序列号32bit UINT nAcknowledgeNum ; // 确认号32bit USHORT nHLenAndFlag ; // 前4位:TCP头长度;中6位:保留;后6位:标志位16bit USHORT nWindowSize ; // 窗口大小16bit USHORT nCheckSum ; // 检验和16bit USHORT nrgentPointer ; // 紧急数据偏移量16bit } TCP_HEADER, *PTCP_HEADER ; UDP数据包的头 typedef struct _UDP_HEADER { USHORT nSourPort ; // 源端口号16bit USHORT nDestPort ; // 目的端口号16bit USHORT nLength ; // 数据包长度16bit USHORT nCheckSum ; // 校验和16bit } UDP_HEADER, *PUDP_HEADER ; 进入协议栈的过程:(从协议栈出来刚好相反) ICMP头和报文校验和的计算 TCP/IP 2010-03-30 11:13:51 阅读33 评论0 字号:大中小 ////////////////////////////////定义ICMP包头 typedef struct _ICMP_HEADER { BYTE bType ; // 类型8bit BYTE bCode ; // 代码8bit USHORT nCheckSum ; // 校验和16bit USHORT nId ; // 标识,本进程ID16bit USHORT nSequence ; // 序列号16bit UINT nTimeStamp ; // 可选项,这里为时间,用于计算时间32bit } ICMP_HEADER, *PICMP_HEADER ; ///////////////////////////////// 发送ICMP报文时,必须由程序自己计算校验和,将它填入ICMP头部对应的域中。校验和的计算方法是: 将数据以字(16位)为单位累加到一个双字中(强转换双字类型),如果数据长度为奇数(奇数个字节),最后一个字节将被扩展到字,累加的结果是一个双字, 最后将这个双字的高16位和低16位相加后取反,便得到了校验和! // 计算ICMP包校验值 // 参数1:ICMP包缓冲区 // 参数2:ICMP包长度 USHORT GetCheckSum ( LPBYTE lpBuf, DWORD dwSize ) { DWORD dwCheckSum = 0 ; USHORT* lpWord = (USHORT*)lpBuf ; // 累加 while ( dwSize > 1 ) { dwCheckSum += *lpWord++ ; dwSize -= 2 ; } // 如果长度是奇数 if ( dwSize == 1 ) dwCheckSum += *((LPBYTE)lpWord) ; // 高16位和低16位相加 dwCheckSum = ( dwCheckSum >> 16 ) + ( dwCheckSum & 0xFFFF ) ; // 取反 return (USHORT)(~dwCheckSum ) ; } 因篇幅问题不能全部显示,请点此查看更多更全内容