GoLang脚本开发使用Go编写一个简单的TCP端口扫描器
Natro92最近在 b 站上刷到了一个用 go 做工具的视频,正巧之前下到过相关的电子书,这里正好学习学习。
基础学习
测试端口可用性
正常 nmap 扫描测试 1-1024 端口:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| nmap -p 1-1024 scanme.nmap.org Starting Nmap 7.94 ( https: Nmap scan report for scanme.nmap.org (45.33.32.156) Host is up (0.22s latency). Not shown: 1016 closed tcp ports (reset) PORT STATE SERVICE 22/tcp open ssh 79/tcp filtered finger 80/tcp open http 111/tcp filtered rpcbind 135/tcp filtered msrpc 137/tcp filtered netbios-ns 139/tcp filtered netbios-ssn 445/tcp filtered microsoft-ds
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| package main
import ( "fmt" "net" )
func main() { for i := 1; i <= 1024; i++ { address := fmt.Sprintf("scanme.nmap.org:%d", i) conn, err := net.Dial("tcp", address) fmt.Println("Trying to connect to ", address) if err != nil { continue } err1 := conn.Close() if err1 != nil { return } fmt.Printf("port %d is opened\n", i) } }
|
由于网络等等原因,这需要不少时间,大概 1min 才能从 0 跑到 22 端口。
但这样是可以得到预期的结果的。
执行非并发扫描
如果按照简单的思路使用 Goroutine 来执行匿名函数:
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
| package main
import ( "fmt" "net" "sync" )
func main() { for i := 1; i <= 1024; i++ { go func(j int) { address := fmt.Sprintf("scanme.nmap.org:%d", i) conn, err := net.Dial("tcp", address) fmt.Println("Trying to connect to ", address) if err != nil { return } err1 := conn.Close() if err1 != nil { return } fmt.Printf("port %d is opened\n", i) }(i) } }
|
但这样只会立即退出,为每个连接都创建一个 goroutine,主 goroutine 不会等待连接发生,这个是不是很眼熟,如果你在 python 的某些情况下这样写的话,也会出现这种情况。然后 for 循环立即退出。
这里可以使用 sync 包的WaitGroup
方法,来控制并发线程的安全,通过调用计数器,来保证程序不会在完成之前退出。
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
| package main
import ( "fmt" "net" "sync" )
func main() { var wg sync.WaitGroup for i := 1; i <= 1024; i++ { wg.Add(1) go func(j int) { defer wg.Done() address := fmt.Sprintf("scanme.nmap.org:%d", i) conn, err := net.Dial("tcp", address) fmt.Println("Trying to connect to ", address) if err != nil { return } err1 := conn.Close() if err1 != nil { return } fmt.Printf("port %d is opened\n", i) }(i) } wg.Wait() }
|
但是似乎这个结果还是有问题。只能扫出来部分正确结果,而且有误报。
使用工人池
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
| package main
import ( "fmt" "sync" )
func worker(ports chan int, wg *sync.WaitGroup) { for p := range ports { fmt.Println(p) wg.Done() } }
func main() { ports := make(chan int, 100) var wg sync.WaitGroup for i := 0; i < cap(ports); i++ { go worker(ports, &wg) } for i := 1; i <= 1024; i++ { wg.Add(1) ports <- i } wg.Wait() close(ports) }
|
之前好久没用 go 了,这里简单介绍下基础代码。但是仅仅使用一个通道会导致扫描结果是乱序,可以通过多通道来将打印之前的结果排序,这样也不再需要 WaitGroup,通过对传输结果的计数就可以判断是否完成。
但实际上这并没有显示出我的全部结果,因此后面还需要再进行修改。
简单完善
后面我添加了解析命令行输入等基础操作,做成了一个简单的 TCP 扫描 demo,但是速度太慢了,扫几个端口就要 20 秒。
后面再完善完善放个 demo,陆续完善下这篇文章。