搞安全的應該都知道端口掃描在滲透測試、漏洞掃描過程中的重要性,其與URL爬蟲等技術構成了漏洞掃描的第一階段,即目標信息收集。因此能否開發出一款高效穩定的端口掃描器,往往決定了漏洞掃描器的好壞。那么說到端口掃描器,我們往往會先想到nmap、masscan等神器,它們是這個領域的標桿。但本篇並不是為了介紹這幾款工具,而是談談如何自研一款高效穩定的端口掃描器。
端口掃描器,顧名思義就是為了探測服務器上的某個端口是否開放,究其原理可以分為很多種探測方式,比如tcp三次握手掃描,syn掃描等等,本篇並不打算詳細介紹這些掃描方式的區別,有興趣的可以看下nmap的文檔,對這幾種掃描方式有詳細的介紹。
那么說下本文重點,基於這幾天我研究並嘗試利用python、go開發tcp掃描器、tcp-syn掃描器,以及對比它們之間的速度性能、穩定性差異情況,將測試結果在此做個記錄,並分享一下代碼以及方案。
說明:文章結尾將給出本篇所使用代碼的Github地址,可供大家測試,代碼測試環境為centos7。
scan for Python Socket
Python的Socket模塊可以創建套接字,創建tcp三次握手連接,以此探測目標端口是否存活。本篇將使用socket模塊編寫tcp掃描以及syn掃描,並對比兩者的差異。
tcp scan
快來看代碼:
1 #! -*- coding:utf-8 -*- 2 import time 3 import socket 4 socket_timeout = 0.1 5 def tcp_scan(ip,port): 6 try: 7 s=socket.socket(socket.AF_INET,socket.SOCK_STREAM) 8 s.settimeout(socket_timeout) 9 c=s.connect_ex((ip,port)) 10 if c==0: 11 print “%s:%s is open” % (ip,port) 12 else : 13 # print “%s:%s is not open” % (ip,port) 14 pass 15 except Exception,e: 16 print e 17 s.close() 18 if __name__== “__main__” : 19 s_time = time.time() 20 ip = “14.215.177.38” 21 for port in range(0,1024): 22 ” ‘ 此處可用協作 ‘ ” 23 tcp_scan(ip,port) 24 e_time = time.time() 25 print “scan time is “ ,e_time-s_time
運行結果:
說明一下:可以看到此代碼掃描1024個端口用了102s,當然代碼並沒有用多線程、協程等方式提高掃描效率(使用協程測試過掃65535個端口用時400s左右),因為python在這方面的能力比較弱;由於掃描過程中會建立tcp三次握手,因此比較消耗資源。
tcp syn scan
相對tcp掃描,tcp syn掃描方式更為隱蔽,也更節省資源,那么如何利用socket模塊實現tcp syn掃描呢?這里需要用到SOCK_RAW,這個在socket編程中相對少用,資料也不多。
1 # -*- coding: UTF-8 -*- 2 import time 3 import random 4 import socket 5 import sys 6 from struct import * 7 ” ‘ 8 Warning:must run it as root 9 yum install python-devel libpcap-devel 10 pip install pcap 11 ‘ ” 12 def checksum(msg): 13 ” ‘ Check Summing ‘ ” 14 s = 0 15 for i in range(0,len(msg),2): 16 w = (ord(msg[i]) << 8) + (ord(msg[i+1])) 17 s = s+w 18 s = (s>>16) + (s & 0xffff) 19 s = ~s & 0xffff 20 return s 21 def CreateSocket(source_ip,dest_ip): 22 ” ‘ create socket connection ‘ ” 23 try: 24 s = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_TCP) 25 except socket.error, msg: 26 print ‘Socket create error: ‘ ,str(msg[0]), ‘message: ‘ ,msg[1] 27 sys.exit() 28 ” ‘ Set the IP header manually ‘ ” 29 s.setsockopt(socket.IPPROTO_IP, socket.IP_HDRINCL, 1) 30 return s 31 def CreateIpHeader(source_ip, dest_ip): 32 ” ‘ create ip header ‘ ” 33 # packet = ” 34 # ip header option 35 headerlen = 5 36 version = 4 37 tos = 0 38 tot_len = 20 + 20 39 id = random.randrange(18000,65535,1) 40 frag_off = 0 41 ttl = 255 42 protocol = socket.IPPROTO_TCP 43 check = 10 44 saddr = socket.inet_aton ( source_ip ) 45 daddr = socket.inet_aton ( dest_ip ) 46 hl_version = (version << 4) + headerlen 47 ip_header = pack( ‘!BBHHHBBH4s4s’ , hl_version, tos, tot_len, id, frag_off, ttl, protocol, check, saddr, daddr) 48 return ip_header 49 def create_tcp_syn_header(source_ip, dest_ip, dest_port): 50 ” ‘ create tcp syn header function ‘ ” 51 source = random.randrange(32000,62000,1) # randon select one source_port 52 seq = 0 53 ack_seq = 0 54 doff = 5 55 ” ‘ tcp flags ‘ ” 56 fin = 0 57 syn = 1 58 rst = 0 59 psh = 0 60 ack = 0 61 urg = 0 62 window = socket.htons (8192) # max windows size 63 check = 0 64 urg_ptr = 0 65 offset_res = (doff << 4) + 0 66 tcp_flags = fin + (syn<<1) + (rst<<2) + (psh<<3) + (ack<<4) + (urg<<5) 67 tcp_header = pack( ‘!HHLLBBHHH’ , source , dest_port, seq, ack_seq, offset_res, tcp_flags, window, check, urg_ptr) 68 ” ‘ headers option ‘ ” 69 source_address = socket.inet_aton( source_ip ) 70 dest_address = socket.inet_aton( dest_ip ) 71 placeholder = 0 72 protocol = socket.IPPROTO_TCP 73 tcp_length = len(tcp_header) 74 psh = pack( ‘!4s4sBBH’ , source_address, dest_address, placeholder, protocol, tcp_length); 75 psh = psh + tcp_header; 76 tcp_checksum = checksum(psh) 77 ” ‘ Repack the TCP header and fill in the correct checksum ‘ ” 78 tcp_header = pack( ‘!HHLLBBHHH’ , source , dest_port, seq, ack_seq, offset_res, tcp_flags, window, tcp_checksum, urg_ptr) 79 return tcp_header 80 def syn_scan(source_ip, dest_ip, des_port) : 81 s = CreateSocket(source_ip, dest_ip) 82 ip_header = CreateIpHeader(source_ip, dest_ip) 83 tcp_header = create_tcp_syn_header(source_ip, dest_ip, des_port) 84 packet = ip_header + tcp_header 85 s.sendto(packet, (dest_ip, 0)) 86 data = s.recvfrom(1024) [0][0:] 87 ip_header_len = (ord(data[0]) & 0x0f) * 4 88 # ip_header_ret = data[0: ip_header_len – 1] 89 tcp_header_len = (ord(data[32]) & 0xf0)>>2 90 tcp_header_ret = data[ip_header_len:ip_header_len+tcp_header_len – 1] 91 ” ‘ SYN/ACK flags ‘ ” 92 if ord(tcp_header_ret[13]) == 0x12: 93 print “%s:%s is open” % (dest_ip,des_port) 94 else : 95 print “%s:%s is not open” % (dest_ip,des_port) 96 if __name__== “__main__” : 97 t_s = time.time() 98 source_ip = ” # 填寫本機ip 99 dest_ip = ‘14.215.177.38’ 100 for des_port in range(1024): 101 syn_scan(source_ip, dest_ip, des_port) 102 t_e = time.time() 103 print “time is “ ,(t_e-t_s)
有一點需要注意的,運行這段代碼前,需要在系統上安裝依賴:
yum install python-devel libpcap-devel pip install pcap
運行結果:
說明:從運行結果上來看,並沒有很准確,而且速度也不快,不清楚是不是代碼上有問題。
scan for Python scapy
除了socket模塊外,python還有一個scapy模塊,可以用來模擬發包,但只能在linux下使用,其他操作系統不建議使用此模塊。
tcp syn csan
代碼在這里:
1 #! -*- coding:utf-8 -*- 2 import time 3 from scapy.all import * 4 ip = “14.215.177.38” 5 TIMEOUT = 0.5 6 threads = 500 7 port_range = 1024 8 retry = 1 9 def is_up(ip): 10 “” ” Tests if host is up “ “” 11 icmp = IP(dst=ip)/ICMP() 12 resp = sr1(icmp, timeout=TIMEOUT) 13 if resp == None: 14 return False 15 else : 16 return True 17 def reset_half_open(ip, ports): 18 # Reset the connection to stop half-open connections from pooling up 19 sr(IP(dst=ip)/TCP(dport=ports, flags= ‘AR’ ), timeout=TIMEOUT) 20 def is_open(ip, ports): 21 to_reset = [] 22 results = [] 23 p = IP(dst=ip)/TCP(dport=ports, flags= ‘S’ ) # Forging SYN packet 24 answers, un_answered = sr(p, verbose=False, retry=retry ,timeout=TIMEOUT) # Send the packets 25 for req, resp in answers: 26 if not resp.haslayer(TCP): 27 continue 28 tcp_layer = resp.getlayer(TCP) 29 if tcp_layer.flags == 0x12: 30 # port is open 31 to_reset.append(tcp_layer.sport) 32 results.append(tcp_layer.sport) 33 elif tcp_layer.flags == 0x14: 34 # port is open 35 pass 36 reset_half_open(ip, to_reset) 37 return results 38 def chunks(l, n): 39 “” “Yield successive n-sized chunks from l.” “” 40 for i in range(0, len(l), n): 41 yield l[i:i + n] 42 if __name__ == ‘__main__’ : 43 start_time = time.time() 44 open_port_list = [] 45 for ports in chunks(list(range(port_range)), threads): 46 results = is_open(ip, ports) 47 if results: 48 open_port_list += results 49 end_time = time.time() 50 print “%s %s” % (ip,open_port_list) 51 print “%s Scan Completed in %fs” % (ip, end_time-start_time)
運行結果:
說明:由於scapy可以一次性發多個syn包,因此速度相對socket更快一些,但穩定性沒有很好。
scan for python+nmap
文章開頭提到了nmap,其實在python中也可以直接調用nmap,看代碼:
1 #! -*- coding:utf-8 -*- 2 ” ‘ 3 pip install python-nmap 4 ‘ ” 5 import nmap 6 nm =nmap.PortScanner() 7 def scan(ip,port,arg): 8 try: 9 nm.scan(ip, arguments=arg+str(port)) 10 except nmap.nmap.PortScannerError: 11 print “Please run -O method for root privileges” 12 else : 13 for host in nm.all_hosts(): 14 for proto in nm[host].all_protocols(): 15 lport = nm[host][proto].keys() 16 lport.sort() 17 for port in lport: 18 print ( ‘port : %ststate : %s’ % (port, nm[host][proto][port][ ‘state’ ])) 19 if __name__== “__main__” : 20 port= “80,443,22,21” 21 scan(ip= “14.215.177.38” ,port=port,arg= “-sS -Pn -p” ) 22 # tcp scan -sT 23 # tcp syn scan -sS
運行結果:
由於nmap掃描速度相對比較慢,因此這里只演示掃描4個端口,不做速度的對比,當然其穩定性還是可以的。
scan for go
前文一直在介紹使用python語言開發端口掃描器,然而由於python在多線程方面的弱勢,掃描器的性能可想而知,因此我又利用go語言的高並發性優勢,嘗試開發端口掃描器。(題外話:為此我花了半天時間看了下go語言的基礎,勉強看懂了掃描代碼,並做了一些修改)
tcp scan
直接看代碼吧:
1 package main 2 // port tcp scan 3 import ( 4 “fmt” 5 “net” 6 “os” 7 “runtime” 8 “strconv” 9 “sync” 10 “time” 11 ) 12 func loop(inport chan int, startport, endport int) { 13 for i := startport; i <= endport; i++ { 14 inport <- i 15 } 16 close(inport) 17 } 18 type ScanSafeCount struct { 19 // 結構體 20 count int 21 mux sync.Mutex 22 } 23 var scanCount ScanSafeCount 24 func scanner(inport int, outport chan int, ip string, endport int) { 25 // 掃描函數 26 in := inport // 定義要掃描的端口號 27 // fmt.Printf( ” %d “ , in ) // 輸出掃描的端口 28 host := fmt.Sprintf( “%s:%d” , ip, in ) // 類似(ip,port) 29 tcpAddr, err := net.ResolveTCPAddr( “tcp4” , host) // 根據域名查找ip 30 if err != nil { 31 // 域名解析ip失敗 32 outport <- 0 33 } else { 34 conn, err := net.DialTimeout( “tcp” , tcpAddr.String(), 10*time.Second) //建立tcp連接 35 if err != nil { 36 // tcp連接失敗 37 outport <- 0 38 } else { 39 // tcp連接成功 40 outport <- in // 將端口寫入outport信號 41 fmt.Printf( “n *************( %d 可以 )*****************n” , in ) 42 conn.Close() 43 } 44 } 45 // 線程鎖 46 scanCount.mux.Lock() 47 scanCount.count = scanCount.count – 1 48 if scanCount.count <= 0 { 49 close(outport) 50 } 51 scanCount.mux.Unlock() 52 } 53 func main () { 54 runtime.GOMAXPROCS(runtime.NumCPU()) // 設置最大可使用的cpu核數 55 // 定義變量 56 inport := make(chan int) // 信號變量,類似python中的queue 57 outport := make(chan int) 58 collect := []int{} // 定義一個切片變量,類似python中的list 59 // fmt.Println(os.Args, len(os.Args)) // 獲取命令行參數並輸出 60 if len(os.Args) != 4 { 61 // 命令行參數個數有誤 62 fmt.Println( “使用方式:port_scanner IP startport endport” ) 63 os.Exit(0) 64 } 65 s_time := time.Now().Unix() 66 // fmt.Println( “掃描開始:” ) // 獲取當前時間 67 ip := string(os.Args[1]) // 獲取參數中的ip 68 startport, _ := strconv.Atoi(os.Args[2]) // 獲取參數中的啟始端口 69 endport, _ := strconv.Atoi(os.Args[3]) // 獲取參數中的結束端口 70 if startport > endport { 71 fmt.Println( “Usage: scanner IP startport endport” ) 72 fmt.Println( “Endport must be larger than startport” ) 73 os.Exit(0) 74 } else { 75 // 定義scanCount變量為ScanSafeCount結構體,即計算掃描的端口數量 76 scanCount = ScanSafeCount{count: (endport – startport + 1)} 77 } 78 fmt.Printf( “掃描 %s:%d———-%dn” , ip, startport, endport) 79 go loop(inport, startport, endport) // 執行loop函數將端口寫入input信號 80 for v := range inport { 81 // 開始循環input 82 go scanner(v, outport, ip, endport) 83 } 84 // 輸出結果 85 for port := range outport { 86 if port != 0 { 87 collect = append(collect, port) 88 } 89 } 90 fmt.Println( “–“ ) 91 fmt.Println(collect) 92 e_time := time.Now().Unix() 93 fmt.Println( “掃描時間:” , e_time-s_time) 94 }
代碼我就不解釋了(我在代碼中加了些注釋,應該大致可以看懂),本文也不打算介紹go的用法,畢竟自己也是剛開始學習go,有興趣的可以看看go的文檔,然后再回過頭來看看這段代碼。
代碼運行結果:
說明:由於是tcp掃描,所以多少還是占資源的,而且測試發現穩定性不是很好。
tcp syn scan
看代碼看代碼:
1 package main 2 // port tcp syn scan 3 import ( 4 “bytes” 5 “encoding/binary” 6 “flag” 7 “fmt” 8 “log” 9 “math/rand” 10 “net” 11 “os” 12 “strconv” 13 “strings” 14 “time” 15 “errors” 16 ) 17 //TCPHeader test 18 type TCPHeader struct { 19 SrcPort uint16 20 DstPort uint16 21 SeqNum uint32 22 AckNum uint32 23 Flags uint16 24 Window uint16 25 ChkSum uint16 26 UrgentPointer uint16 27 } 28 //TCPOption test 29 type TCPOption struct { 30 Kind uint8 31 Length uint8 32 Data []byte 33 } 34 type scanResult struct { 35 Port uint16 36 Opened bool 37 } 38 type scanJob struct { 39 Laddr string 40 Raddr string 41 SPort uint16 42 DPort uint16 43 Stop uint8 44 } 45 var stopFlag = make(chan uint8, 1) 46 func main () { 47 rate := time.Second / 400 48 throttle := time.Tick(rate) 49 jobs := make(chan *scanJob, 65536) 50 results := make(chan *scanResult, 1000) 51 for w := 0; w < 10; w++ { 52 go worker(w, jobs , throttle, results) 53 } 54 // 獲取命令行參數 55 ifaceName := flag.String( “i” , “eth0” , “Specify network” ) 56 remote := flag.String( “r” , “” , “remote address” ) 57 portRange := flag.String( “p” , “1-1024” , “port range: -p 1-1024” ) 58 flag.Parse() 59 // ifaceName := &interfaceName_ 60 // remote := &remote_ 61 // portRange := &portRange_ 62 s_time := time.Now().Unix() 63 laddr := interfaceAddress(*ifaceName) // 64 raddr := *remote 65 minPort , maxPort := portSplit(portRange) 66 // fmt.Println(laddr, raddr) // 輸出源ip地址,目標ip地址 67 go func(num int){ 68 for i := 0; i < num; i++ { 69 recvSynAck(laddr, raddr, results) 70 } 71 }(10) 72 go func(jobLength int) { 73 for j := minPort; j < maxPort + 1; j++ { 74 s := scanJob{ 75 Laddr: laddr, 76 Raddr: raddr, 77 SPort: uint16(random(10000, 65535)), 78 DPort: uint16(j + 1), 79 } 80 jobs <- &s 81 } 82 jobs <- &scanJob{Stop: 1} 83 }(1024) 84 for { 85 select { 86 case res := <-results: 87 fmt.Println( “掃描到開放的端口:” ,res.Port) 88 case <-stopFlag: 89 e_time := time.Now().Unix() 90 fmt.Println( “總共用了多少時間(s):” ,e_time-s_time) 91 os.Exit(0) 92 } 93 } 94 } 95 func worker(id int, jobs <-chan *scanJob, th <-chan time.Time, results chan<- *scanResult) { 96 for j := range jobs { 97 if j.Stop != 1 { 98 sendSyn(j.Laddr, j.Raddr, j.SPort, j.DPort) 99 } else { 100 stopFlag <- j.Stop 101 } 102 <-th 103 } 104 } 105 func checkError(err error) { 106 // 錯誤check 107 if err != nil { 108 log.Println(err) 109 } 110 } 111 //CheckSum test 112 func CheckSum(data []byte, src, dst [4]byte) uint16 { 113 pseudoHeader := []byte{ 114 src[0], src[1], src[2], src[3], 115 dst[0], dst[1], dst[2], dst[3], 116 0, 117 6, 118 0, 119 byte(len(data)), 120 } 121 totalLength := len(pseudoHeader) + len(data) 122 if totalLength%2 != 0 { 123 totalLength++ 124 } 125 d := make([]byte, 0, totalLength) 126 d = append(d, pseudoHeader…) 127 d = append(d, data…) 128 return ^mySum(d) 129 } 130 func mySum(data []byte) uint16 { 131 var sum uint32 132 for i := 0; i < len(data)-1; i += 2 { 133 sum += uint32(uint16(data[i])<<8 | uint16(data[i+1])) 134 } 135 sum = (sum >> 16) + (sum & 0xffff) 136 sum = sum + (sum >> 16) 137 return uint16(sum) 138 } 139 func sendSyn(laddr, raddr string, sport, dport uint16) { 140 conn, err := net.Dial( “ip4:tcp” , raddr) 141 checkError(err) 142 defer conn.Close() 143 op := []TCPOption{ 144 TCPOption{ 145 Kind: 2, 146 Length: 4, 147 Data: []byte{0x05, 0xb4}, 148 }, 149 TCPOption{ 150 Kind: 0, 151 }, 152 } 153 tcpH := TCPHeader{ 154 SrcPort: sport, 155 DstPort: dport, 156 SeqNum: rand.Uint32(), 157 AckNum: 0, 158 Flags: 0x8002, 159 Window: 8192, 160 ChkSum: 0, 161 UrgentPointer: 0, 162 } 163 buff := new(bytes.Buffer) 164 err = binary.Write(buff, binary.BigEndian, tcpH) 165 checkError(err) 166 for i := range op { 167 binary.Write(buff, binary.BigEndian, op[i].Kind) 168 binary.Write(buff, binary.BigEndian, op[i].Length) 169 binary.Write(buff, binary.BigEndian, op[i].Data) 170 } 171 binary.Write(buff, binary.BigEndian, [6]byte{}) 172 data := buff.Bytes() 173 checkSum := CheckSum(data, ipstr2Bytes(laddr), ipstr2Bytes(raddr)) 174 //fmt.Printf( “CheckSum 0x%Xn” , checkSum) 175 tcpH.ChkSum = checkSum 176 buff = new(bytes.Buffer) 177 binary.Write(buff, binary.BigEndian, tcpH) 178 for i := range op { 179 binary.Write(buff, binary.BigEndian, op[i].Kind) 180 binary.Write(buff, binary.BigEndian, op[i].Length) 181 binary.Write(buff, binary.BigEndian, op[i].Data) 182 } 183 binary.Write(buff, binary.BigEndian, [6]byte{}) 184 data = buff.Bytes() 185 //fmt.Printf( “% Xn” , data) 186 _, err = conn.Write(data) 187 checkError(err) 188 } 189 func recvSynAck(laddr, raddr string, res chan<- *scanResult) error { 190 listenAddr, err := net.ResolveIPAddr( “ip4” , laddr) // 解析域名為ip 191 checkError(err) 192 conn, err := net.ListenIP( “ip4:tcp” , listenAddr) 193 defer conn.Close() 194 checkError(err) 195 for { 196 buff := make([]byte, 1024) 197 _, addr, err := conn.ReadFrom(buff) 198 if err != nil { 199 continue 200 } 201 if addr.String() != raddr || buff[13] != 0x12 { 202 continue 203 } 204 var port uint16 205 binary.Read(bytes.NewReader(buff), binary.BigEndian, &port) 206 res <- &scanResult{ 207 Port: port, 208 Opened: true , 209 } 210 } 211 } 212 func ipstr2Bytes(addr string) [4]byte { 213 s := strings.Split(addr, “.” ) 214 b0, _ := strconv.Atoi(s[0]) 215 b1, _ := strconv.Atoi(s[1]) 216 b2, _ := strconv.Atoi(s[2]) 217 b3, _ := strconv.Atoi(s[3]) 218 return [4]byte{byte(b0), byte(b1), byte(b2), byte(b3)} 219 } 220 func random(min, max int) int { 221 return rand.Intn(max-min) + min 222 } 223 func interfaceAddress(ifaceName string ) string { 224 iface, err:= net.InterfaceByName(ifaceName) 225 if err != nil { 226 panic(err) 227 } 228 addr, err := iface.Addrs() 229 if err != nil { 230 panic(err) 231 } 232 addrStr := strings.Split(addr[0].String(), “/” )[0] 233 return addrStr 234 } 235 func portSplit(portRange *string) (uint16, uint16) { 236 ports := strings.Split(*portRange, “-“ ) 237 minPort, err := strconv.ParseUint(ports[0], 10, 16) 238 if err !=nil { 239 panic(err) 240 } 241 maxPort, err := strconv.ParseUint(ports[1], 10, 16) 242 if err != nil { 243 panic(err) 244 } 245 if minPort > maxPort { 246 panic(errors.New( “minPort must greater than maxPort” )) 247 } 248 return uint16(minPort), uint16(maxPort) 249 }
代碼運行結果:
沒錯,就是2s!我測試了掃描全端口(0-65535),大概120s左右,而且穩定性不錯。
scan for go+python
經過前面的測試我們不難發現,在並發的性能上,go完勝python,但go不適合做復雜的邏輯處理,以及web開發之類的。因此如何整合python跟go呢?這里我想了兩種方案,第一種將go語言打包成.so動態連接庫,利用python的ctypes模塊可以調用;第二種是go寫成接口,提供python調用。寫成接口的方式相對簡單一些,因此這里不介紹了,說說如何打包go,即如何利用python調用go的方法或者說函數。
先看下修改過后的tcp_syn_scan.go代碼:
1 package main 2 // port tcp syn scan 3 import ( 4 “C” 5 “os” 6 “bytes” 7 “encoding/binary” 8 “fmt” 9 “log” 10 “math/rand” 11 “net” 12 “strconv” 13 “strings” 14 “time” 15 “errors” 16 ) 17 //TCPHeader test 18 type TCPHeader struct { 19 SrcPort uint16 20 DstPort uint16 21 SeqNum uint32 22 AckNum uint32 23 Flags uint16 24 Window uint16 25 ChkSum uint16 26 UrgentPointer uint16 27 } 28 //TCPOption test 29 type TCPOption struct { 30 Kind uint8 31 Length uint8 32 Data []byte 33 } 34 type scanResult struct { 35 Port uint16 36 Opened bool 37 } 38 type scanJob struct { 39 Laddr string 40 Raddr string 41 SPort uint16 42 DPort uint16 43 Stop uint8 44 } 45 var stopFlag = make(chan uint8, 1) 46 // export Scan 47 func Scan(remote_ *C.char, portRange_ *C.char, interfaceName_ *C.char) { 48 rate := time.Second / 400 49 throttle := time.Tick(rate) 50 jobs := make(chan *scanJob, 65536) 51 results := make(chan *scanResult, 1000) 52 for w := 0; w < 10; w++ { 53 go worker(w, jobs , throttle, results) 54 } 55 // 獲取命令行參數 56 // ifaceName := flag.String( “i” , “eth0” , “Specify network” ) 57 // remote := flag.String( “r” , “” , “remote address” ) 58 // portRange := flag.String( “p” , “1-1024” , “port range: -p 1-1024” ) 59 // flag.Parse() 60 interfaceName_1 := C.GoString(interfaceName_) 61 remote_1 := C.GoString(remote_) 62 portRange_1 := C.GoString(portRange_) 63 ifaceName := &interfaceName_1 64 remote := &remote_1 65 portRange := &portRange_1 66 s_time := time.Now().Unix() 67 laddr := interfaceAddress(*ifaceName) // 68 raddr := *remote 69 minPort , maxPort := portSplit(portRange) 70 fmt.Println(laddr, raddr) // 輸出源ip地址,目標ip地址 71 go func(num int){ 72 for i := 0; i < num; i++ { 73 recvSynAck(laddr, raddr, results) 74 } 75 }(10) 76 go func(jobLength int) { 77 for j := minPort; j < maxPort + 1; j++ { 78 s := scanJob{ 79 Laddr: laddr, 80 Raddr: raddr, 81 SPort: uint16(random(10000, 65535)), 82 DPort: uint16(j + 1), 83 } 84 jobs <- &s 85 } 86 jobs <- &scanJob{Stop: 1} 87 }(1024) 88 for { 89 select { 90 case res := <-results: 91 fmt.Println( “掃描到開放的端口:” ,res.Port) //輸出開放的端口號 92 case <-stopFlag: 93 e_time := time.Now().Unix() 94 fmt.Println( “本次掃描總共耗時(s):” ,e_time-s_time) 95 os.Exit(0) 96 } 97 } 98 } 99 func worker(id int, jobs <-chan *scanJob, th <-chan time.Time, results chan<- *scanResult) { 100 for j := range jobs { 101 if j.Stop != 1 { 102 sendSyn(j.Laddr, j.Raddr, j.SPort, j.DPort) 103 } else { 104 stopFlag <- j.Stop 105 } 106 <-th 107 } 108 } 109 func checkError(err error) { 110 // 錯誤check 111 if err != nil { 112 log.Println(err) 113 } 114 } 115 //CheckSum test 116 func CheckSum(data []byte, src, dst [4]byte) uint16 { 117 pseudoHeader := []byte{ 118 src[0], src[1], src[2], src[3], 119 dst[0], dst[1], dst[2], dst[3], 120 0, 121 6, 122 0, 123 byte(len(data)), 124 } 125 totalLength := len(pseudoHeader) + len(data) 126 if totalLength%2 != 0 { 127 totalLength++ 128 } 129 d := make([]byte, 0, totalLength) 130 d = append(d, pseudoHeader…) 131 d = append(d, data…) 132 return ^mySum(d) 133 } 134 func mySum(data []byte) uint16 { 135 var sum uint32 136 for i := 0; i < len(data)-1; i += 2 { 137 sum += uint32(uint16(data[i])<<8 | uint16(data[i+1])) 138 } 139 sum = (sum >> 16) + (sum & 0xffff) 140 sum = sum + (sum >> 16) 141 return uint16(sum) 142 } 143 func sendSyn(laddr, raddr string, sport, dport uint16) { 144 conn, err := net.Dial( “ip4:tcp” , raddr) 145 checkError(err) 146 defer conn.Close() 147 op := []TCPOption{ 148 TCPOption{ 149 Kind: 2, 150 Length: 4, 151 Data: []byte{0x05, 0xb4}, 152 }, 153 TCPOption{ 154 Kind: 0, 155 }, 156 } 157 tcpH := TCPHeader{ 158 SrcPort: sport, 159 DstPort: dport, 160 SeqNum: rand.Uint32(), 161 AckNum: 0, 162 Flags: 0x8002, 163 Window: 8192, 164 ChkSum: 0, 165 UrgentPointer: 0, 166 } 167 buff := new(bytes.Buffer) 168 err = binary.Write(buff, binary.BigEndian, tcpH) 169 checkError(err) 170 for i := range op { 171 binary.Write(buff, binary.BigEndian, op[i].Kind) 172 binary.Write(buff, binary.BigEndian, op[i].Length) 173 binary.Write(buff, binary.BigEndian, op[i].Data) 174 } 175 binary.Write(buff, binary.BigEndian, [6]byte{}) 176 data := buff.Bytes() 177 checkSum := CheckSum(data, ipstr2Bytes(laddr), ipstr2Bytes(raddr)) 178 //fmt.Printf( “CheckSum 0x%Xn” , checkSum) 179 tcpH.ChkSum = checkSum 180 buff = new(bytes.Buffer) 181 binary.Write(buff, binary.BigEndian, tcpH) 182 for i := range op { 183 binary.Write(buff, binary.BigEndian, op[i].Kind) 184 binary.Write(buff, binary.BigEndian, op[i].Length) 185 binary.Write(buff, binary.BigEndian, op[i].Data) 186 } 187 binary.Write(buff, binary.BigEndian, [6]byte{}) 188 data = buff.Bytes() 189 //fmt.Printf( “% Xn” , data) 190 _, err = conn.Write(data) 191 checkError(err) 192 } 193 func recvSynAck(laddr, raddr string, res chan<- *scanResult) error { 194 listenAddr, err := net.ResolveIPAddr( “ip4” , laddr) // 解析域名為ip 195 checkError(err) 196 conn, err := net.ListenIP( “ip4:tcp” , listenAddr) 197 defer conn.Close() 198 checkError(err) 199 for { 200 buff := make([]byte, 1024) 201 _, addr, err := conn.ReadFrom(buff) 202 if err != nil { 203 continue 204 } 205 if addr.String() != raddr || buff[13] != 0x12 { 206 continue 207 } 208 var port uint16 209 binary.Read(bytes.NewReader(buff), binary.BigEndian, &port) 210 res <- &scanResult{ 211 Port: port, 212 Opened: true , 213 } 214 } 215 } 216 func ipstr2Bytes(addr string) [4]byte { 217 s := strings.Split(addr, “.” ) 218 b0, _ := strconv.Atoi(s[0]) 219 b1, _ := strconv.Atoi(s[1]) 220 b2, _ := strconv.Atoi(s[2]) 221 b3, _ := strconv.Atoi(s[3]) 222 return [4]byte{byte(b0), byte(b1), byte(b2), byte(b3)} 223 } 224 func random(min, max int) int { 225 return rand.Intn(max-min) + min 226 } 227 func interfaceAddress(ifaceName string ) string { 228 iface, err:= net.InterfaceByName(ifaceName) 229 if err != nil { 230 panic(err) 231 } 232 addr, err := iface.Addrs() 233 if err != nil { 234 panic(err) 235 } 236 addrStr := strings.Split(addr[0].String(), “/” )[0] 237 return addrStr 238 } 239 func portSplit(portRange *string) (uint16, uint16) { 240 ports := strings.Split(*portRange, “-“ ) 241 minPort, err := strconv.ParseUint(ports[0], 10, 16) 242 if err !=nil { 243 panic(err) 244 } 245 maxPort, err := strconv.ParseUint(ports[1], 10, 16) 246 if err != nil { 247 panic(err) 248 } 249 if minPort > maxPort { 250 panic(errors.New( “minPort must greater than maxPort” )) 251 } 252 return uint16(minPort), uint16(maxPort) 253 } 254 func main () { }
然后利用go自身的build命令,將其打包成.so庫:
go build -buildmode=c-shared -o tcp_syn_scan.so tcp_syn_scan.go
打包后會得到一個tcp_syn_scan.so和一個tcp_syn_scan.h。然后利用下面的python代碼就可以調用Go代碼中的Scan()函數了,創建一個tcp_syn_scan.py文件。
1 #! -*- coding:utf-8 -*- 2 from ctypes import * 3 lib = cdll.LoadLibrary(u ‘./scan.so’ ) 4 lib.Scan( “14.215.177.38” , “1-1024” , “eth0” ) # ip,端口范圍,網卡
代碼運行結果:
說明:相當原生的go,利用python去調用go會損耗一些性能,但總體上還可以。
后記
本文結論就是可以利用go開發掃描模塊(性能更佳),並結合python調用。
本文代碼項目地址:https://github.com/tengzhangchao/PortScan