嗯!拖延症晚期,已经无可救药,熬了两个晚上的夜,基本也把计算机网络课程设计的网络扫描器的功能实现了。
其实,写个扫描器也挺好玩的,牵涉到了RAW Socket编程,可以尽情地DIY数据包(当然,不符合数据包规则,比如checksum错误就没办法了),收获颇深。其中,我觉得用C语言写更有利于在编写过程中对加深对计算机网络的理解,特别是数据包细节。但是由于效率问题,还有Python真是太好用了(自从用了python,日常再也不想去碰C/C++了,虽然python也写的挺烂的)。话不多说,言归正传。
学习信息安全的自然听说过nmap这种网络扫描神器,其中功能选项多,老少咸宜,不仅能满足网络管理员的日常,还能满足网络安全工程师的渗透测试,这个课程设计程度的扫描器自然不会有那么多功能,主要实现利用TCP和UDP的一些特性的进行IP段主机存活情况以及端口扫描。
基本功能如下:
1.发送udp包,检测一个极少使用的端口,对回传的ICMP包的进行分析,从而判断主机是否存活。
2.利用TCP三次握手,通过是否连接成功,来判定端口是否开放,其中采用了多线程加快了扫描速度。
3.通过RAW Socket的原生编程,对TCP标志位进行人工设置,对回复数据包的标志位进行分析,从而不需要TCP三次握手就可以对端口是否开放进行判定。部分方式如下
i:通过SYN置1,检测回传的数据包的标志位是否为SYN/ACK
ii:通过ACK置1,查看是否回传数据包,且数据包的标志位是否为RST
iii:通过将所有标志位都置0,查看是否回传数据包,且数据包的标志位是否为RST
iv:通过FIN+URG+PSH置1,查看是否回传数据包,且数据包的标志位是否为RST
在编写功能之前,有必要写对IP,ICMP,TCP的包头进行解析,直接看代码:
IP数据包头:
_fields_ = [ ("ihl", c_ubyte, 4), ("version", c_ubyte, 4), ("tos", c_ubyte), ("len", c_ushort), ("id", c_ushort), ("offset", c_ushort), ("ttl", c_ubyte), ("protocol_num", c_ubyte), ("sum", c_ushort), ("src", c_ulong), ("dst", c_ulong) ]
ICMP数据包头:
_fields_ = [ ("type", c_ubyte), ("code", c_ubyte), ("checksum", c_ushort), ("unused", c_ushort), ("next_hop_mtu", c_ushort) ]
TCP数据包头:
_fields_ = [ ("src_port", c_ushort), ("dst_port", c_ushort), ("seq", c_ulong), ("ack_seq", c_ulong), ("offset", c_ubyte), ("flag", c_ubyte), ("windows", c_ushort), ("checksum", c_ushort), ("point", c_ushort), ]
TCP数据包头的 offset,flag 并不是真正的数据包结构,但是由于单字节细节处不好处理,直接写成上文那样了,所有结构非一言两语可以说完的,详情可参考《IP/TCP详解》。
1.先从最简单的TCPconnect多线程端口扫描开始,基于部分防火墙的策略,应该对需要扫描的端口区间进行随机分配算法,来干扰防火墙的判断。当然,由于太懒了,就直接一路扫下去了。具体代码如下:
def portTest(ip,port,num): s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) i = 0 while i <num: try: myport = i + port s.connect(( "%s" %ip, myport )) s.close() print "%s:%d is open" % (ip,myport) except BaseException,e: pass i = i+1 def TcpConnect(subnet,port,num=1): Port = int(port) if num > 8: for ip in IPNetwork(subnet): for i in range(0,THREADNUM): t = threading.Thread(target=portTest, args=(ip,Port+i*num/THREADNUM,num/THREADNUM)) #开了8个线程, t.start() else: for ip in IPNetwork(subnet): #方便对区段进行扫描 portTest(ip,Port,num)
总共开了八个线程,将端口段分成8份进行扫描。通过异常来退出对位打开的端口的连接,但是实际使用中,容易被网络发现,这种方法只能说是最为简单,但是不推荐使用。
2.利用udp进行扫描。
这里需要涉及到RAW socket的编程,《python黑帽》里关于udp扫描的代码写的非常好(其它的代码也写的不错,在里面也学习了很多python的技巧)。基本原理就是通过setsocketopt函数来设置网卡的混杂模式。进行嗅探,直接贴里面的代码:
def udp_sender(subnet,magic_message): time.sleep(5) sender = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) for ip in IPNetwork(subnet): try: sender.sendto(magic_message, ("%s" % ip, 65211)) #对每个ip地址进行发包 except: pass def ICMPecho(subnet): if os.name == "nt": socket_protocol = socket.IPPROTO_IP else: socket_protocol = socket.IPPROTO_ICMP sniffer = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket_protocol) sniffer.bind((host, 0)) sniffer.setsockopt(socket.IPPROTO_IP, socket.IP_HDRINCL, 1) if os.name == "nt": #跨平台必备 sniffer.ioctl(socket.SIO_RCVALL, socket.RCVALL_ON) t = threading.Thread(target=udp_sender, args=(subnet,magic_message)) #线程用于发送数据包 t.start() try: while True: raw_buffer = sniffer.recvfrom(65565)[0] #对收到的数据包进行检测 ip_header = IP(raw_buffer[0:20]) if ip_header.protocol == "ICMP": offset = ip_header.ihl * 4 buf = raw_buffer[offset:offset+sizeof(ICMP)] icmp_header = ICMP(buf) if icmp_header.type == 3 and icmp_header.code == 3: if IPAddress(ip_header.src_address) in IPNetwork(subnet): if raw_buffer[len(raw_buffer) - len(magic_message):] == magic_message: print "Host Up: %s" % ip_header.src_address except KeyboardInterrupt: if os.name == "nt": sniffer.ioctl(socket.SIO_RCVALL, socket.RCVALL_OFF)
3:最好玩的当然是构造数据包,通过自己构造数据包可以做很多非常geek的事情,比如DNS欺骗,ARP欺骗,SYN洪泛等等。先来看看怎么构造标志位。
def createTcpFlag(fin=0,syn=0,rst=0,psh=0,ack=0,urg=0): tcp_flags = fin + (syn<<1) + (rst<<2) + (psh<<3) + (ack<<4) + (urg<<5) return tcp_flags
简单的移位操作就可以实现了对符号位的操作了。
再看看怎么创建TCP数据包头。
def create_tcp_header(source_ip, dest_ip, dest_port,tcp_flag): source = random.randrange(32000,62000,1) seq = 0 ack_seq = 0 doff = 5 window = socket.htons (8192) check = 0 #先将数据包的校验位置0 urg_ptr = 0 offset_res = (doff << 4) + 0 tcp_flags = tcp_flag tcp_header = struct.pack(‘!HHLLBBHHH‘, source, dest_port, seq, ack_seq, offset_res, tcp_flags, window, check, urg_ptr) #TCP头在进行校验和时,需要有一个伪IP头,基本细节如下 source_address = socket.inet_aton( source_ip ) dest_address = socket.inet_aton( dest_ip ) placeholder = 0 protocol = socket.IPPROTO_TCP tcp_length = len(tcp_header) psh = struct.pack(‘!4s4sBBH‘, source_address, dest_address, placeholder, protocol, tcp_length); psh = psh + tcp_header; tcp_checksum = checksum(psh) tcp_header = struct.pack(‘!HHLLBBHHH‘, source, dest_port, seq, ack_seq, offset_res, tcp_flags, window, tcp_checksum, urg_ptr) return tcp_header
IP数据包头部基本如上,详情请见文尾的github的地址。可以结合
create_tcp_header()和createTcpFlag()来操作数据包符号位,以实现基本功能3下的功能了。