Criando scanner TCP/IP em Golang

higordiego

Higor Diego

Posted on December 29, 2021

Criando scanner TCP/IP em Golang

scanner

Um das ferramentas interessantes a ser utilizada em manutenção e criações de servidores é o scanner map. Esse tipo de ferramenta informa as portas que estão abertas em nossos servidores de forma interna ou externa, e se utilizando desses meios podemos garantir que aquela configuração que foi feita no firewall está como o esperado.

Temos também o nmap que é uma software que realiza validação de porta em redes e servidores. Essa ferramenta é um pouco mais complexa do que se imagina, mas iremos focar em criar uma versão mais simples com o Golang.

Para que possamos começar a desenvolver temos que entender alguns conceitos de protocolo de TCP/IP:
O protocolo de comunicação TCP (Protocolo de Controle de Tramissão) tem a função de ser a camada de transporte em rede de computadores no modelo OSI.
Sendo conhecido também como protocolo TCP/IP, que é um complemento do protocolo de internet (IP). Essa junção dos TCP+IP traz a possibilidade dos aplicativos se comunicarem entre si.

Com isso, é preciso entender sobre o funcionamento desse protocolo, pois através dele temos o handshake, conhecido como “aperto de mão”, que é um processo que envolve duas ou mais maquinas que se conhecem, e estão prontas para uma comunicação. O handshake é utilizado em aplicações como FTP, HTTP, HTTPS, POP3 e etc.

Analisando esse tipo de contrato entre maquinas, temos o seguinte esquema para verificação de serviço disponível:

handshake

De acordo a imagem acima temos um contrato que é divido em três etapas, que são:

  • O cliente envia um pacote chamado SYN para sincronização que contém um número sequencial gerado aleatoriamente;
  • O servidor envia um pacote SYN-ACK. (O ACK significa reconhecimento do SYN, que envia o numero recebido e complementa com um novo numero para retorno);
  • O cliente finaliza com um ACK informando que foi fechado a comunicação entre as partes. E essa comunicação é chamada de full-duplex.

Pensando em um cenário onde o cliente gostaria de se conectar em um servidor na porta 80, caso essa a porta esteja fechada para comunicação TCP o cliente envia o seu sinalizador SYN, e o servidor responder um RST em vez de SYNK-ACK, informando que essa comunicação não teve sucesso.

Segue a ilustração abaixo:

handshake

O RST é um tipo de retorno onde o servidor informa para o cliente que a conexão para ação em porta não poderá ser concluída, e com isso retorna o reset da comunicação. Esse tipo de retorno pode ser avaliado por meio de bloqueios em Firewalls ou porta fechada.
Existem outros tipos de varreduras que podem ser utilizadas para verificação, tais como: XMAS, FIN, TCP ACK e etc. Para esses tipos de scanners podemos utilizar pacotes Gopacket.

Agora que entendemos um pouco sobre o TCP/IP e o handshake podemos iniciar o nosso desenvolvimento enviando uma solicitação para o servidor, e aguardando assim sua resposta, porém se não houver uma resposta, seguiremos a linha de que o firewall bloqueou nossa solicitação, ou a porta está fechada.

Pensando nesse cenário iremos utilizar o fluxo de BPMN abaixo:

handshake

No golang existe um pacote bem simples que realiza esse trabalho para validação de portas e outras utilidades, que se chama net.
E para validação de portas abertas online usaremos um projeto para teste fornecido pelo Nmap security scanner e insecure.org.
Iniciando nossa codificação temos que entender que a quantidade de portas contida no TCP é de 65536.

Segue abaixo a codificação para a resolução:

package main

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

func main() {
    limit := 444
    for i := 1; i < limit; i++ {
        address := fmt.Sprintf("scanme.nmap.org:%d", i)
    // Enviando comando com um tempo limite para espera.
        conn, err := net.DialTimeout("tcp", address, 1*time.Second)
    // Caso exista um error nesse pedido.
    if err != nil {
            //  passe para a próxima porta
            continue
        }
    // fechando conexão aberta
        conn.Close()
    // informando qual porta respondeu o comando
        fmt.Printf("Porta %d aberta\n", i)
    }
}

Enter fullscreen mode Exit fullscreen mode

Caso tenha executado o código acima, imagino que tenha havido uma demora na execução para validação de todas as portas. Isso porque o software verifica as portas uma após a outra, iniciando uma fila bloqueante que depende dessa análise sequencial.

Para solucionar essa problemática podemos utilizar um processos simultâneo, tendo em vista que é um dos pontos fortes do golang, que disponibiliza abordagens para aperfeiçoamento como o goroutine e channels.

A goroutine é uma forma de executar as funções simultâneas de um jeito simples.
Analisando o código do cenário acima podemos encapsular o código do loop em uma goroutine. E com essa abordagem criaremos uma nova problemática que será a inconsistência do resultado, pois todas as goroutines serão executadas sem a sua finalização. Pensando nisso, podemos utilizar os channels para facilitar a comunicação entre as goroutines, e também transferir os dados entre os processos.

Segue abaixo a nova codificação:


package main

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

// Aqui geramos as goroutines para o processo de validação das portas
func generateGoroutines(host string, ports, resultScanner chan int) {
    // percorrendo todas as portas que chegaram no canal
    for p := range ports {
    // padronizando o endereço para o scanner de host + porta.
        address := fmt.Sprintf("%v:%d", host, p)
    // tentando conectar na porta com o protocolo TCP e aguardando um tempo
        conn, err := net.DialTimeout("tcp", address, 1*time.Second)
    // caso tenha um erro
        if err != nil {
    //  passe para a próxima porta e informe que a porta está fechada.
            resultScanner <- 0
            continue
        }

    // fechando a conexão aberta
        conn.Close()

    // retornando a porta que está aberta.
        resultScanner <- p
    }
}

// Enviando para as goroutines cada porta a ser verificada.
func SenderPortsProcess(limitPort int, ports chan int) {
  // percorrendo a quantidade de portas para análise.
    for i := 1; i <= limitPort; i++ {
  // enviando cada porta da lista
        ports <- i
    }
}

// Recebendo todas as portas que estão abertas
func getResultPortsOpen(limitPort int, results chan int) []int {
    var listOpenPorts []int

  // percorrendo todas as portas informadas 
    for i := 0; i < limitPort; i++ {
  // pegando todas as portas que foram validadas
        port := <-results
  // verificando qual porta está aberta
        if port != 0 {
  // adicionando lista de portas abertas
            listOpenPorts = append(listOpenPorts, port)
        }
    }
  // retornando a lista de portas abertas.
    return listOpenPorts
}

func main() {

  // adicionando o host para verificação.
    var host string = "scanme.nmap.org"

  // a quantidade de portas a serem verificadas. 
    var limitPort int = 1024
  // canal com a quantidade de portas.
    ports := make(chan int, limitPort)
  // canal que recebe todos os resultados de portas abertas ou não.
    resultsScan := make(chan int)

  // percorrendo os canais e criando as goroutines para serem executadas.
    for i := 0; i < cap(ports); i++ {
  // criando as goroutines
        go generateGoroutines(host, ports, resultsScan)
    }

  // função responsável para começar a validação das portas.
    go SenderPortsProcess(limitPort, ports)

  // recebendo todos os resultados das portas abertas ou não.
    portsOpens := getResultPortsOpen(limitPort, resultsScan)

  // fechando todos os canais de portas.
    close(ports)
  // fechando todos os canais de resultado do scanner.
    close(resultsScan)

  // informando qual host foi validado.
    fmt.Printf("Endereço verificado - %v\n", host)
  // percorrendo todas as portas que estão abertas.
    for _, port := range portsOpens {
  // informando as portas abertas
        fmt.Printf("%d aberta\n", port)
    }
}

Enter fullscreen mode Exit fullscreen mode

Com o código acima temos uma scanner que executa de forma menos custosa a verificação das portas, usando uma das melhores funcionalidades do golang, a simultaneidade.
Por fim, esse estudo mostra um breve funcionamento do TCP/IP e o handshake, e a base para o desenvolvimento de um programa com intuito de validar portas que estão abertas para o mundo.

Espero ter ajudado, até a próxima...



Referências

https://pt.wikipedia.org/wiki/Handshake

https://www.linkedin.com/pulse/goroutines-e-concorr%C3%AAncia-jefferson-otoni-lima

https://livro.descomplicandokubernetes.com.br/pt/day_two/descomplicando_kubernetes.html#criando-um-service-nodeport

https://medium.com/@KentGruber/building-a-high-performance-port-scanner-with-golang-9976181ec39d

https://odysee.com/@kelvinmai:6/golang-port-scanner:c

https://benoitgoujon.com/post/write-tcp-scanner/

💖 💪 🙅 🚩
higordiego
Higor Diego

Posted on December 29, 2021

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related

Criando scanner TCP/IP em Golang