Python é a língua franca da segurança ofensiva. A maioria das ferramentas modernas é escrita nela, exploits públicos vêm em Python, e quando nenhuma ferramenta existente faz exatamente o que você precisa, você escreve a sua — em minutos. Nesta aula você não vai aprender Python do zero (assumo que você consegue ler lógica básica; se não, o curso tem referências), mas vai aprendê-lo pela ótica ofensiva, construindo algo real: um scanner de portas TCP funcional, usando sockets. Ao final, você entenderá por dentro o que o Nmap faz por fora — e esse é exatamente o princípio "o manual antes da ferramenta".

O socket é o handshake em código

O conceito central é o socket: a abstração de programação que representa um "ponto de conexão" de rede. Criar um socket e tentar conectá-lo a um IP e porta é, na prática, iniciar aquele three-way handshake TCP da aula Protocolos em ação: handshake TCP, DNS e inspeção real. Se a conexão tem sucesso, a porta está aberta; se falha ou expira, está fechada ou filtrada. Toda a varredura de portas se reduz a essa ideia. Veja o núcleo mínimo, testando uma porta:

import socket

alvo = "127.0.0.1"
porta = 80

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)  # AF_INET=IPv4, SOCK_STREAM=TCP
s.settimeout(1)                                         # não espera para sempre
resultado = s.connect_ex((alvo, porta))                # tenta conectar

if resultado == 0:
    print(f"[+] Porta {porta} ABERTA")
else:
    print(f"[-] Porta {porta} fechada/filtrada")
s.close()

O método-chave é o connect_ex: ele tenta o handshake e devolve 0 se a conexão foi estabelecida (porta aberta), ou um código de erro caso contrário. Usamos connect_ex em vez de connect justamente porque ele retorna um código em vez de lançar exceção, o que é mais limpo para varredura. O settimeout(1) é essencial: sem ele, uma porta filtrada faria o script travar esperando indefinidamente.

De uma porta para mil

Agora transformamos isso num scanner de verdade, varrendo um intervalo de portas com um laço. Esta é a versão que você vai efetivamente usar em laboratório:

import socket

def scan_porta(alvo, porta):
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.settimeout(1)
    resultado = s.connect_ex((alvo, porta))
    s.close()
    return resultado == 0   # True se aberta

alvo = input("Alvo: ")
print(f"Escaneando {alvo}...\n")

for porta in range(1, 1025):        # portas bem-conhecidas: 1 a 1024
    if scan_porta(alvo, porta):
        print(f"[+] Porta {porta} ABERTA")

print("\nScan concluído.")

Rode isso contra uma máquina do seu laboratório (jamais contra alvos sem autorização — a regra de ouro de sempre) e ele lista as portas abertas, exatamente como um scanner comercial faria, só que você entende cada linha. Um exercício revelador: rode-o contra sua própria máquina Metasploitable e compare o resultado com o do Nmap; eles devem coincidir, e ver isso funcionar fecha o conceito.

Do funciona ao funciona rápido

Há um problema óbvio: esse scanner é lento, porque testa uma porta de cada vez, esperando o timeout de cada uma em sequência. A solução profissional é o paralelismo — testar muitas portas simultaneamente com threads. Não é preciso dominar concorrência agora, mas veja como a ideia se materializa, porque é o que torna a ferramenta utilizável:

import socket
from concurrent.futures import ThreadPoolExecutor

alvo = "127.0.0.1"
abertas = []

def scan_porta(porta):
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.settimeout(1)
    if s.connect_ex((alvo, porta)) == 0:
        abertas.append(porta)
    s.close()

# dispara até 100 verificações em paralelo
with ThreadPoolExecutor(max_workers=100) as executor:
    executor.map(scan_porta, range(1, 1025))

print("Portas abertas:", sorted(abertas))

Com ThreadPoolExecutor, o mesmo scan que levaria minutos roda em segundos. A diferença de velocidade é dramática, e essa passagem do "funciona" para o "funciona rápido" é uma lição de engenharia que se repete em toda ferramenta ofensiva.

O banner entrega software e versão

Um refinamento que aproxima seu scanner do profissional: além de saber que a porta está aberta, descobrir qual serviço responde nela, capturando o banner — o texto de identificação que muitos serviços enviam ao conectar. É o embrião da enumeração do Bloco VI:

import socket

def pegar_banner(alvo, porta):
    try:
        s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        s.settimeout(2)
        s.connect((alvo, porta))
        banner = s.recv(1024).decode(errors="ignore").strip()  # lê o que o serviço envia
        s.close()
        return banner
    except:
        return None

banner = pegar_banner("127.0.0.1", 22)
if banner:
    print(f"Serviço na porta 22: {banner}")   # ex.: "SSH-2.0-OpenSSH_8.2p1..."

Aquele retorno tipo SSH-2.0-OpenSSH_8.2p1 não é detalhe: a versão do serviço é o que você cruza com bases de vulnerabilidades para descobrir se há um exploit conhecido (aula CVE, CVSS e CWE: a linguagem das vulnerabilidades). Um banner entregou, de graça, o software e a versão exata do alvo.

A ferramenta que parecia mágica

O que espero que você leve desta aula vai além do scanner. É a percepção de que as ferramentas que parecem mágicas são, por dentro, lógica que você é capaz de escrever. Nmap é este scanner com anos de refinamento, milhares de casos tratados e otimizações; mas o coração — abrir sockets, testar conexões, ler banners — é o que você acabou de construir. Essa desmistificação é libertadora: quando uma ferramenta pronta falhar ou não existir para o seu caso, você não trava, você codifica a solução. Do lado defensivo, entender scanning por dentro é entender o que aparece nos logs quando alguém varre sua rede — conexões parciais em rajada, múltiplas portas de uma mesma origem —, o que alimenta as detecções do Blue Team.

Fontes e leituras recomendadas

Exercícios

  1. O que é um socket, e como tentar conectá-lo a uma porta se relaciona com o handshake TCP da aula Protocolos em ação: handshake TCP, DNS e inspeção real?

    ✓ Resposta:

    Um socket é a abstração que representa um ponto de conexão de rede. Tentar conectá-lo a um IP e porta inicia, na prática, o three-way handshake do TCP; se o handshake se completa, a porta está aberta.

  2. Por que o código usa connect_ex em vez de connect, e o que o retorno 0 significa?

    ✓ Resposta:

    connect_ex retorna um código de erro em vez de lançar uma exceção, o que é mais limpo para varredura. O retorno 0 significa que a conexão foi estabelecida com sucesso — porta aberta.

  3. Qual o papel do settimeout, e o que aconteceria ao scanner sem ele?

    ✓ Resposta:

    O settimeout limita quanto tempo o socket espera por resposta. Sem ele, o scanner travaria indefinidamente ao encontrar uma porta filtrada que não responde.

  4. Que problema o uso de threads (ThreadPoolExecutor) resolve num scanner de portas?

    ✓ Resposta:

    Resolve a lentidão: sem threads, as portas são testadas em sequência, cada uma esperando seu timeout; com threads, muitas são testadas em paralelo, reduzindo o tempo de minutos para segundos.

  5. O que é "capturar o banner" de um serviço, e por que a versão obtida é valiosa para o pentester?

    ✓ Resposta:

    É ler o texto de identificação que o serviço envia ao conectar (ex.: SSH-2.0-OpenSSH_8.2p1). A versão é valiosa porque pode ser cruzada com bases de vulnerabilidades para identificar exploits conhecidos aplicáveis àquele software.