搞安全的应该都知道端口扫描在渗透测试、漏洞扫描过程中的重要性,其与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