欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

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

程序员文章站 2022-05-15 22:10:16
...
这雨夜太漫长 失眠的我 在谁梦里 歌唱

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

快来看代码:

12345678910111213141516171819202122232425262728293031
#! -*- coding:utf-8 -*-import timeimport socketsocket_timeout = 0.1def 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)passexcept Exception,e:print es.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编程中相对少用,资料也不多。

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141
# -*- coding: UTF-8 -*-import timeimport randomimport socketimport sysfrom struct import *'''Warning:must run it as rootyum install python-devel libpcap-develpip install pcap'''def checksum(msg):''' Check Summing '''s = 0for i in range(0,len(msg),2):w = (ord(msg[i]) << 8) + (ord(msg[i+1]))s = s+ws = (s>>16) + (s & 0xffff)s = ~s & 0xffffreturn sdef 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 sdef CreateIpHeader(source_ip, dest_ip):''' create ip header '''# packet = ''# ip header optionheaderlen = 5version = 4tos = 0tot_len = 20 + 20id = random.randrange(18000,65535,1)frag_off = 0ttl = 255protocol = socket.IPPROTO_TCPcheck = 10saddr = socket.inet_aton ( source_ip )daddr = socket.inet_aton ( dest_ip )hl_version = (version << 4) + headerlenip_header = pack('!BBHHHBBH4s4s', hl_version, tos, tot_len, id, frag_off, ttl, protocol, check, saddr, daddr)return ip_headerdef 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_portseq = 0ack_seq = 0doff = 5''' tcp flags '''fin = 0syn = 1rst = 0psh = 0ack = 0urg = 0window = socket.htons (8192)    # max windows sizecheck = 0urg_ptr = 0offset_res = (doff << 4) + 0tcp_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 = 0protocol = socket.IPPROTO_TCPtcp_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_headerdef 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_headers.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)>>2tcp_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 = '' # 填写本机ipdest_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)

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

12
yum install python-devel libpcap-develpip install pcap

运行结果:
端口扫描器的几种代码实现方案

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

scan for Python scapy

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

tcp syn csan

代码在这里:

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374
#! -*- coding:utf-8 -*-import timefrom scapy.all import *ip = "14.215.177.38"TIMEOUT = 0.5threads = 500port_range = 1024retry = 1def is_up(ip):""" Tests if host is up """icmp = IP(dst=ip)/ICMP()resp = sr1(icmp, timeout=TIMEOUT)if resp == None:return Falseelse:return Truedef reset_half_open(ip, ports):# Reset the connection to stop half-open connections from pooling upsr(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 packetanswers, un_answered = sr(p, verbose=False, retry=retry ,timeout=TIMEOUT) # Send the packetsfor req, resp in answers:if not resp.haslayer(TCP):continuetcp_layer = resp.getlayer(TCP)if tcp_layer.flags == 0x12:# port is opento_reset.append(tcp_layer.sport)results.append(tcp_layer.sport)elif tcp_layer.flags == 0x14:# port is openpassreset_half_open(ip, to_reset)return resultsdef 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 += resultsend_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,看代码:

123456789101112131415161718192021222324252627
#! -*- coding:utf-8 -*-'''pip install python-nmap'''import nmapnm =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

直接看代码吧:

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121
package main// port tcp scanimport ("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 intmux   sync.Mutex}var scanCount ScanSafeCountfunc 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) // 根据域名查找ipif 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 - 1if scanCount.count <= 0 {close(outport)}scanCount.mux.Unlock()}func main() {runtime.GOMAXPROCS(runtime.NumCPU()) // 设置最大可使用的cpu核数// 定义变量inport := make(chan int) // 信号变量,类似python中的queueoutport := 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]) // 获取参数中的ipstartport, _ := 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 {// 开始循环inputgo 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

看代码看代码:

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291
package main// port tcp syn scanimport ("bytes""encoding/binary""flag""fmt""log""math/rand""net""os""strconv""strings""time""errors")//TCPHeader testtype TCPHeader struct {SrcPort       uint16DstPort       uint16SeqNum        uint32AckNum        uint32Flags         uint16Window        uint16ChkSum        uint16UrgentPointer uint16}//TCPOption testtype TCPOption struct {Kind   uint8Length uint8Data   []byte}type scanResult struct {Port   uint16Opened bool}type scanJob struct {Laddr stringRaddr stringSPort uint16DPort uint16Stop  uint8}var stopFlag = make(chan uint8, 1)func main() {rate := time.Second / 400throttle := 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 := *remoteminPort , 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) {// 错误checkif err != nil {log.Println(err)}}//CheckSum testfunc 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 uint32for 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 = checkSumbuff = 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) // 解析域名为ipcheckError(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 uint16binary.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代码:

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299
package main// port tcp syn scanimport ("C""os""bytes""encoding/binary""fmt""log""math/rand""net""strconv""strings""time""errors")//TCPHeader testtype TCPHeader struct {SrcPort       uint16DstPort       uint16SeqNum        uint32AckNum        uint32Flags         uint16Window        uint16ChkSum        uint16UrgentPointer uint16}//TCPOption testtype TCPOption struct {Kind   uint8Length uint8Data   []byte}type scanResult struct {Port   uint16Opened bool}type scanJob struct {Laddr stringRaddr stringSPort uint16DPort uint16Stop  uint8}var stopFlag = make(chan uint8, 1)//export Scanfunc Scan(remote_ *C.char, portRange_ *C.char, interfaceName_ *C.char) {rate := time.Second / 400throttle := 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_1remote := &remote_1portRange := &portRange_1s_time := time.Now().Unix()laddr := interfaceAddress(*ifaceName) //raddr := *remoteminPort , 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) {// 错误checkif err != nil {log.Println(err)}}//CheckSum testfunc 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 uint32for 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 = checkSumbuff = 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) // 解析域名为ipcheckError(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 uint16binary.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文件。

12345
#! -*- 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