一 程序环境
1.操作系统: WindowsXP + SP2 / Windows 2003 2.开发语言: C#
3.开发工具:visual studio .net 2005
4.运行支持:release文件是一个用C#做的Form程序,如图.运行要.netframework2.0支持. 5.注: 因为xp+sp2中微软禁止了用rawsocket发送tcp包,所以TCP SYN/FIN扫描是在windows 2003下调试.
6.功能:实现各个扫描功能。可以多线程扫描提高扫描速度,用户可以对IP段下的所有端口和指定一些端口扫描(即可纵向扫描,横向扫描,也可纵向横向都扫描),对TCP和UDP的扫描长时间没回应进行了处理,做了图形界面,DataGrid可以排序,对用户输入做了很多安全限制和判断。
图1.1 程序演示图
二 相关知识介绍
1.ARP协议
因为既有网络层地址(例如,因特网IP地址),又有链路层地址(即局域网地址),因此有必要在两者之间进行转化。对于因特网,这就是地址解析协议(Address Resolution Protocol,ARP)。每个因特网主机和局域网上的路由器都有一个ARP模块。
因此,一台主机发送一个ARP查询包查询另一个局域网内的主机的MAC地址,若该主机存活的话,就会返回它的MAC地址,若不存活,就不会返回。所以就给局域网内的所以机器发送ARP查询包,根据返回的ARP包确定哪些局域网的机器是存活的。
2.ICMP扫描
ICMP因特网控制消息协议,主机、路由器和网关使用它来进行网络层信息的交互。ICMP信息有一个类型字段和一个代码字段。众所周知的ping程序给指定的主机发送ICMP类型8编码0的信息。目的主机,看到回应请求后,返回一个类型0编码0的ICMP回应应答,表示该主机存活。
3.TCP扫描
IP地址和端口被称作套接字,它代表一个TCP连接的一个连接端。为了获得TCP服务,必须在发送机的一个端口上 和接收机的一个端口上建立连接。TCP连接用两个连接端来区别,也就是(连接端1,连接端2)。连接端互相发送数 据包。 一个TCP数据包包括一个TCP头,后面是选项和数据。一个TCP头包含6个标志位。它们的意义分别为:
SYN: 标志位用来建立连接,让连接双方同步序列号。如果SYN=1而ACK=0,
则表示该数据包为连接请求,如果SYN=1而 ACK=1则表示接受连接。
FIN: 表示发送端已经没有数据要求传输了,希望释放连接。
RST: 用来复位一个连接。RST标志置位的数据包称为复位包。一般情况下,如果
TCP收到的一个分段明显不是属于该主
机上的任何一个连接,则向远端发送一个复位包。
URG: 为紧急数据标志。如果它为1,表示本数据包中包含紧急数据。此时紧急数
据指针有效。
ACK: 为确认标志位。如果为1,表示包中的确认号时有效的。否则,包中的确认
号无效。
PSH: 如果置位,接收端应尽快把数据传送给应用层。 TCP连接的建立
TCP是一个面向连接的可靠传输协议。面向连接表示两个应用端在利用TCP传送数据前必须先建立TCP连接。 TCP的可靠性通过校验和,定时器,数据序号和应答来提供。通过给每个发送的字节分配一个序号,接收端接收到数据后发送应答,TCP协议保证了数据
的可靠传输。数据序号用来保证数据的顺序,剔除重复的数据。在一个TCP会话中,有两个数据流(每个连接端从另外一端接收数据,同时向对方发送数据),因此在建立连接时,必须要为每一个数据流分配ISN(初始序号)。为了了解实现过程,我们假设客户端C希望跟服务器端S建立连接,然后分析连接建立的过程(通常称作三阶段握手)。
一些实现细节
大部分TCP/IP实现遵循以下原则:
1:当一个SYN或者FIN数据包到达一个关闭的端口,TCP丢弃数据包同时发送一个RST数据包。
2:当一个RST数据包到达一个监听端口,RST被丢弃。 3:当一个RST数据包到达一个关闭的端口,RST被丢弃。 4:当一个包含ACK的数据包到达一个监听端口时,数据包被丢弃,同时发送一个RST数据包。
5:当一个SYN位关闭的数据包到达一个监听端口时,数据包被丢弃。
6:当一个SYN数据包到达一个监听端口时,正常的三阶段握手继续,回答一个SYN|ACK数据包。
7:当一个FIN数据包到达一个监听端口时,数据包被丢弃。\"FIN行为\"(关闭得端口返回RST,监听端口丢弃包),在
URG和PSH标志位置位时同样要发生。所有的URG,PSH和FIN,或者没有任何标记的TCP数据包都会引起\"FIN行为\"。
4. 全TCP连接和SYN扫描器
1) 全TCP连接
全TCP连接是长期以来TCP端口扫描的基础。扫描主机尝试(使用三次握手)与目的机指定端口建立建立正规的连接。
连接由系统调用connect()开始。对于每一个监听端口,connect()会获得成功,否则返回-1,表示端口不可访问。由于通常情况下,这不需要什么特权,所以几乎所有的用户(包括多用户环境下)都可以通过connect来实现这个技术。
这种扫描方法很容易检测出来(在日志文件中会有大量密集的连接和错误记录)。Courtney,Gabriel和TCP Wrapper 监测程序通常用来进行监测。另外,TCP Wrapper可以对连接请求进行控制,所以它可以用来阻止来自不明主机的全连接扫描。
2) TCP SYN扫描
在这种技术中,扫描主机向目标主机的选择端口发送SYN数据段。如果应答是RST,那么说明端口是关闭的,按照设定就探听其它端口;如果应答中包含SYN和ACK,说明目标端口处于监听状态。由于所有的扫描主机都需要知道这个信息,传送一个RST给目标机从而停止建立连接。由于在SYN扫描时,全连接尚未建立,所以这种技术通常被称为半打开扫描。
SYN扫描的优点在于即使日志中对扫描有所记录,但是尝试进行连接的记录也要比全扫描少得多。缺点是在大部分操作系统下,发送主机需要构造适用于这种扫描的IP包,通常情况下,构造SYN数据包需要超级用户或者授权用户访问专门的系统调用。
3) 秘密(FIN)扫描 秘密扫描技术
由于这种技术不包含标准的TCP三次握手协议的任何部分,所以无法被记录下来,从而必SYN扫描隐蔽得多。另外,FIN数据包能够通过只监测SYN包的包过滤器。
秘密扫描技术使用FIN数据包来探听端口。当一个FIN数据包到达一个关闭的端口,数据包会被丢掉,并且回返回一个RST数据包。否则,当一个FIN数据包到达一个打开的端口,数据包只是简单的丢掉(不返回RST)。
Xmas和Null扫描是秘密扫描的两个变种。Xmas扫描打开FIN,URG和PUSH标记,而Null扫描关闭所有标记。这些组合的目的是为了通过所谓的FIN标记监测器的过滤。
秘密扫描通常适用于UNIX目标主机,除过少量的应当丢弃数据包却发送reset信号的操作系统(包括CISCO,BSDIHP/UX,MVS和IRIX)。在Windows95/NT环境下,该方法无效,因为不论目标端口是否打开,操作系统都发送RST。
跟SYN扫描类似,秘密扫描也需要自己构造IP 包。
5. UDP扫描
UDP扫描发送空的(没有数据)UDP报头到每个目标端口。如果返回ICMP端口不可到达错误(类型3,代码3),该端口是closed(关闭的)。 其它ICMP不可到达错误(类型3,代码1,2,9,10,或者13)表明该端口是filtered(被过滤的)。偶尔地,某服务会响应一个UDP报文,证明该端口是open(开放的)。如果几次重试后还没有响应,该端口就被认为是 open|filtered(开放|被过滤的)。这意味着该端口可能是开放的,也可能包过滤器正在封锁通信。可以用版本扫描(-sV)帮助区分真正的开放端口和被过滤的端口。
UDP扫描的巨大挑战是怎样使它更快速。开放的和被过滤的端口很少响应,让Nmap超时然后再探测,以防探测帧或者响应丢失。关闭的端口常常是更大的问题。它们一般发回一个ICMP端口无法到达错误。但是不像关闭的TCP端口响应SYN或者Connect 扫描所发送的RST报文,许多主机在默认情况下限制ICMP端口不可到达消息。 Linux和Solaris对此特别严格。例如, Linux 2.4.20内核限制一秒钟只发送一条目标不可到达消息 (见net/ipv4/icmp。c)。
三 程序设计思路
1.基于ARP的活动主机扫描
先获得本机IP和子网掩码,从而算法子网内的所有主机,对这些主机发送ARP请求包,有返回的ARP包,则是子网内存活的主机。
计算子网和开启发送线程函数是ARPScan(),发送和接收线程函数是GetMACFromIP(object A_strIP)。
2.基于ICMP的存活主机扫描
首先开个IP包拦截处理线程,处理本机接收到的一切ICMP包。对用户填入的IP段中的所有IP发送type为8、subcode为0的ICMP包。拦截处理线程收到返回的type为0、subcode为0的ICMP包则说明该主机存活,在用户界面中显示其IP。
拦截处理线程函数是receivICMP(),发送线程函数是ping(object host1)
3.基于TCP全连接的开放端口扫描
对用户输入的填入的每个IP的每个端口用TcpClient对象的Connect函数进行连接,若连接成功,则说明该端口开放。
对指定的IP和端口连接线程函数是TCPConnectScan(object ipPort)
4.基于TCP SYN/FIN的开放端口扫描
1) 开启一个IP包拦截处理线程,处理本机接收到的一切IP包。
2)用rawsocket自己构建IP包对用户输入的填入的每个IP的每个端口发送TCP SYN/FIN包,并把发送的IP和Port存到hashtable中,当发送完最后一个扫描端口后,开启TCPTimeOut线程,对没回应的IPPort对进行处理。
3)拦截线程处理每个拦截到的包。
若是TCP的ACKSYN包,且该TCP包的IP和Port在hashtable中,则说明
该IP的Port开放,在用户界面上显示,并删除hashtable中该项。 若是TCP的ACKRST包,且该TCP包的IP和Port在hashtable中,若是SYN
扫描,则说明IP的Port关闭。若是FIN扫描,则说明IP的Port开放。在用户界面上显示,并删除hashtable中该项。
若是ICMP不可到达错误(类型3,代码1,2,9,10,或者13),且包的IP
和Port在hashtable中,则说明该IP的Port被过滤。在用户界面上显示,并删除hashtable中该项。
若是type为0,subcode为0的ICMP包,且该IP在ICMPHashTable中,则
此IP存活,但没回应TCP的SYN/FIN包,说明此IP的Port被过滤。
4)TCPTimeOut线程思路:等待一段时间后,hashtable中还存在的项就是长时间没回应的IPPort对,把这些IP及对应的Port List存到ICMPHashTable中。对这些IP发送type为8、subcode为0的ICMP包,看这些主机是否存活。若存活则说明探测的该IP包下的一些端口被过滤。
拦截处理线程函数是receiveACK(),发送TCP SYN/FIN包线程是SYNConnect(object ipPort),对长时间没回应的IPPort对处理线程函数是TCPTimeOut()。
5.UDP开放端口扫描
1) 开启一个IP包拦截处理线程,处理本机接收到的一切IP包。
2)用rawsocket自己构建IP包对用户输入的填入的每个IP的每个端口发送两次UDP包,并把发送的IP和Port存到hashtable中,当发送完最后一个扫描端口后,开启UDPTimeOut线程,对没回应的IPPort对进行处理。
3)拦截线程处理每个拦截到的包。
若是UDP包,且该UDP包的IP和Port在hashtable中,则说明该IP的Port
开放,在用户界面上显示,并删除hashtable中该项。
若是返回ICMP端口不可到达错误(类型3,代码3),且该包的IP和Port在
hashtable中,则说明IP的Port关闭。在用户界面上显示,并删除hashtable中该项。
若是ICMP不可到达错误(类型3,代码1,2,9,10,或者13),且该包的IP
和Port在hashtable中,则说明该IP的Port被过滤。在用户界面上显示,并删除hashtable中该项。
若是type为0,subcode为0的ICMP包,且该IP在ICMPHashTable中,则
此IP存活,但没回应UDP包,说明此IP的Port开放或被过滤。
4)UDPTimeOut线程思路:等待一段时间后,hashtable中还存在的项就是长时间没回应的IPPort对,把这些IP及对应的Port List存到ICMPHashTable中。对这些IP发送type为8、subcode为0的ICMP包,看这些主机是否存活。若存活则说明探测的该IP包下的一些端口被过滤。
拦截处理线程函数是receiveUDP(),发送UDP包线程是UDPScan(object ipPort),对长时间没回应的IPPort对处理线程函数是UDPTimeOut()。
6.用户输入的安全性考虑和限制
程序需要的最小用户输入都不能为空,IP输入框只能输入’.’和数字,并且要符合IP规格,不能输入大于255的数。Port输入框只能输入’,’和数字,并且只能输入小于65535的端口。线程数输入框只能输入数字。同时为了用户输入方便,输入开始IP时,结束IP会同步显示。
判断用户输入是否合法的函数是inputCheck(),各个输入的限制输入等是各个输入的Press事件函数,如IPStart_TextChanged(object sender, EventArgs e)。
7.C#中网络编程及多线程的一些问题
1)C#中用指针:考虑到要属于网络底层编程,不要指针的话代码就很难写。C#中也可以中指针,只需要一些设置。
首先要在项目属性中设置运行不安全代码。
其次在类前面使用unsafe关键字。
最后用fixed关键字禁止垃圾回收器重定位可移动的变量,如:
byte[] buf = new byte[sizeof(IcmpPacket)]; fixed (byte* fixedbuf = buf) { }
„„//操作指针代码
就可以像C++一样用指针操作了。
2).net 2.0中线程中对界面控件的访问都该用delegate,以防止线程死锁等不安全现象发生。
声明delegate:
delegate void SetDataGridDelegate(object data); private void SetDataGrid(object data) //控件操作 {
DataInfo dataInfo = (DataInfo)data;
this.dataGrid.Rows.Add(this.dataGrid.Rows.Count, dataInfo.ip, dataInfo.port,
dataInfo.stat);
}
线程中访问界面控件:
this.Invoke(new SetDataGridDelegate(SetDataGrid), dataInfo);
为什么用C#:本人用C++和MFC已经用了差不多3年,基本已经算精通。所以用C#更多是一种尝试,同时C#中的简单的图形界面程序编程,比MFC简单多的多线程编程,网络高层编程提供了很多函数,比如TCP连接可以直接用TCPClient的Connect函数,不像原来C++那样还要重复写那么多代码。而且和JAVA不同的是,它还支持指针,同样可以做网络底层编程。还算不错,呵呵。
四 程序流程图
1.总流程图
用户输入,点击“开始扫描” 否 用户输入是否合法 是 初始化一些执行扫描所需变量,关闭以前的拦截包处理线程 按用户要求,开启拦截包处理线程,对每个IP的每个Port开启线程发送数据包。
2.TCP SYN/FIN扫描流程图
因为TCP SYN/SYN扫描流程图最复杂,UDP扫描流程与之类似,其他的扫描是简化的TCP扫描流程。因此在此只画出TCP SYN/FIN扫描流程图
主线程 开启拦截包处理本机所有IP线程 对每个IP的每个扫描端口开启一个线程发送TCP 包 SYNACK,端口开放 RSTACK,SYN端口关闭,F IN开放 3,3 I CMP,端口过滤 0,0 I CMP,端口过滤 是否是最后一个要发送的包 是 都是发送过的IP和Port 开启TimeOut线程 对所有没回应的IP,发送ICMP包。
五.主要数据结构和函数介绍
1. 数据结构
//ICMP包结构
public struct IcmpPacket {
public byte Type; // type of message public byte SubCode; // type of sub code
public ushort CheckSum; // ones complement checksum of struct public ushort Identifier; // identifier
public ushort SequenceNumber; // sequence number
} //IP包结构
public struct IP_HEADER {
public byte VerLen; public byte ServiceType; public ushort TotalLen; public ushort ID; public ushort offset; public byte TimeToLive; public byte Protocol; public ushort HdrChksum; public uint SrcAddr; public uint DstAddr; //public byte Options; };
//图形界面里结果显示表的数据结构 public struct DataInfo {
public string ip; public string port; public string stat; }
//包含IP和端口的数据结构,给一些需要IP和Port的函数传值 public struct IPPort
{
public string ip; public int port; }
//为了计算TCP,UDP checksum定义的伪首部 public struct FAKE_HEADER //定义TCP,UDP伪首部 {
public uint src; //源地址 public uint dst; //目的地址 public byte mbz;
public byte ptcl; //协议类型 public ushort len; //长度 };
//TCP包结构
public struct TCP_HEADER {
public ushort srcPort; public ushort dstPort; public uint seq; public uint ack; public byte headLen; public byte flag; public ushort windows; public ushort checkSum; public ushort urgency; public uint option; public uint option2; }
//UDP包结构
public struct UDP_HEADER {
public ushort srcPort; public ushort dstPort; public ushort headLen; public ushort checkSum;
}
2. 主要函数
1) 扫描处理函数
//发送ARP包获得存活主机
public void GetMACFromIP(object A_strIP) //基于ARP的局域网存活主机发现
public void ARPScan()
//发送ICMP包扫描活动主机
public static void ping(object host1) //对ICMP扫描返回的包进行处理的线程函数
public void receivICMP()
//基于Connect的TCP扫描函数
void TCPConnectScan(object ipPort)
//发送TCP SYN/FIN包扫描开放端口
public static void SYNConnect(object ipPort) //对发送的TCP SYN/FIN 包的返回数据处理的线程函数
public void receiveACK()
//对长时间没用回应的IP,探测是不是活动主机.
public static void TCPTimeOut()
//发送udp扫描包
public void UDPScan(object ipPort) //对长时间没用回应的IP,探测是不是活动主机.
public static void UDPTimeOut() //对发送的UDP 包的返回数据处理的线程函数
public void receiveUDP()
2)一些转换处理函数
//把byte*转换成byte[]
public static byte[] ConvertPoint(byte* p, int length)
//计算checksum函数
public static ushort checksum(ushort* buffer, int size)
3) 一些界面函数
//“开始扫描”按钮点击响应函数
private void start_Click(object sender, EventArgs e)
//判断用户的输入是不是合法
public bool inputCheck()
//用delegate在多线程中处理界面控件
delegate void SetDataGridDelegate(object data);
//使threadNum输入框只接收数字
private void threadNum_KeyPress(object sender, KeyPressEventArgs e)
//使ipend输入框和ipstart输入框同步
private void IPStart_TextChanged(object sender, EventArgs e)
//使ipstart输入框只接收数字和'.'
private void IPStart_KeyPress(object sender, KeyPressEventArgs e)
因篇幅问题不能全部显示,请点此查看更多更全内容