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

这雨夜太漫长 失眠的我 在谁梦里 歌唱

  搞安全的应该都知道端口扫描在渗透测试、漏洞扫描过程中的重要性,其与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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#! -*- coding:utf-8 -*-
import time
import socket
socket_timeout = 0.1
def tcp_scan(ip,port):
try:
s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
s.settimeout(socket_timeout)
c=s.connect_ex((ip,port))
if c==0:
print "%s:%s is open" % (ip,port)
else:
# print "%s:%s is not open" % (ip,port)
pass
except Exception,e:
print e
s.close()
if __name__=="__main__":
s_time = time.time()
ip = "14.215.177.38"
for port in range(0,1024):
''' 此处可用协作 '''
tcp_scan(ip,port)
e_time = time.time()
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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
# -*- coding: UTF-8 -*-
import time
import random
import socket
import sys
from struct import *
'''
Warning:must run it as root
yum install python-devel libpcap-devel
pip install pcap
'''
def checksum(msg):
''' Check Summing '''
s = 0
for i in range(0,len(msg),2):
w = (ord(msg[i]) << 8) + (ord(msg[i+1]))
s = s+w
s = (s>>16) + (s & 0xffff)
s = ~s & 0xffff
return s
def CreateSocket(source_ip,dest_ip):
''' create socket connection '''
try:
s = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_TCP)
except socket.error, msg:
print 'Socket create error: ',str(msg[0]),'message: ',msg[1]
sys.exit()
''' Set the IP header manually '''
s.setsockopt(socket.IPPROTO_IP, socket.IP_HDRINCL, 1)
return s
def CreateIpHeader(source_ip, dest_ip):
''' create ip header '''
# packet = ''
# ip header option
headerlen = 5
version = 4
tos = 0
tot_len = 20 + 20
id = random.randrange(18000,65535,1)
frag_off = 0
ttl = 255
protocol = socket.IPPROTO_TCP
check = 10
saddr = socket.inet_aton ( source_ip )
daddr = socket.inet_aton ( dest_ip )
hl_version = (version << 4) + headerlen
ip_header = pack('!BBHHHBBH4s4s', hl_version, tos, tot_len, id, frag_off, ttl, protocol, check, saddr, daddr)
return ip_header
def create_tcp_syn_header(source_ip, dest_ip, dest_port):
''' create tcp syn header function '''
source = random.randrange(32000,62000,1) # randon select one source_port
seq = 0
ack_seq = 0
doff = 5
''' tcp flags '''
fin = 0
syn = 1
rst = 0
psh = 0
ack = 0
urg = 0
window = socket.htons (8192) # max windows size
check = 0
urg_ptr = 0
offset_res = (doff << 4) + 0
tcp_flags = fin + (syn<<1) + (rst<<2) + (psh<<3) + (ack<<4) + (urg<<5)
tcp_header = pack('!HHLLBBHHH', source, dest_port, seq, ack_seq, offset_res, tcp_flags, window, check, urg_ptr)
''' headers option '''
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 = pack('!4s4sBBH', source_address, dest_address, placeholder, protocol, tcp_length);
psh = psh + tcp_header;
tcp_checksum = checksum(psh)
''' Repack the TCP header and fill in the correct checksum '''
tcp_header = pack('!HHLLBBHHH', source, dest_port, seq, ack_seq, offset_res, tcp_flags, window, tcp_checksum, urg_ptr)
return tcp_header
def syn_scan(source_ip, dest_ip, des_port) :
s = CreateSocket(source_ip, dest_ip)
ip_header = CreateIpHeader(source_ip, dest_ip)
tcp_header = create_tcp_syn_header(source_ip, dest_ip, des_port)
packet = ip_header + tcp_header
s.sendto(packet, (dest_ip, 0))
data = s.recvfrom(1024) [0][0:]
ip_header_len = (ord(data[0]) & 0x0f) * 4
# ip_header_ret = data[0: ip_header_len - 1]
tcp_header_len = (ord(data[32]) & 0xf0)>>2
tcp_header_ret = data[ip_header_len:ip_header_len+tcp_header_len - 1]
''' SYN/ACK flags '''
if ord(tcp_header_ret[13]) == 0x12:
print "%s:%s is open" % (dest_ip,des_port)
else:
print "%s:%s is not open" % (dest_ip,des_port)
if __name__=="__main__":
t_s = time.time()
source_ip = '' # 填写本机ip
dest_ip = '14.215.177.38'
for des_port in range(1024):
syn_scan(source_ip, dest_ip, des_port)
t_e = time.time()
print "time is ",(t_e-t_s)

有一点需要注意的,运行这段代码前,需要在系统上安装依赖:

1
2
yum install python-devel libpcap-devel
pip install pcap

运行结果:

说明:从运行结果上来看,并没有很准确,而且速度也不快,不清楚是不是代码上有问题。

scan for Python scapy

除了socket模块外,python还有一个scapy模块,可以用来模拟发包,但只能在linux下使用,其他操作系统不建议使用此模块。

tcp syn csan

代码在这里:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
#! -*- coding:utf-8 -*-
import time
from scapy.all import *
ip = "14.215.177.38"
TIMEOUT = 0.5
threads = 500
port_range = 1024
retry = 1
def is_up(ip):
""" Tests if host is up """
icmp = IP(dst=ip)/ICMP()
resp = sr1(icmp, timeout=TIMEOUT)
if resp == None:
return False
else:
return True
def reset_half_open(ip, ports):
# Reset the connection to stop half-open connections from pooling up
sr(IP(dst=ip)/TCP(dport=ports, flags='AR'), timeout=TIMEOUT)
def is_open(ip, ports):
to_reset = []
results = []
p = IP(dst=ip)/TCP(dport=ports, flags='S') # Forging SYN packet
answers, un_answered = sr(p, verbose=False, retry=retry ,timeout=TIMEOUT) # Send the packets
for req, resp in answers:
if not resp.haslayer(TCP):
continue
tcp_layer = resp.getlayer(TCP)
if tcp_layer.flags == 0x12:
# port is open
to_reset.append(tcp_layer.sport)
results.append(tcp_layer.sport)
elif tcp_layer.flags == 0x14:
# port is open
pass
reset_half_open(ip, to_reset)
return results
def chunks(l, n):
"""Yield successive n-sized chunks from l."""
for i in range(0, len(l), n):
yield l[i:i + n]
if __name__ == '__main__':
start_time = time.time()
open_port_list = []
for ports in chunks(list(range(port_range)), threads):
results = is_open(ip, ports)
if results:
open_port_list += results
end_time = time.time()
print "%s %s" % (ip,open_port_list)
print "%s Scan Completed in %fs" % (ip, end_time-start_time)

运行结果:

说明:由于scapy可以一次性发多个syn包,因此速度相对socket更快一些,但稳定性没有很好。

scan for python+nmap

文章开头提到了nmap,其实在python中也可以直接调用nmap,看代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#! -*- coding:utf-8 -*-
'''
pip install python-nmap
'''
import nmap
nm =nmap.PortScanner()
def scan(ip,port,arg):
try:
nm.scan(ip, arguments=arg+str(port))
except nmap.nmap.PortScannerError:
print "Please run -O method for root privileges"
else:
for host in nm.all_hosts():
for proto in nm[host].all_protocols():
lport = nm[host][proto].keys()
lport.sort()
for port in lport:
print ('port : %s\tstate : %s' % (port, nm[host][proto][port]['state']))
if __name__=="__main__":
port="80,443,22,21"
scan(ip="14.215.177.38",port=port,arg="-sS -Pn -p")
# tcp scan -sT
# tcp syn scan -sS

运行结果:

由于nmap扫描速度相对比较慢,因此这里只演示扫描4个端口,不做速度的对比,当然其稳定性还是可以的。

scan for go

  前文一直在介绍使用python语言开发端口扫描器,然而由于python在多线程方面的弱势,扫描器的性能可想而知,因此我又利用go语言的高并发性优势,尝试开发端口扫描器。(题外话:为此我花了半天时间看了下go语言的基础,勉强看懂了扫描代码,并做了一些修改)

tcp scan

直接看代码吧:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
package main
// port tcp scan
import (
"fmt"
"net"
"os"
"runtime"
"strconv"
"sync"
"time"
)
func loop(inport chan int, startport, endport int) {
for i := startport; i <= endport; i++ {
inport <- i
}
close(inport)
}
type ScanSafeCount struct {
// 结构体
count int
mux sync.Mutex
}
var scanCount ScanSafeCount
func scanner(inport int, outport chan int, ip string, endport int) {
// 扫描函数
in := inport // 定义要扫描的端口号
// fmt.Printf(" %d ", in) // 输出扫描的端口
host := fmt.Sprintf("%s:%d", ip, in) // 类似(ip,port)
tcpAddr, err := net.ResolveTCPAddr("tcp4", host) // 根据域名查找ip
if err != nil {
// 域名解析ip失败
outport <- 0
} else {
conn, err := net.DialTimeout("tcp", tcpAddr.String(), 10*time.Second) //建立tcp连接
if err != nil {
// tcp连接失败
outport <- 0
} else {
// tcp连接成功
outport <- in // 将端口写入outport信号
fmt.Printf("\n *************( %d 可以 )*****************\n", in)
conn.Close()
}
}
// 线程锁
scanCount.mux.Lock()
scanCount.count = scanCount.count - 1
if scanCount.count <= 0 {
close(outport)
}
scanCount.mux.Unlock()
}
func main() {
runtime.GOMAXPROCS(runtime.NumCPU()) // 设置最大可使用的cpu核数
// 定义变量
inport := make(chan int) // 信号变量,类似python中的queue
outport := make(chan int)
collect := []int{} // 定义一个切片变量,类似python中的list
// fmt.Println(os.Args, len(os.Args)) // 获取命令行参数并输出
if len(os.Args) != 4 {
// 命令行参数个数有误
fmt.Println("使用方式: port_scanner IP startport endport")
os.Exit(0)
}
s_time := time.Now().Unix()
// fmt.Println("扫描开始:") // 获取当前时间
ip := string(os.Args[1]) // 获取参数中的ip
startport, _ := strconv.Atoi(os.Args[2]) // 获取参数中的启始端口
endport, _ := strconv.Atoi(os.Args[3]) // 获取参数中的结束端口
if startport > endport {
fmt.Println("Usage: scanner IP startport endport")
fmt.Println("Endport must be larger than startport")
os.Exit(0)
} else {
// 定义scanCount变量为ScanSafeCount结构体,即计算扫描的端口数量
scanCount = ScanSafeCount{count: (endport - startport + 1)}
}
fmt.Printf("扫描 %s:%d----------%d\n", ip, startport, endport)
go loop(inport, startport, endport) // 执行loop函数将端口写入input信号
for v := range inport {
// 开始循环input
go scanner(v, outport, ip, endport)
}
// 输出结果
for port := range outport {
if port != 0 {
collect = append(collect, port)
}
}
fmt.Println("--")
fmt.Println(collect)
e_time := time.Now().Unix()
fmt.Println("扫描时间:", e_time-s_time)
}

  代码我就不解释了(我在代码中加了些注释,应该大致可以看懂),本文也不打算介绍go的用法,毕竟自己也是刚开始学习go,有兴趣的可以看看go的文档,然后再回过头来看看这段代码。

代码运行结果:

说明:由于是tcp扫描,所以多少还是占资源的,而且测试发现稳定性不是很好。

tcp syn scan

看代码看代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
package main
// port tcp syn scan
import (
"bytes"
"encoding/binary"
"flag"
"fmt"
"log"
"math/rand"
"net"
"os"
"strconv"
"strings"
"time"
"errors"
)
//TCPHeader test
type TCPHeader struct {
SrcPort uint16
DstPort uint16
SeqNum uint32
AckNum uint32
Flags uint16
Window uint16
ChkSum uint16
UrgentPointer uint16
}
//TCPOption test
type TCPOption struct {
Kind uint8
Length uint8
Data []byte
}
type scanResult struct {
Port uint16
Opened bool
}
type scanJob struct {
Laddr string
Raddr string
SPort uint16
DPort uint16
Stop uint8
}
var stopFlag = make(chan uint8, 1)
func main() {
rate := time.Second / 400
throttle := time.Tick(rate)
jobs := make(chan *scanJob, 65536)
results := make(chan *scanResult, 1000)
for w := 0; w < 10; w++ {
go worker(w, jobs, throttle, results)
}
// 获取命令行参数
ifaceName := flag.String("i", "eth0", "Specify network")
remote := flag.String("r", "", "remote address")
portRange := flag.String("p", "1-1024", "port range: -p 1-1024")
flag.Parse()
// ifaceName := &interfaceName_
// remote := &remote_
// portRange := &portRange_
s_time := time.Now().Unix()
laddr := interfaceAddress(*ifaceName) //
raddr := *remote
minPort , maxPort := portSplit(portRange)
// fmt.Println(laddr, raddr) // 输出源ip地址,目标ip地址
go func(num int){
for i := 0; i < num; i++ {
recvSynAck(laddr, raddr, results)
}
}(10)
go func(jobLength int) {
for j := minPort; j < maxPort + 1; j++ {
s := scanJob{
Laddr: laddr,
Raddr: raddr,
SPort: uint16(random(10000, 65535)),
DPort: uint16(j + 1),
}
jobs <- &s
}
jobs <- &scanJob{Stop: 1}
}(1024)
for {
select {
case res := <-results:
fmt.Println("扫描到开放的端口:",res.Port)
case <-stopFlag:
e_time := time.Now().Unix()
fmt.Println("总共用了多少时间(s):",e_time-s_time)
os.Exit(0)
}
}
}
func worker(id int, jobs <-chan *scanJob, th <-chan time.Time, results chan<- *scanResult) {
for j := range jobs {
if j.Stop != 1 {
sendSyn(j.Laddr, j.Raddr, j.SPort, j.DPort)
} else {
stopFlag <- j.Stop
}
<-th
}
}
func checkError(err error) {
// 错误check
if err != nil {
log.Println(err)
}
}
//CheckSum test
func CheckSum(data []byte, src, dst [4]byte) uint16 {
pseudoHeader := []byte{
src[0], src[1], src[2], src[3],
dst[0], dst[1], dst[2], dst[3],
0,
6,
0,
byte(len(data)),
}
totalLength := len(pseudoHeader) + len(data)
if totalLength%2 != 0 {
totalLength++
}
d := make([]byte, 0, totalLength)
d = append(d, pseudoHeader...)
d = append(d, data...)
return ^mySum(d)
}
func mySum(data []byte) uint16 {
var sum uint32
for i := 0; i < len(data)-1; i += 2 {
sum += uint32(uint16(data[i])<<8 | uint16(data[i+1]))
}
sum = (sum >> 16) + (sum & 0xffff)
sum = sum + (sum >> 16)
return uint16(sum)
}
func sendSyn(laddr, raddr string, sport, dport uint16) {
conn, err := net.Dial("ip4:tcp", raddr)
checkError(err)
defer conn.Close()
op := []TCPOption{
TCPOption{
Kind: 2,
Length: 4,
Data: []byte{0x05, 0xb4},
},
TCPOption{
Kind: 0,
},
}
tcpH := TCPHeader{
SrcPort: sport,
DstPort: dport,
SeqNum: rand.Uint32(),
AckNum: 0,
Flags: 0x8002,
Window: 8192,
ChkSum: 0,
UrgentPointer: 0,
}
buff := new(bytes.Buffer)
err = binary.Write(buff, binary.BigEndian, tcpH)
checkError(err)
for i := range op {
binary.Write(buff, binary.BigEndian, op[i].Kind)
binary.Write(buff, binary.BigEndian, op[i].Length)
binary.Write(buff, binary.BigEndian, op[i].Data)
}
binary.Write(buff, binary.BigEndian, [6]byte{})
data := buff.Bytes()
checkSum := CheckSum(data, ipstr2Bytes(laddr), ipstr2Bytes(raddr))
//fmt.Printf("CheckSum 0x%X\n", checkSum)
tcpH.ChkSum = checkSum
buff = new(bytes.Buffer)
binary.Write(buff, binary.BigEndian, tcpH)
for i := range op {
binary.Write(buff, binary.BigEndian, op[i].Kind)
binary.Write(buff, binary.BigEndian, op[i].Length)
binary.Write(buff, binary.BigEndian, op[i].Data)
}
binary.Write(buff, binary.BigEndian, [6]byte{})
data = buff.Bytes()
//fmt.Printf("% X\n", data)
_, err = conn.Write(data)
checkError(err)
}
func recvSynAck(laddr, raddr string, res chan<- *scanResult) error {
listenAddr, err := net.ResolveIPAddr("ip4", laddr) // 解析域名为ip
checkError(err)
conn, err := net.ListenIP("ip4:tcp", listenAddr)
defer conn.Close()
checkError(err)
for {
buff := make([]byte, 1024)
_, addr, err := conn.ReadFrom(buff)
if err != nil {
continue
}
if addr.String() != raddr || buff[13] != 0x12 {
continue
}
var port uint16
binary.Read(bytes.NewReader(buff), binary.BigEndian, &port)
res <- &scanResult{
Port: port,
Opened: true,
}
}
}
func ipstr2Bytes(addr string) [4]byte {
s := strings.Split(addr, ".")
b0, _ := strconv.Atoi(s[0])
b1, _ := strconv.Atoi(s[1])
b2, _ := strconv.Atoi(s[2])
b3, _ := strconv.Atoi(s[3])
return [4]byte{byte(b0), byte(b1), byte(b2), byte(b3)}
}
func random(min, max int) int {
return rand.Intn(max-min) + min
}
func interfaceAddress(ifaceName string ) string {
iface, err:= net.InterfaceByName(ifaceName)
if err != nil {
panic(err)
}
addr, err := iface.Addrs()
if err != nil {
panic(err)
}
addrStr := strings.Split(addr[0].String(), "/")[0]
return addrStr
}
func portSplit(portRange *string) (uint16, uint16) {
ports := strings.Split(*portRange, "-")
minPort, err := strconv.ParseUint(ports[0], 10, 16)
if err !=nil {
panic(err)
}
maxPort, err := strconv.ParseUint(ports[1], 10, 16)
if err != nil {
panic(err)
}
if minPort > maxPort {
panic(errors.New("minPort must greater than maxPort"))
}
return uint16(minPort), uint16(maxPort)
}

代码运行结果:

没错,就是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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
package main
// port tcp syn scan
import (
"C"
"os"
"bytes"
"encoding/binary"
"fmt"
"log"
"math/rand"
"net"
"strconv"
"strings"
"time"
"errors"
)
//TCPHeader test
type TCPHeader struct {
SrcPort uint16
DstPort uint16
SeqNum uint32
AckNum uint32
Flags uint16
Window uint16
ChkSum uint16
UrgentPointer uint16
}
//TCPOption test
type TCPOption struct {
Kind uint8
Length uint8
Data []byte
}
type scanResult struct {
Port uint16
Opened bool
}
type scanJob struct {
Laddr string
Raddr string
SPort uint16
DPort uint16
Stop uint8
}
var stopFlag = make(chan uint8, 1)
//export Scan
func Scan(remote_ *C.char, portRange_ *C.char, interfaceName_ *C.char) {
rate := time.Second / 400
throttle := time.Tick(rate)
jobs := make(chan *scanJob, 65536)
results := make(chan *scanResult, 1000)
for w := 0; w < 10; w++ {
go worker(w, jobs, throttle, results)
}
// 获取命令行参数
// ifaceName := flag.String("i", "eth0", "Specify network")
// remote := flag.String("r", "", "remote address")
// portRange := flag.String("p", "1-1024", "port range: -p 1-1024")
// flag.Parse()
interfaceName_1 := C.GoString(interfaceName_)
remote_1 := C.GoString(remote_)
portRange_1 := C.GoString(portRange_)
ifaceName := &interfaceName_1
remote := &remote_1
portRange := &portRange_1
s_time := time.Now().Unix()
laddr := interfaceAddress(*ifaceName) //
raddr := *remote
minPort , maxPort := portSplit(portRange)
fmt.Println(laddr, raddr) // 输出源ip地址,目标ip地址
go func(num int){
for i := 0; i < num; i++ {
recvSynAck(laddr, raddr, results)
}
}(10)
go func(jobLength int) {
for j := minPort; j < maxPort + 1; j++ {
s := scanJob{
Laddr: laddr,
Raddr: raddr,
SPort: uint16(random(10000, 65535)),
DPort: uint16(j + 1),
}
jobs <- &s
}
jobs <- &scanJob{Stop: 1}
}(1024)
for {
select {
case res := <-results:
fmt.Println("扫描到开放的端口:",res.Port) //输出开放的端口号
case <-stopFlag:
e_time := time.Now().Unix()
fmt.Println("本次扫描总共耗时(s):",e_time-s_time)
os.Exit(0)
}
}
}
func worker(id int, jobs <-chan *scanJob, th <-chan time.Time, results chan<- *scanResult) {
for j := range jobs {
if j.Stop != 1 {
sendSyn(j.Laddr, j.Raddr, j.SPort, j.DPort)
} else {
stopFlag <- j.Stop
}
<-th
}
}
func checkError(err error) {
// 错误check
if err != nil {
log.Println(err)
}
}
//CheckSum test
func CheckSum(data []byte, src, dst [4]byte) uint16 {
pseudoHeader := []byte{
src[0], src[1], src[2], src[3],
dst[0], dst[1], dst[2], dst[3],
0,
6,
0,
byte(len(data)),
}
totalLength := len(pseudoHeader) + len(data)
if totalLength%2 != 0 {
totalLength++
}
d := make([]byte, 0, totalLength)
d = append(d, pseudoHeader...)
d = append(d, data...)
return ^mySum(d)
}
func mySum(data []byte) uint16 {
var sum uint32
for i := 0; i < len(data)-1; i += 2 {
sum += uint32(uint16(data[i])<<8 | uint16(data[i+1]))
}
sum = (sum >> 16) + (sum & 0xffff)
sum = sum + (sum >> 16)
return uint16(sum)
}
func sendSyn(laddr, raddr string, sport, dport uint16) {
conn, err := net.Dial("ip4:tcp", raddr)
checkError(err)
defer conn.Close()
op := []TCPOption{
TCPOption{
Kind: 2,
Length: 4,
Data: []byte{0x05, 0xb4},
},
TCPOption{
Kind: 0,
},
}
tcpH := TCPHeader{
SrcPort: sport,
DstPort: dport,
SeqNum: rand.Uint32(),
AckNum: 0,
Flags: 0x8002,
Window: 8192,
ChkSum: 0,
UrgentPointer: 0,
}
buff := new(bytes.Buffer)
err = binary.Write(buff, binary.BigEndian, tcpH)
checkError(err)
for i := range op {
binary.Write(buff, binary.BigEndian, op[i].Kind)
binary.Write(buff, binary.BigEndian, op[i].Length)
binary.Write(buff, binary.BigEndian, op[i].Data)
}
binary.Write(buff, binary.BigEndian, [6]byte{})
data := buff.Bytes()
checkSum := CheckSum(data, ipstr2Bytes(laddr), ipstr2Bytes(raddr))
//fmt.Printf("CheckSum 0x%X\n", checkSum)
tcpH.ChkSum = checkSum
buff = new(bytes.Buffer)
binary.Write(buff, binary.BigEndian, tcpH)
for i := range op {
binary.Write(buff, binary.BigEndian, op[i].Kind)
binary.Write(buff, binary.BigEndian, op[i].Length)
binary.Write(buff, binary.BigEndian, op[i].Data)
}
binary.Write(buff, binary.BigEndian, [6]byte{})
data = buff.Bytes()
//fmt.Printf("% X\n", data)
_, err = conn.Write(data)
checkError(err)
}
func recvSynAck(laddr, raddr string, res chan<- *scanResult) error {
listenAddr, err := net.ResolveIPAddr("ip4", laddr) // 解析域名为ip
checkError(err)
conn, err := net.ListenIP("ip4:tcp", listenAddr)
defer conn.Close()
checkError(err)
for {
buff := make([]byte, 1024)
_, addr, err := conn.ReadFrom(buff)
if err != nil {
continue
}
if addr.String() != raddr || buff[13] != 0x12 {
continue
}
var port uint16
binary.Read(bytes.NewReader(buff), binary.BigEndian, &port)
res <- &scanResult{
Port: port,
Opened: true,
}
}
}
func ipstr2Bytes(addr string) [4]byte {
s := strings.Split(addr, ".")
b0, _ := strconv.Atoi(s[0])
b1, _ := strconv.Atoi(s[1])
b2, _ := strconv.Atoi(s[2])
b3, _ := strconv.Atoi(s[3])
return [4]byte{byte(b0), byte(b1), byte(b2), byte(b3)}
}
func random(min, max int) int {
return rand.Intn(max-min) + min
}
func interfaceAddress(ifaceName string ) string {
iface, err:= net.InterfaceByName(ifaceName)
if err != nil {
panic(err)
}
addr, err := iface.Addrs()
if err != nil {
panic(err)
}
addrStr := strings.Split(addr[0].String(), "/")[0]
return addrStr
}
func portSplit(portRange *string) (uint16, uint16) {
ports := strings.Split(*portRange, "-")
minPort, err := strconv.ParseUint(ports[0], 10, 16)
if err !=nil {
panic(err)
}
maxPort, err := strconv.ParseUint(ports[1], 10, 16)
if err != nil {
panic(err)
}
if minPort > maxPort {
panic(errors.New("minPort must greater than maxPort"))
}
return uint16(minPort), uint16(maxPort)
}
func main() { }

然后利用go自身的build命令,将其打包成.so库:

1
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
2
3
4
5
#! -*- coding:utf-8 -*-
from ctypes import *
lib = cdll.LoadLibrary(u'./scan.so')
lib.Scan("14.215.177.38","1-1024","eth0") # ip,端口范围,网卡

代码运行结果:

说明:相当原生的go,利用python去调用go会损耗一些性能,但总体上还可以。

后记

本文结论就是可以利用go开发扫描模块(性能更佳),并结合python调用。
本文代码项目地址:https://github.com/tengzhangchao/PortScan

参考文章

https://blog.csdn.net/pwc1996/article/details/73469850

本文标题:端口扫描器的几种代码实现方案

文章作者:nMask

发布时间:2018年05月17日 - 18:05

最后更新:2018年05月21日 - 10:05

原始链接:http://thief.one/2018/05/17/1/

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。

nMask wechat
欢迎您扫一扫上面的微信公众号,订阅我的博客!
坚持原创技术分享,您的支持将鼓励我继续创作!