◇ OpenCyber Academy
Técnico · Capítulo 2 · Português

Capítulo 2 — Redes e protocolos

Ensino gratuito e aberto para quem deseja proteger pessoas e tecnologias. Não é preciso criar uma conta para estudar ou consultar as respostas.

Capítulo 2 — Redes e protocolos

Mapa da aula

Aula 02 publicada. Mapa, exemplo, EX005.

flowchart TD
  N0["2.1 Protocolos industriais e confiança"]
  N1["2.2 O modelo de dados do Modbus"]
  N2["2.3 O cabeçalho MBAP"]
  N3["2.5 Atividade prática"]
  N4["Síntese do capítulo"]
  N0 --> N1 --> N2 --> N3 --> N4
  N4 --> EX["Exemplo resolvido"]
  EX --> Q["Exercício EX005"]

Desenho lógico

flowchart LR
  N0["2.1 Protocolos industriais e confiança"]
  N1["2.2 O modelo de dados do Modbus"]
  N2["2.3 O cabeçalho MBAP"]
  N3["2.5 Atividade prática"]
  N4["Síntese do capítulo"]
  N0 --> N1 --> N2 --> N3 --> N4 --> Q["EX005"]
[2.1 Protocolos industriais e confiança]
    |
    v
[2.2 O modelo de dados do Modbus]
    |
    v
[2.3 O cabeçalho MBAP]
    |
    v
[2.5 Atividade prática]
    |
    v
[Síntese do capítulo]
    |
    v
[Exemplo resolvido] --> [Exercício EX005]

Objetivos de aprendizagem e conexão

Ao final deste capítulo, você deverá ser capaz de:

O Capítulo 1 desenhou os caminhos. Este capítulo abre os pacotes dos caminhos de controle e de monitoramento, para que os capítulos seguintes possam filtrá-los, verificá-los e detectá-los.

2.1 Protocolos industriais e confiança

Protocolos como o Modbus e o DNP3 foram projetados para redes seriais fechadas, em que se presumia que tudo o que estivesse no fio pertencia a elas. Suas versões TCP/IP mantiveram esse pressuposto: um dispositivo Modbus/TCP clássico aceita uma escrita válida de qualquer host que consiga abrir uma conexão TCP com a porta 502. Existem variantes seguras — a Modbus Organization publica uma especificação Modbus/TCP Security que usa TLS (Modbus specifications), e o DNP3 tem o Secure Authentication (DNP Users Group overview) —, mas os dispositivos instalados muitas vezes não as suportam. Até que suportem, alcançabilidade é autorização, e os controles de rede dos Capítulos 10 e 11 são a principal defesa.

2.2 O modelo de dados do Modbus

Um dispositivo Modbus expõe quatro tabelas: coils (um bit, leitura e escrita), entradas discretas (um bit, somente leitura), registradores de entrada (16 bits, somente leitura) e registradores de retenção (holding registers; 16 bits, leitura e escrita). O que um registrador significa — nível em centímetros, um setpoint de bomba, um sinalizador de modo — é definido pela configuração do dispositivo e documentado num mapa de registradores. O protocolo em si carrega números, não significados; assim, um decodificador pode lhe dizer "escrita no registrador de retenção 7", mas só o mapa de registradores lhe diz que o registrador 7 é o setpoint da bomba 2.

Os códigos de função públicos comuns são 1 (ler coils), 2 (ler entradas discretas), 3 (ler registradores de retenção), 4 (ler registradores de entrada), 5 (escrever um coil), 6 (escrever um registrador), 15 (escrever vários coils), 16 (escrever vários registradores), 8 (diagnóstico) e 43/14 (ler a identificação do dispositivo). Os códigos de função são definidos na especificação Modbus Application Protocol publicada pela Modbus Organization (Modbus specifications).

2.3 O cabeçalho MBAP

Sobre TCP, cada requisição Modbus começa com um cabeçalho MBAP de 7 bytes, seguido da unidade de dados do protocolo (PDU):

Bytes Campo Observações
0–1 Identificador de transação Associa uma resposta à sua requisição.
2–3 Identificador de protocolo Sempre 0 para o Modbus.
4–5 Comprimento Número de bytes que seguem este campo (id de unidade + PDU).
6 Identificador de unidade Endereça um dispositivo atrás de um gateway; muitas vezes 1 ou 255.
7 Código de função O que fazer.
8– Dados Para os códigos 1–6: um endereço de 2 bytes e uma quantidade ou valor de 2 bytes.

Todos os campos de vários bytes são big-endian. O módulo struct do Python os decodifica com o formato >HHHBB (Python struct). O quadro 00 03 00 00 00 06 01 06 00 07 01 2c se lê, portanto: transação 3, protocolo 0, seguem seis bytes, unidade 1, função 6 (escrever um registrador), registrador 7, valor 0x012C = 300.

Duas verificações de consistência pegam a maior parte do tráfego malformado ou não Modbus na porta 502: o identificador de protocolo deve ser 0, e o campo de comprimento deve ser igual ao número de bytes depois dele. Uma ferramenta de detecção que pula essas verificações vai ler lixo como se fossem comandos.

2.4 Exemplo resolvido: uma captura da KW-04 (sintético)

Um técnico exportou oito requisições enviadas à RTU da KW-04 (10.10.4.2) durante uma manhã de manutenção. A política do KVROC diz que só a estação de engenharia eng-ws-15 (10.20.0.15) pode escrever nos controladores das estações; o scada-01 (10.20.0.10) apenas lê.

Laboratório 2 (salve como lab02.py e execute com python3 lab02.py):

# Lab 2 - decode Modbus/TCP frames and flag writes (offline, standard library only)
# Synthetic capture from Kestrel Water station KW-04; addresses are private (RFC 1918).
import struct
ENGINEERING = {"10.20.0.15"}          # only the engineering workstation may write
WRITE_FC = {5: "write single coil", 6: "write single register",
            15: "write multiple coils", 16: "write multiple registers"}
READ_FC = {1: "read coils", 2: "read discrete inputs", 3: "read holding registers", 4: "read input registers"}
capture = [  # (source, destination, frame as hex)
    ("10.20.0.10", "10.10.4.2", "000100000006010300000004"),
    ("10.20.0.10", "10.10.4.2", "000200000006010400100002"),
    ("10.20.0.15", "10.10.4.2", "00030000000601060007012c"),
    ("10.30.3.7",  "10.10.4.2", "000400000006010500a0ff00"),
    ("10.30.3.7",  "10.10.4.2", "00050000000b011000070002040064000a"),
    ("10.20.0.10", "10.10.4.2", "0006000000060103"),
    ("10.20.0.10", "10.10.4.2", "000700010006010300000004"),
    ("10.20.0.10", "10.10.4.2", "000800000006012b0e010000"),
]
def decode(hexframe):
    b = bytes.fromhex(hexframe)
    if len(b) < 8: return None, "short frame (< 8 bytes)"
    tid, pid, length, unit, fc = struct.unpack(">HHHBB", b[:8])
    if pid != 0: return None, f"protocol id {pid} is not 0 (not Modbus)"
    if length != len(b) - 6: return None, f"length field {length} but {len(b) - 6} bytes follow"
    addr, qty = struct.unpack(">HH", b[8:12]) if len(b) >= 12 else (None, None)
    return (tid, unit, fc, addr, qty), None
for src, dst, h in capture:
    d, err = decode(h)
    if err: print(f"{src:<11} -> {dst}  MALFORMED  {err}"); continue
    tid, unit, fc, addr, qty = d
    if fc in WRITE_FC:
        verdict = "ok (engineering)" if src in ENGINEERING else "ALERT unauthorized write"
        print(f"{src:<11} -> {dst}  tid={tid} unit={unit} fc={fc:<2} {WRITE_FC[fc]:<24} addr={addr} value/qty={qty}  {verdict}")
    elif fc in READ_FC:
        print(f"{src:<11} -> {dst}  tid={tid} unit={unit} fc={fc:<2} {READ_FC[fc]:<24} addr={addr} qty={qty}  ok")
    else:
        print(f"{src:<11} -> {dst}  tid={tid} unit={unit} fc={fc:<2} {'other function':<24}  REVIEW (not on read/write list)")

Saída esperada (Python 3.13.5):

10.20.0.10  -> 10.10.4.2  tid=1 unit=1 fc=3  read holding registers   addr=0 qty=4  ok
10.20.0.10  -> 10.10.4.2  tid=2 unit=1 fc=4  read input registers     addr=16 qty=2  ok
10.20.0.15  -> 10.10.4.2  tid=3 unit=1 fc=6  write single register    addr=7 value/qty=300  ok (engineering)
10.30.3.7   -> 10.10.4.2  tid=4 unit=1 fc=5  write single coil        addr=160 value/qty=65280  ALERT unauthorized write
10.30.3.7   -> 10.10.4.2  tid=5 unit=1 fc=16 write multiple registers addr=7 value/qty=2  ALERT unauthorized write
10.20.0.10  -> 10.10.4.2  MALFORMED  length field 6 but 2 bytes follow
10.20.0.10  -> 10.10.4.2  MALFORMED  protocol id 1 is not 0 (not Modbus)
10.20.0.10  -> 10.10.4.2  tid=8 unit=1 fc=43 other function            REVIEW (not on read/write list)

Lendo a saída linha a linha: as leituras do SCADA são normais; a estação de engenharia escreveu o valor 300 no registrador 7 — esperado, se houver um chamado de mudança; uma mesa corporativa (10.30.3.7) escreveu ON no coil 160 (0xFF00 = 65280) e depois escreveu dois registradores a partir do 7. Essas duas linhas são a constatação da manhã: um host da zona corporativa consegue alcançar um controlador de estação e alterá-lo. Dois quadros estão malformados — um truncado, outro com identificador de protocolo diferente de zero —, e a função 43 (identificação do dispositivo) não é nem uma leitura de dados do processo nem uma escrita, por isso é marcada para revisão: uma consulta de identificação vinda de um servidor SCADA conhecido pode ser um recurso do fornecedor; a mesma consulta vinda de um host desconhecido é reconhecimento clássico (o MITRE lista a descoberta de informações de sistemas remotos entre as técnicas de ICS: ATT&CK for ICS).

2.5 Atividade prática

Decodifique à mão os três quadros abaixo, depois acrescente-os ao Laboratório 2 e confirme: 000900000006010100000010, 000a0000000601050003 0000 (remova o espaço), 000b00000006020300640001. Anote a função, o endereço e a quantidade ou o valor de cada um, e se o identificador de unidade é o que você espera para a KW-04.

O segundo quadro traz a função 05, uma escrita de bobina única, então registre se o servidor SCADA deveria algum dia enviá-la à KW-04. Se um quadro tiver um espaço no hexadecimal, remova-o antes de colar; os espaços não fazem parte da captura.

2.6 Erros comuns e limites

Exercícios — capítulo 2

EX005 — Decodificação à mão. Objetivo: ler os campos MBAP e PDU sem código. Insumos: 0010000000060110000a0002 está truncado; 001100000006010f00000008. Passos: decodificar cada campo que puder e declarar se cada quadro é válido. Evidência esperada: tabelas de campos com o raciocínio. Rubrica: 3 = reconhece o primeiro como função 16 sem a contagem de bytes e os valores (o comprimento 6 é coerente com o que está presente, mas uma fc 16 válida precisa de mais), e o segundo como função 15 no endereço 0 para 8 coils, também incompleto sem contagem de bytes e dados; 2 = campos certos, validade não percebida; 1 = parcial; 0 = nada.

EX006 — Estenda o decodificador. Objetivo: melhorar uma ferramenta de detecção. Insumos: Laboratório 2. Passos: (1) para a função 16, decodificar também a contagem de bytes e os valores dos registradores; (2) imprimir os valores do quadro de 10.30.3.7. Evidência esperada: alteração no código e a nova linha de saída. Rubrica: 3 = contagem de bytes 4 e valores 100 e 10 impressos, com uma verificação de que a contagem de bytes é igual a 2 × quantidade; 2 = valores sem verificação; 1 = tentativa; 0 = nada.

EX007 — Política de escrita como dados. Objetivo: expressar uma lista de permissões com precisão. Insumos: a política do KVROC. Passos: escrever uma lista de permissões com entradas (origem, faixa de destino, códigos de função) para a operação normal e para uma janela de manutenção aprovada, e dizer quem alterna entre elas. Evidência esperada: duas tabelas curtas e um responsável. Rubrica: 3 = códigos somente de leitura do SCADA para todas as estações; escritas da engenharia apenas para a estação nomeada no chamado e apenas durante a janela; alternância a cargo do plantão do KVROC, com aprovação da Kestrel Water; 2 = sem janela; 1 = vago; 0 = nada.

EX008 — Reconhecimento ou recurso? Objetivo: raciocinar sobre tráfego ambíguo. Insumos: consultas de função 43 do scada-01 a cada 24 h para todas as estações, e a mesma consulta de 10.20.0.31 para quatro estações em três segundos. Passos: decidir qual exige ação e o que você verificaria primeiro. Evidência esperada: uma nota curta. Rubrica: 3 = a consulta diária do SCADA tratada como provável recurso de inventário, a confirmar com o fornecedor; a rajada de um host desconhecido tratada como constatação prioritária — identificar 10.20.0.31 (o Capítulo 17 volta a ele) sem sondá-lo; 2 = prioridade certa, verificações fracas; 1 = ambas ignoradas ou ambas alarmadas; 0 = nada.

Síntese do capítulo

Os protocolos industriais clássicos confiam em quem consegue alcançá-los; portanto, a posição na rede é autoridade. Os quadros Modbus/TCP têm uma estrutura fixa e verificável; decodificá-los corretamente — incluindo as verificações de comprimento e de protocolo — é a base de todo filtro e detecção posteriores. Escritas vindas do lugar errado são a primeira regra; funções de identificação e de diagnóstico precisam de contexto; e um decodificador só vê o que o seu ponto de captura vê.

Continue estudando — capítulo 02 de 18

← Capítulo 01 · Índice de capítulos do curso · Capítulo 03 →

Siga para a próxima lição escrita.