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


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

  • 相关阅读:
    第二章 Flask——Flask中的request
    第一章 Flask——Flask简介
    第四章 Linux——Nginx环境部署指南
    众测平台
    jmeter进阶
    adb命令对app进行测试
    众测平台
    selenium
    java基础知识
    接口测试工具对比
  • 原文地址:https://www.cnblogs.com/darecy/p/13356787.html
Copyright © 2020-2023  润新知