1. 程式人生 > >Python之埠掃描器編寫

Python之埠掃描器編寫

其實,寫個掃描器也挺好玩的,牽涉到了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資料包頭部基本如上。可以結合

  create_tcp_header()和createTcpFlag()來操作資料包符號位,以實現基本功能3下的功能了。