您的位置:首页 > 博客中心 > 互联网 >

端口扫描器的几种代码实现方案

时间:2022-05-11 01:12

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

 

本类排行

今日推荐

热门手游