tcp端口扫描器

  1. 并发tcp全端口扫描

并发tcp全端口扫描

​ Go语言是原生支持并发的语言,它的并发是通过协程实现的。这里介绍了两个版本的支持并发的TCP全连接端口扫描器

​ 1 生成扫描任务列表:首先解析出需要扫描的IP与端口的切片,然后将需要扫描的IP与端口列表放入一个[]map[string]int中,map的key为IP地址,value为端口,

[]map[string]int表示所有需要扫描的IP与端口对的切片。

1
2
3
4
5
6
7
8
9
10
11
12
func GenerateTask(ipList []net.IP, ports []int) ([]map[string]int, int) {
tasks := make([]map[string]int, 0)

for _, ip := range ipList {
for _, port := range ports {
ipPort := map[string]int{ip.String(): port}
tasks = append(tasks, ipPort)
}
}

return tasks, len(tasks)
}

​ 2.分割扫描任务:根据并发数将需要扫描的[]map[string]int切片分割成组,以便按组进行并发扫描。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

func AssigningTasks(tasks []map[string]int) {
scanBatch := len(tasks) / vars.ThreadNum

for i := 0; i < scanBatch; i++ {
curTask := tasks[vars.ThreadNum*i : vars.ThreadNum*(i+1)]
RunTask(curTask)
}

if len(tasks)%vars.ThreadNum > 0 {
lastTasks := tasks[vars.ThreadNum*scanBatch:]
RunTask(lastTasks)

}
}

​ 3 按组执行扫描任务:分别将每组扫描任务传入具体的扫描任务中,扫描任务函数利用sync.WaitGroup实现并发扫描,在扫描的过程中将结果保存到一个并发安全的map中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
func RunTask(tasks []map[string]int) {
var wg sync.WaitGroup
wg.Add(len(tasks))
// 每次创建len(tasks)个goroutine,每个goroutine只处理一个ip:port对的检测
for _, task := range tasks {
for ip, port := range task {
go func(ip string, port int) {
err := SaveResult(Connect(ip, port))
_ = err
wg.Done()
}(ip, port)
}
}
wg.Wait()
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

func SaveResult(ip string, port int, err error) error {
// fmt.Printf("ip: %v, port: %v,err: %v, goruntineNum: %v\n", ip, port, err, runtime.NumGoroutine())
if err != nil {
return err
}

v, ok := vars.Result.Load(ip)
if ok {
ports, ok1 := v.([]int)
if ok1 {
ports = append(ports, port)
vars.Result.Store(ip, ports)
}
} else {
ports := make([]int, 0)
ports = append(ports, port)
vars.Result.Store(ip, ports)
}
return err
}

4 展示扫描结果:所有扫描任务完成后,输出保存在并发安全map中的扫描结果。

1
2
3
4
5
6
7
8
9
10

func PrintResult() {
vars.Result.Range(func(key, value interface{}) bool {
fmt.Printf("ip:%v\n", key)
fmt.Printf("ports: %v\n", value)
fmt.Println(strings.Repeat("-", 100))
return true
})
}

以上4步全部完成后,在main函数中分别调用任务生成、任务分配与结果展示的函数即可,代码片断如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
func main() {
if len(os.Args) == 3 {
ipList := os.Args[1]
portList := os.Args[2]
ips, err := util.GetIpList(ipList)
ports, err := util.GetPorts(portList)
_ = err

task, _ := scanner.GenerateTask(ips, ports)
scanner.AssigningTasks(task)
scanner.PrintResult()

} else {
fmt.Printf("%v iplist port\n", os.Args[0])
}
}

func init() {
runtime.GOMAXPROCS(runtime.NumCPU())
}

​ 接下来用新实现的并发端口扫描器tcp-connect-scanner1与Nmap分别执行一遍刚才的任务

​ 这个扫描器虽然已经实现了并发扫描,但对协程的控制不够精细,每组扫描任务都会瞬间启动大量的协程,然后逐渐关闭,而不是一个平滑的过程。这种方法可能会瞬间将服务器的CPU占满,为了解决此问题,在tcp-connect-scanner2中使用sync.WaitGroup与channel配合实现了新的并发方式,代码片断如下所示

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
func RunTask(tasks []map[string]int) {
wg := &sync.WaitGroup{}

// 创建一个buffer为vars.threadNum * 2的channel
taskChan := make(chan map[string]int, vars.ThreadNum*2)

// 创建vars.ThreadNum个协程
for i := 0; i < vars.ThreadNum; i++ {
go Scan(taskChan, wg)
}

// 生产者,不断地往taskChan channel发送数据,直接channel阻塞
for _, task := range tasks {
wg.Add(1)
taskChan <- task
}

close(taskChan)
wg.Wait()
}

func Scan(taskChan chan map[string]int, wg *sync.WaitGroup) {
// 每个协程都从channel中读取数据后开始扫描并入库
for task := range taskChan {
for ip, port := range task {
err := SaveResult(Connect(ip, port))
_ = err
wg.Done()
}
}
}

转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 jaytp@qq.com

💰

×

Help us with donation