在从事网络产品尤其是网络安全产品开发时,我们一直面临着一个问题,就是对产品的TCP/IP 协议栈进行稳定性或安全性测试,确保开发产品在遇到各种不规则的错误的IP 包时仍可正常稳定高效地工作,我们知道,在正常的网络环境中,很难产生错误的IP 包,也很难产生我们想要的错误的IP 包,为此,要完成对产品的测试,我们必须自己来制造各种各样错误的IP 包,本篇的目的就是介绍如何利用各种发包工具来制造自己想要的错误的IP 包。SENDIP 是一个LINUX 下的命令行工具,可以通过命令行参数的方式发送各种格式的IP 包,它有大量的命令行参数来规定各种协议的头格式,目前可支持NTP, BGP, RIP, RIPng,TCP, UDP, ICMP 或raw IPv4 和IPv6 包格式,并且可以随意在包中添加数据。
帮助文件说明
Usage: sendip [-v] [-d data] [-h] [-f datafile] [-p module] [module options] hostname
-d data add this data as a string to the end of the packet
Data can be:
rN to generate N random(ish) data bytes;
0x or 0X followed by hex digits;
0 followed by octal digits;
any other stream of bytes
-f datafile read packet data from file
-h print this message
-p module load the specified module (see below)
-v be verbose
参数含义:
-v: 详细模式,即打印出你发送报文的内容,类似于debug模式,建议配置该参数,可以清楚的看到发送的报文内容是否正确。
-d: 添加data字段的内容,有option字段的话放在options字段之后,任意添加。也可以一次用文件导入,不过参数是下面的-f。
-h: 显示上面的帮助文件。
-f: 在data字段添加参数后所指的文件里的内容,参数后跟的内容就是一个文件名。
-p: 指定发送报文的类型,选项就是帮助提示中的ipv4 ipv6 icmp tcp udp bgp rip ntp的8种类型,注意各个协议之间的搭配使用,例如ntp是用udp传输,而rip是用tcp传输。这个参数可以复用来更加精确的确定一个报文的类型和各个字段,例如:-p ipv4 –p tcp –p rip是可以一起用的。该参数必须配置。
hostname: 直接输入ip地址即可,也可以是主机名,但是之前要把主机名和对应的ip写入到/etc/hosts的文件中。该参数必须配置。注意不需要输入hostname这个字段,要不是ip,要不是主机名。
配置完以上参数后就可以发送报文了,但是具体报文的各个字段都是default配置,并没有达到自己要构造报文的目的。下面以ipv4、udp、tcp、icmp为例根据man文件的内容来说明各个字段的构造方法,ipv6、rip、bgp和ntp希望后面有时间再补充。
IPV4报文的构造
先看man文件中显示支持可以构造的IPV4报文字段有哪些,然后在参数后直接说明该字段的含义。构造报文首先要求对报文的各个字段非常熟悉,所以先看一下IP首部的图表:
IP首部:
0 15 16 31
4位版 本 | 首部长 度 | 8位 服务类型(TOS) | 16位 总长度(字节) | |
16位 标识 | 3位标志 | 13位 片偏移 | ||
8位 生存时间TTL | 8位 协议 | 16位 首部检验和 | ||
32位 源IP地址 | ||||
32位 目的IP地址 | ||||
32位 选项(若有) | ||||
数据 |
sendip_表1
上面是一个简单的图示,具体各个字段可以参见高文宇的《IP协议》文档。
Arguments for module ./ipv4.so:
-is x Source IP address (see README)
构造源IP地址,任意构造。判断不是很严格,可以输入错误,它会自动补充或用255.255.255.255来代替,例如输入1.1.则用255.255.255.255来代替;如果输入1.1则会自动补齐中间的两位,显示为1.0.0.1;如果输入1.1.1则会自动填充为1.1.0.1。目的IP地址也是一样。源IP默认为127.0.0.1。
-id x Desitnation IP address
构造目的IP地址,这里其实可以不用写,因为hostname为一个必带的参数字段,最后的目的地址是匹配hostname参数后的内容。注意,这里的ip和hostname的内容要求一样,不然sendip发出了报文(它发出的报文目的地址是-id参数后的地址),但是id和hostname所带参数内容所指的主机的网卡并收不到报文。默认以-id后的参数为准。
-ih x IP header length (see README)
构造header(首部)长度,这里所指的是32-bit的word的个数,就是以4个字节为单位元,你定义的结果要乘以4,要求最小为5(5x4=20bytes),最大为15(15x4=60bytes)。如果构造首部不是20bytes的报文,网卡收到的时候ethereal只解析到mac地址,它不认为是一个完整的IP报文。默认是20字节。
-iv x IP version (you almost definately don’t want to change this)
构造IP版本,这个可以任意,0到15任意选择,超过15发出的报文IP version为0。默认为4。
-iy x IP type of service
构造服务类型,这里先说明一下Type of service(PreDTRCx)的各个标志位的意义:
Precedence(000-111)
D(1 = minimize delay)
T(1 = maximize throughout)
R(1 = maximize reliability)
C(1 = minimize cost)
x(reservd and set to 0)
服务类型的前3位设置分组的优先级,数值越大,则分组越重要。接下来的3位分别表示延迟、吞吐率和可靠性,如果为0则表示常规服务,如果为1则表示短延迟、高吞吐率和高可靠性。最后两位没有使用。sendip并不能细致到定义每一个bit位的数值,默认是全0。
-il x Total IP packet length (see README)
构造IP报文的总长度,这里的总长度是包括数据字段的,就是说如果没有数据字段,这里的值应该和首部长度字段的值是一样的。最大值为65535(二进制16个1),但是注意配置这个值的意义不大,用ethereal抓到这个报文时显示的值还是一个正确的值,这个报文本来有多大就是多大。默认是根据报文内容来确定,是一个正确的值。
-ii x IP packet ID (see README)
构造IP报文的标识号,这个时随机的,没有太多意义,不知道是不是会在校验的时候用到。默认是随机构造。在IP协议中的作用是标识报文,例如一个分片后的IP报文,它的ID肯定是一样的。取值的范围是1-65535,不在这个范围的话,程序会随机构造一个id号。
-ifr x IP reservced flag (see README)
保留的标志位。默认为0,选项为0、1、r。
-ifd x IP don’t fragment flag (see README)
构造强制不分片标志位,默认为0,选项为0、1、r。
-ifm x IP more fragments flag (see README)
构造分片标志位,这个标志位只有在报文需要分片时才置为1,默认为0,选项为0、1、r。
-if x IP fragment offset
构造需要分片的报文的位偏移,默认发送的报文没有分片的话就是0。
-it x IP time to live
生存时间值,修改的意义不大,默认配置为255。
-ip x IP protocol
协议字段,判断哪个协议使用了IP封装来传输,例如ICMP为0x01,TCP为0x06,UDP为0x11。
-ic x IP checksum (see README)
构造首部校验和字段,只覆盖首部,不包括数据字段。默认是正确的校验和,看不出什么规律来,应该是有一种算法。测试时可以随意构造一个错误的checksum,看DUT是不是检测checksum这个字段。但是验证的过程中发现配置后没有作用,并没有修改。测试了icmp的包倒是修改了,不知道是不是软件的bug。
注意:以下内容都是选项字段,之前没有了解过的话,可能会觉得有些陌生,为了更好的了解这些字段的意思,可以先用ping这个简单的命令来进行一些实践操作,理解其中的一些字段的含义。
实例:
1、返回一个时戳,表示到达目的地址的时间,在reply的回应包中可以看到。
C:/>ping -s 1 192.168.100.18
Pinging 192.168.100.18 with 32 bytes of data:
Reply from 192.168.100.18: bytes=32 time<1ms TTL=63
Timestamp: 192.168.100.18 : 9612956
2、记录路由,表示在ping包到达目的地址时经过的路由设备,需要设备的支持。
C:/>ping -r 1 192.168.100.18 -n 1
Pinging 192.168.100.18 with 32 bytes of data:
Reply from 192.168.100.18: bytes=32 time<1ms TTL=63
Route: 192.168.100.18
Ping statistics for 192.168.100.18:
-r表示记录路由的跳数,注意这个参数值只能是1-9,原因是因为记录路由这个选项需要3个字节的开销,后面每跟一个ip地址需要4个字节,而选项字段的最大值为40个字节。这个参数的作用被之后的tracert命令代替。
-n 表示发多少个ping包。
3、严格源路由和记录路由,表示不但要记录路由,还要求到达目的地址必须要经过指定的路由地址。
C:/>ping -k 192.168.96.2 192.168.98.1 192.168.100.18
表示到达192.168.100.18时,先必须经过192.168.96.2和192.168.98.1这两个路由设备,再到达目的ip。
现在逐一来看各个参数的含义:
-ionum x
根据《TCP/IP详解》卷2第9章,并没有这个选项字段,这里应该是软件自己为了控制选项字段的长度而设计的。这里先说明一些选项字段的基础知识:
l 选项字段必须是4字节的整数倍,原因很简单,因为IP header length是以4字节为一个单位元;
l IP选项字段可能包含0个或多个单独选项,选项有两种类型,单字节和多字节,如下图:
?/P>
sendip_图1
所有选项1都以字节类型(type)字段开始。在多字段选项中,类型字段后面紧接着一个长度(len)字段,其它的字节是数据(data)。许多选项数据字段的第一个字节是1字节的位移(offset)字段,指向数据字段内的某个字节。长度(len)字节的计算覆盖了类型、长度、和数据字段。类型(type)被继续分成了3个子字段:1bit备份(copied)、2bit类(class)字段和5bit数字(number)字段。
现在来看各个选贤字段的定义和使用方法。默认这个参数后的长度值总是正确的,默认没有选项字段。
-ioeol IP option: end of list
1字节,表示选项表的结尾,就是一个选项字段的结束标识符。
-ionop IP option: no-op
1字节,没有任何意义,表示无操作,碰到这个字段可以忽略。
-iorr x
长度可变,作用是记录路由。格式是pointer:addr1:addr2,这里的pointer其实就是一个具体的offset值,是一个十六进制的值,我测试最小值是十进制的10(这里只能用十进制表示,10刚好对应的是0x16)。使用可以参照这个命令行:
[root@FC5 ~]# sendip -v -p ipv4 -is 192.168.96.111 -id 192.168.96.202 -iorr 10:192.168.96.1 192.168.96.202 -d asf
这里要说明一点为什么要加一个asf的3个字节的数据字段,因为在测试的时候,我在被发送报文的pc端抓包发现如果ethereal分析显示total length这个值不对的话,它就不会分析其它字段,这样会导致不能清楚看到option字段,不知道是不是正确构造了报文,所以在构造报文时还需要注意这个小窍门,ethereal分析后还少几个字节就加几个字符。这样你就可以看到你刚才发送的option字段里的内容了。
-iots x
长度可变,作用是时戳。格式是pointer:overflow:flag:(ip1:)ts1:(ip2:)ts2,pointer表示位偏移,overflow表示??,flag是标识位(0表示记录时间戳、1表示记录地址和时间戳、3表示只在预先指定的系统记录时间戳,其它保留)。注意只有flag位置1时才有ip1、ip2的参数。
[root@FC5 ~]# sendip -v -p ipv4 -is 192.168.96.222 -iots 11:4:1:192.168.96.1:11:
192.168.9.8:90 192.168.96.202
11是位偏移,4表示??,1表示记录地址和时间戳,后面就是一个ip对应一个时间戳。
构造这样的报文主要可以用于测试DUT是不是检查option字段,如果检查,是不是检查时间戳等信息。
-iolsr x
长度可变,表示宽松源路由和记录路由(LSRR)。格式很简单了,上面ping的实例如果运行后抓包看过以后,就发现这个是按照标准的格式来定义的。
[root@FC5 ~]# sendip -v -p ipv4 -is 192.168.96.253 -iolsr 10:192.168.96.1 192.168.96.202 -d 1
-iosid x
4个字节,构造identifier(标识符),取值范围0-65535,超过该范围自动变为0。我测试的结果显示前2个字节总是0x8804,后面2个字节就是输入的参数值。
-iossr x
长度可变,表示严格源路由和记录路由(SSRR),格式同-iolsr。
TCP报文的构造
先看man文件中显示支持可以构造的TCP报文字段有哪些,然后在参数后直接说明该字段的含义。构造报文首先要求对报文的各个字段非常熟悉,所以先看一下TCP首部的图表:
TCP首部:
0 15 16 31
16位源端口号 | 16位目的端口号 | |||||||
32位序列号 | ||||||||
32位确认号 | ||||||||
4位 首部 长度 | 6位 保留 | U R G | ACK | P S H | RST | S Y N | FIN | 16位窗口大小 |
16位TCP检验和 | 16位紧急指针 | |||||||
选项(若有) | ||||||||
数据(若有) | ||||||||
sendip_表2
Arguments for module ./tcp.so:
-ts x TCP source port
构造TCP的源端口,默认为0。
-td x TCP destination port
构造TCP的目的端口,默认为0。
-tn x TCP sequence number
构造TCP的序列号,默认为随机。
-ta x TCP ack number
构造TCP的应答号,默认为0。
-tt x TCP data offset
构造TCP的首部长度,默认是正确值,标准是20字节,最大60字节。
-tr x TCP header reserved field EXCLUDING ECN and CWR bits
TCP头部保留位。
-tfe x TCP ECN(Explicit Congestion Notification)bit (rfc2481)
TCP标志位中保留的ECN字段,默认为0,选项为0、1或r。
-tfc x TCP CWR(Congestion Window Reduced)bit (rfc2481)
TCP标志位中保留的CWR字段,默认为0,选项为0、1或r。
注意:ECN和CWR
-tfu x TCP URG bit
构造TCP标志位中的URG,表示紧急指针有效。当紧急指针(Urgent Pointer)字段有内容时有效。默认为0,选项为0、1或r。
-tfa x TCP ACK bit
构造TCP标志位中的ACK,表示确认ack number有效。默认为0,选项为0、1或r。
-tfp x TCP PSH bit
构造TCP标志位中的PSH,表示接收方应该尽快将这个报文段交给应用层。默认为0,选项为0、1或r。
-tfr x TCP RST bit
构造TCP标志位中的RST,表示需要重建连接,断开目前的连接。默认为0,选项为0、1或r。
-tfs x TCP SYN bit
构造TCP标志位中的SYN,表示使用同步序号用来发起一个连接。默认为1,选项为0、1或r。
-tff x TCP FIN bit
构造TCP标志位中的FIN,表示完成了发送任务。默认为0,选项为0、1或r。
-tw x TCP window size
构造TCP的期望接收的窗口大小值,默认为65535字节。
-tc x TCP checksum
构造TCP的checksum,检验和覆盖了整个TCP的报文段。默认为正确值。可以用于测试DUT设备是不是检验TCP报文的checksum值。
-tu x TCP urgent pointer
构造紧急指针,所谓紧急指针是一个正的位偏移量,和序号字段中的值相加可以得出紧急数据最后一个字节的序号。默认配置为0。注意这个值只有在-tfu置1时才有效,默认情况下如果有-tu参数,-tfu会自动为1,但是强制把-tfu置为1的话会导致-tu的参数无效。
-tonum x
TCP option as string of hex bytes (length is always correct)
选项字段的长度,但是标准字段没有这个选项。默认是没有选项字段的内容。
-toeol TCP option: end of list
选项字段结束的标识符。
-tonop TCP option: no op
没有任何意义,表示无操作,碰到这个字段可以忽略。
-tomss x
表示最长报文大小,每个连接方通常都在通信的第一个报文段中指明这个选项。
-towscale x
TCP option: window scale (rfc1323)
-tosackok
TCP option: allow selective ack (rfc2018)
-tosack x
TCP option: selective ack (rfc2018), format is l_edge1:r_edge1,l_edge2:r_edge2...
-tots x
TCP option: timestamp (rfc1323), format is tsval:tsecr
ICMP报文的构造
先看man文件中显示支持可以构造的ICMP报文字段有哪些,然后在参数后直接说明该字段的含义。构造报文首先要求对报文的各个字段非常熟悉,所以先看一下UDP首部的图表:
ICMP结构:
0 15 16 31
8位类型 | 8位代码 | 16位检验和 |
其它一些细节信息内容 |
sendip_表4
Arguments for module ./icmp.so:
-ct x ICMP message type
构造ICMP报文类型,一个字节。内容比较多,最常见的类型如下:
0 表示Echo Reply
3 表示Eestination Unreachable
这个又分很多类型,这里不作分析,具体可以参见资料。
4 Source Quench
5 Redirect
8 Echo
10 Router Selection
11 Time Exceeded
13 Timestamp
-cd x ICMP code
构造代码字段,取值范围0-255,超过255后变为0,默认配置为0。
-cc x ICMP checksum
构造checksum字段,2个字节。默认是正确内容。
[root@FC5 ~]# sendip -v -p ipv4 -is 192.168.96.253 192.168.96.202 -p icmp -ct 3
发送一个源地址为192.168.96.253的类行为3的icmp报文给192.168.96.202这个地址。
实例:
[root@FC5 ~]# sendip -v -p ipv4 -is 192.168.96.7 -id 192.168.96.1 -p udp -us 8000 -ud 4000 192.168.96.1 –d asdfasdf
小结:
这篇文档主要是介绍sendip的报文构造方式,而sendip的缺陷就在于它不能连续的发送报文,所以这里提供一个简单的perl脚本,来循环的发送数据包,虽说不能形成flood的效果,但是对于测试可以达到数量控制的目的,有助于自动化脚本的测试,提高测试的效率。
#!/usr/bin/perl -w
$i=1;
while ($i<2)
{
for($j=0;$j<=65;++$j)
{
system "sendip -v -d r64 -p ipv4 -iv 4 -ih 5 -il 20 -ifm 1 -if 0 -iy 8 -is 13.1.1.2 -id 192.168.98.173 -p udp -us $j -ud $j -ul 56 192.168.98.173";
system "sendip -v -d r64 -p ipv4 -iv 4 -ih 5 -il 20 -ifm 0 -if 0x3 -iy 8 -is 13.1.1.2 -id 192.168.98.173 -p udp -us $j -ud $j -ul 56 192.168.98.173";
system "sendip -v -d r64 -p ipv4 -iv 4 -ih 5 -il 65535 -ifm 1 -if 10 -iy 4 -is 13.1.1.2 -id 192.168.98.173 -p tcp -ts $j -td $j -tt 8 192.168.98.173";
}
++$i
}
//i代表循环的次数,j代表端口,本来是65535,我修改了一下,可以根据需要来调整次数和//端口范围