快乐虾
http://blog.csdn.net/lights_joy/
欢迎转载,但请保留作者信息
本节学习一下使用NS3构造一个TCP包,再利用构造好的TCP包进行最简单的TCP端口扫描。
下图中给出了TCP协议数据报头的格式。
源端口、目的端口:16位长。标识出远端和本地的端口号。
顺序号:32位长。表明了发送的数据报的顺序。
确认号:32位长。希望收到的下一个数据报的序列号。
TCP协议数据报头DE 头长:4位长。表明TCP头中包含多少个32位字。
接下来的6位未用。
ACK:ACK位置1表明确认号是合法的。如果ACK为0,那么数据报不包含确认信息,确认字段被省略。
PSH:表示是带有PUSH标志的数据。接收方因此请求数据报一到便可送往应用程序而不必等到缓冲区装满时才传送。
RST:用于复位由于主机崩溃或其它原因而出现的错误的连接。还可以用于拒绝非法的数据报或拒绝连接请求。
SYN:用于建立连接。
FIN:用于释放连接。
窗口大小:16位长。窗口大小字段表示在确认了字节之后还可以发送多少个字节。
校验和:16位长。是为了确保高可靠性而设置的。它校验头部、数据和伪TCP头部之和。
可选项:0个或多个32位字。包括最大TCP载荷,窗口比例、选择重发数据报等选项。
最大TCP载荷:允许每台主机设定其能够接受的最大的TCP载荷能力。在建立连接期间,双方均声明其最大载荷能力,并选取其中较小的作为标准。如果一台主机未使用该选项,那么其载荷能力缺省设置为536字节。
窗口比例:允许发送方和接收方商定一个合适的窗口比例因子。这一因子使滑动窗口最大能够达到232字节。
TCP协议数据报头选择重发数据报:这个选项允许接收方请求发送指定的一个或多个数据报。
当一个客户端向一个服务器发出连接请求时,它将首先发送一个SYN报文:
这是一个从192.168.24.1到192.168.24.129的FTP连接请求。对照上一节的包结构不难理解。下面我们用NS3构造一个这样的包。
首先我们构造一个TCP包头:
ns3::Ptr pkt = ns3::Create();
ns3::TcpHeader tcphdr;
tcphdr.SetSourcePort(rand()%32768);
tcphdr.SetDestinationPort(i); // 任意填写
tcphdr.EnableChecksums();
tcphdr.SetFlags(ns3::TcpHeader::SYN);
tcphdr.SetWindowSize(8192);
tcphdr.SetSequenceNumber(ns3::SequenceNumber32(rand()));
tcphdr.AppendOption(mss);
tcphdr.AppendOption(ns3::TcpOption::CreateOption(ns3::TcpOption::NOP));
tcphdr.AppendOption(ws);
tcphdr.AppendOption(ns3::TcpOption::CreateOption(ns3::TcpOption::NOP));
tcphdr.AppendOption(ns3::TcpOption::CreateOption(ns3::TcpOption::NOP));
这里的源端口随机选择了一个数,目标端口i是一个循环递增的数,也就是我们要扫描的目标端口,需要注意的是,在NS3中并不支持sack permit这一个选项,因此我们直接忽略了它。
接下来构造IP数据包的包头:
// 添加IP头
ns3::Ipv4Header iph;
iph.SetDestination((const char*)dest_ip);
iph.SetSource((const char*)src_ip);
iph.SetIdentification(rand() % 65536);
iph.SetTtl(64);
iph.SetDontFragment();
iph.SetProtocol(ns3::TcpL4Protocol::PROT_NUMBER);
iph.EnableChecksum();
再接下来就是将TCP头加到空的数据包的前面:
tcphdr.InitializeChecksum(iph.GetSource(), iph.GetDestination(), TcpL4Protocol::PROT_NUMBER);
pkt->AddHeader(tcphdr);
这里有一点需要注意的是tcp头中的checksum计算需要包含源IP、目标IP和协议号。
再将ip报文的头加上:
iph.SetPayloadSize(pkt->GetSize());
pkt->AddHeader(iph);
最后加上以太网的帧头:
// 添加以太网头
ns3::EthernetHeader eeh;
eeh.SetDestination((const char*)dest_mac);
eeh.SetSource((const char*)src_mac);
eeh.SetLengthType(ns3::Ipv4L3Protocol::PROT_NUMBER);
pkt->AddHeader(eeh);
大功告成!
下面就是我们构造出来的SYN数据包:
对于每一个要扫描的端口,只需要构造一个SYN包,再将这些生成的包放到winpcap的发送队列里去,一次性送出。
然后就可以坐等服务端的响应了。
嗯,这个效率可比开一堆线程进行connect快多了~~~
当一个端口允许连接时,服务端将响应SYN/ACK,就像这样的:
而当服务端拒绝连接时,它将回复RST包:
我们所需要做的就是判断一下回包的类型,确认此端口是否可以连接就OK了:
bool CNetHostPortScan::ProcessTcpPacket(ns3::EthernetHeader& eh, ns3::Ipv4Header& iph, ns3::TcpHeader& th, ns3::Ptr& pkt)
{
// 处理tcp包,返回true表示已经处理
if ((th.GetFlags() & TcpHeader::SYN) && (th.GetFlags() & TcpHeader::ACK))
{
// 此端口可以响应tcp
......
}
return true;
}
再加上一点UI的显示,我们就得到了一个自己的端口扫描工具:
1、NS3对数据包的处理效率不高,因此导致了我们构造32768个包所用的时间比网络发包并等待回应的时间还长。
2、自己构造TCP包的扫描方式比用上层的connect方式速度要快得多,基本上2秒钟就把所有端口都扫出来了~~~
嗯,仿真用的NS3居然还有此妙用,哈哈......
努力学习中~~~~~~