其實,寫個掃描器也挺好玩的,牽涉到了RAW Socket編程,能夠盡情地DIY數據包(固然,不符合數據包規則,好比checksum錯誤就沒辦法了),收穫頗深。其中,我以爲用C語言寫更有利於在編寫過程當中對加深對計算機網絡的理解,特別是數據包細節。可是因爲效率問題,還有Python真是太好用了(自從用了python,平常不再想去碰C/C++了,雖然python也寫的挺爛的)。話很少說,言歸正傳。python
學習信息安全的天然據說過nmap這種網絡掃描神器,其中功能選項多,老小咸宜,不只能知足網絡管理員的平常,還能知足網絡安全工程師的滲透測試,這個課程設計程度的掃描器天然不會有那麼多功能,主要實現利用TCP和UDP的一些特性的進行IP段主機存活狀況以及端口掃描。算法
基本功能以下:編程
1.發送udp包,檢測一個極少使用的端口,對回傳的ICMP包的進行分析,從而判斷主機是否存活。windows
2.利用TCP三次握手,經過是否鏈接成功,來斷定端口是否開放,其中採用了多線程加快了掃描速度。安全
3.經過RAW Socket的原生編程,對TCP標誌位進行人工設置,對回覆數據包的標誌位進行分析,從而不須要TCP三次握手就能夠對端口是否開放進行斷定。部分方式以下網絡
i:經過SYN置1,檢測回傳的數據包的標誌位是否爲SYN/ACK多線程
ii:經過ACK置1,查看是否回傳數據包,且數據包的標誌位是否爲RSTdom
iii:經過將全部標誌位都置0,查看是否回傳數據包,且數據包的標誌位是否爲RSTsocket
iv:經過FIN+URG+PSH置1,查看是否回傳數據包,且數據包的標誌位是否爲RSTtcp
在編寫功能以前,有必要寫對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數據包頭部基本如上。能夠結合
create_tcp_header()和createTcpFlag()來操做數據包符號位,以實現基本功能3下的功能了。