当前位置: 首页 > 科技观察

手把手教你用Go语言搭建一个简单的TCP端口扫描器

时间:2023-03-16 00:57:26 科技观察

前言大家好,我是码农,周五。这一次,让我们实现一个简单的TCP端口扫描器!也来体验一下黑客的风采吧!TCP扫描的本质当我们使用TCP进行连接时,我们需要知道对方机器的ip:port。如果正常握手连接成功,流程如下。连接失败可能是正常的,也可能是失败的。如果连接被连接方关闭,流程如下。如果有防火墙,还有一种可能是端口是开放的,但是被防火墙屏蔽了,过程如下。理解了代码的本质之后,就可以开始写代码了。在Go中,我们通常使用net.Dial进行TCP连接。它在两种情况下成功:返回conn。失败:错误!=无。相对来说,一开始我们可能不会太大胆,都是先写原型,不考虑性能。代码packagemainimport("fmt""net")funcmain(){varip="192.168.43.34"fori:=21;i<=120;i++{varaddress=fmt.Sprintf("%s:%d",ip,i)conn,err:=net.Dial("tcp",address)iferr!=nil{fmt.Println(address,"isclosed")continue}conn.Close()fmt.Println(address,"open")}}执行结果但是这个过程很慢。因为如果net.Dial连接的是一个未开放的端口,一个端口可能是20s+,那为什么要学多线程就明白了!!!上面的多线程版本通过一个循环一个接一个接ip:ports,然后我们就知道了,让多人在一个接的位置上做。所以,多线程如下。代码packagemainimport("fmt""net""sync""time")funcmain(){varbegin=time.Now()//wgvarwgsync.WaitGroup//ipvarip="192.168.99.112"//varip="192.168.43.34"//loopforj:=21;j<=65535;j++{//addwgwg.Add(1)gofunc(iint){//releasewgdeferwg.Done()varaddress=fmt.Sprintf("%s:%d",ip,i)//conn,err:=net.DialTimeout("tcp",address,time.Second*10)conn,err:=net.Dial("tcp",address)iferr!=nil{//fmt.Println(address,"isclosed",err)return}conn.Close()fmt.Println(address,"open")}(j)}//等待wgwg.Wait()varelapseTime=time.Now().Sub(begin)fmt.Println("Time-consuming:",elapseTime)}执行结果其实是同时开启6W多个线程扫描每个ip:port。所以最长线程结束的时间就是程序结束的时间。感觉还行,20s+扫描了6000多个端口!!!在线程池版本中,我们通过简单粗暴的方式为每个ip:port创建了一个协程。虽然在Go中,理论上开个几十万个协程是没有问题的,但是还是有一定的压力。因此,我们应该采用一种相对经济的方式来精简代码,一般采用线程池的方式。本次使用的线程池包:gohive地址:https://github.com/loveleshsharma/gohive简单介绍代码包main//线程池方法import("fmt""github.com/loveleshsharma/gohive""net""sync""time")//wgvarwgsync.WaitGroup//地址管道,100容量varaddressChan=make(chanstring,100)//Workerfuncworker(){//函数结束释放连接deferwg.Done()for{address,ok:=<-addressChanif!ok{break}//fmt.Println("address:",address)conn,err:=net.Dial("tcp",address)//conn,err:=net.DialTimeout("tcp",address,10)iferr!=nil{//fmt.Println("close:",address,err)continue}conn.Close()fmt.Println("open:",address)}}funcmain(){varbegin=time.Now()//ipvarip="192.168.99.112"//线程池大小varpool_size=70000varpool=gohive.NewFixedSizePool(pool_size)//拼接ip:port//启动一个线程生成ip:port,并存储在地址管道中gofunc(){forport:=1;port<=65535;port++{varaddress=fmt.Sprintf("%s:%d",ip,port)//向地址管道添加地址//fmt.Println("<-:",广告dress)addressChan<-address}//发送关闭addressChan管道close(addressChan)}()//启动pool_sizeworker,处理每个addressChan类型的地址forwork:=0;work