暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

工具|Golang安全开发之并发TCP扫描器的实现

灼剑安全团队 2021-08-31
475

点击上方蓝字关注我们

0x01 前言

由于Golang有天生的并发优势,所以一直想写一篇能够体现golang并发优势而又比较基础的文章,于是决定使用Golang实现一个简单的并发tcp端口扫描器。准备写一个系列,从单线程单ip多端口一直到多线程多ip多端口,从0开始一个一个例子进行对比,使读者对golang并发编程能有一个较为清晰的认识。

0x02 单线程tcp扫描器实现

    package main


    import (
    "fmt"
    "net"
    "time"
    )


    func main() {
    start := time.Now()
    for i := 21; i <= 80; i++ {
    start1 := time.Now()
    address := fmt.Sprintf("1.1.1.1:%d", i)
    conn, err := net.Dial("tcp", address)
    if err != nil {
    fmt.Printf("%d 端口关闭了!\n", i)
    fmt.Printf("扫描端口%d,共用了%2f秒\n", i, time.Since(start1).Seconds())
    continue
    }
    conn.Close()
    fmt.Printf("%d 端口开放了!\n", i)
    fmt.Printf("扫描端口%d,共用了%2f秒\n", i, time.Since(start1).Seconds())
    }
    fmt.Printf("共用了%2f秒", time.Since(start).Seconds())
    }

    这段代码中,首先创建一个for循环来遍历我们想要扫描的端口,然后使用net.Dial函数来对该端口进行tcp连接尝试,如果tcp建立连接失败,则说明该端口不开放。反之则开放了该端口,然后关闭连接。

    但是这样的效率是非常的慢的,首先是因为net.Dial函数的问题,该函数使用系统默认的tcp超时时间,而如果如果你是mac用户,你会发现扫描一个不开放的端口需要的时间竟然长达75秒。

    那是因为在mac中,tcp连接超时默认时间为75秒,我们可以使用命令sysctl net.inet.tcp来查看本地的tcp配置信息。(windows中的tcp超时时间为21妙)

    所以在我们使用macos进行编程的时候,最好将net.Dial()函数替换为net.DialTimeout()函数

    该函数的第三个参数可以传入一个timeout值,我们可以利用该函数达到修改tcp超时等待时间的目的。所以在进行tcp连接的时候代码应该使用以下方式:

      conn, err := net.DialTimeout("tcp", address, 15*time.Second)

      这时候我们再看扫描结果,每个端口只需要等待15秒了。

      0x03 并发tcp端口扫描器

      刚才使用单线程的扫描器,扫描时间为每个端口扫描时间之和。这样一来没有效率,二来更体现不出golang并发的优势。所以我们需要修改代码,将代码修改成以下格式:

        package main


        import (
        "fmt"
        "net"
        "sync"
        "time"
        )


        func main() {
        start := time.Now()
        var wg sync.WaitGroup
        for i := 21; i <= 120; i++ {
        wg.Add(1)
        go func(j int) {
        defer wg.Done()
        address := fmt.Sprintf("1.1.1.1:%d", j)
        conn, err := net.DialTimeout("tcp", address, 15*time.Second)
        if err != nil {
        fmt.Printf("%d 该端口关闭了!\n", j)
        return
        }
        conn.Close()
        fmt.Printf("%d 该端口打开了!\n", j)
        }(i)
        }
        wg.Wait()
        fmt.Printf("共用了%2f秒\n", time.Since(start).Seconds())
        }

        1、我们将刚才建立连接处的代码放入goroutine,然后在循环之中创建goroutine,循环多少次我们就创建多少个goroutine,这样达到并发的效果。

        2、使用sync包中的waitgroup来同步等待所有goroutine完成,因为所有goroutine和main函数都是同步工作的,但是main函数结束之后,程序就终止了,那么所有的goroutine也就终止了。所以我们需要使用waitgroup来阻塞main函数等待我们的所有goroutine完成。我们在vscode中按住control+鼠标左键来查看函数的说明:

        3、在每一次for循环中,wg都会add,代表增加了一个goroutine。而goroutine运行完毕之后,都会defer一个wg.Done(),代表需要等待的goroutine减少一个。这样main函数之中的wg.Wait()函数就会一直阻塞到所有的goroutine都运行完毕之后。

        如果我们不使用sync包中的waitGroup,那么程序没有阻塞等待goroutine的执行就会一下子终止,效果如下:

        而正确的效果应该如下:

        0x04 总结

        至此我们算是简单的完成了并发的tcp扫描器,但是这样并不优雅。比如我们想要扫描1-65535个端口,这样就会开启65535个goroutine,这样会导致我们的资源突然占用率飙升;并且我们现在只能扫描单个ip对应多个端口,如果想要扫描多个端口对应多个ip,请看下篇文章。


        ☆ END ☆



        文章转载自灼剑安全团队,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

        评论