VORVEXAPEX

Windows 95 fez 31 anos: as vulnerabilidades que moldaram a segurança ofensiva

Para fins de estudo: como CIH, WinNuke, Ping of Death e outras falhas históricas do Windows 95 funcionavam por dentro, e por que o mesmo padrão de falha ainda aparece em sistemas modernos.

Por Equipe VorvexPublicado em 17 de agosto de 202611 min de leitura

Aviso antes de começar: todas as vulnerabilidades deste post são históricas, documentadas publicamente há mais de duas décadas, e afetam sistemas que saíram de suporte da Microsoft há muito tempo (Windows 95 em 2001, Windows 98 em 2006). Nada aqui é explorável em ambiente atual. O objetivo é educacional, entender como essas falhas funcionavam por dentro ajuda a enxergar por que certas práticas de segurança que hoje parecem óbvias (separação de privilégio, validação de entrada, revisão de protocolo) não existiam ainda, e o quanto custou pra virarem padrão.

Windows 95 completa 31 anos em agosto de 2026. Foi o sistema que trouxe Plug and Play, barra de tarefas, menu Iniciar e a pilha TCP/IP nativa pra sala de milhões de casas, mas chegou num momento em que segurança ainda não era uma disciplina de engenharia reconhecível como é hoje. Não existia processo formal de resposta a vulnerabilidade, não existia CVSS, o próprio sistema de numeração CVE só nasceria em 1999. O resultado foi um sistema operacional inteiro rodando sem boa parte das defesas que hoje consideramos básicas. Sete falhas, entre elas um vírus que ainda é citado como marco em cursos de segurança, mostram como isso jogava na prática.

A retro 1990s CRT computer monitor displaying a classic blue error screen, surrounded by floppy disks, a dial-up modem, and tangled cables on a desk, moody dark background with cyan and blue accent lighting, technical illustration style, no embedded text
O Windows 95 chegou antes de boa parte do vocabulário de segurança que hoje damos como básico existir.

1. Sem separação real entre ring 3 e ring 0

A pior falha do Windows 95 não tem número de CVE porque é anterior ao próprio sistema de numeração, e porque não é um bug pontual, é uma decisão de arquitetura. O kernel do Windows 9x não isolava direito o modo usuário do modo kernel: por compatibilidade com drivers de 16 bits e software de MS-DOS, o sistema permitia que drivers VxD e, na prática, boa parte do código em execução, alcançasse ring 0, o nível de privilégio máximo do processador, sem barreira real no caminho. Não era uma vulnerabilidade corrigível com um patch pontual, era o próprio desenho do sistema.

Technical diagram of concentric privilege rings around a computer processor chip, an arrow piercing from the outermost ring into the innermost ring, dark tech background, blue and red accent lighting, clean vector illustration style, no embedded text
Do modo usuário direto ao kernel: no Windows 9x, a barreira entre ring 3 e ring 0 era mais teórica que real.
  • Escaneava executáveis PE em busca de espaços vazios não utilizados entre seções de código
  • Dividia o próprio corpo em pedaços que cabiam nesses espaços, sem aumentar o tamanho do arquivo infectado
  • Usava um driver VxD como trampolim de ring 3 pra ring 0
  • A partir do kernel, interceptava chamadas de sistema de arquivo pra reinfectar qualquer executável aberto
asm
; Win9x: ring 3 -> ring 0 escalation (technique used by CIH)
SIDT  [mem]          ; leaks the IDT base (SIDT is NOT privileged)
; locate an interrupt descriptor in the IDT (e.g. INT 3)
; save the original gate, then overwrite it to point at attacker code
INT   3              ; firing the interrupt lands execution in ring 0
; from here the code runs with kernel privilege
Ilustração simplificada da técnica: SIDT vaza a base da IDT, o atacante reescreve um gate de interrupção e cai em ring 0.

Foi essa brecha que o CIH, também conhecido como Chernobyl, explorou em escala global. Descoberto em junho de 1998 e atribuído ao estudante taiwanês Chen Ing-hau, o vírus se espalhou por canais de software pirata e chegou a ser distribuído, sem querer, em lotes de computadores comerciais. A técnica de infecção descrita acima o tornava difícil de detectar por checagem de tamanho de arquivo. O payload disparava em datas específicas, a mais conhecida em 26 de abril, aniversário do desastre de Chernobyl, de onde vem o apelido; o pior surto real aconteceu em 26 de abril de 1999. Tinha dois estágios: sobrescrevia o primeiro megabyte do disco de boot com lixo, destruindo a tabela de partições e tornando os dados inacessíveis, e em placas-mãe com certos chipsets de BIOS Flash regravável por software, tentava sobrescrever a própria BIOS, inutilizando a máquina em nível de hardware, um dos primeiros malwares capazes disso. Estimativas da época falam em cerca de 60 milhões de máquinas infectadas e US$ 40 milhões em prejuízo. Um detalhe reforça o diagnóstico de falha arquitetural: o Windows NT, com kernel de verdade e separação de privilégio real, era imune.

2. Uma senha de 1 byte bastava (CVE-2000-0979)

O compartilhamento de arquivos e impressoras do Windows 95, 98 e Me (File and Print Sharing) protegia compartilhamentos com senha em nível de compartilhamento, o modelo mais simples possível, sem usuário, só uma senha pro recurso inteiro. O problema estava na validação: o servidor comparava a senha enviada pelo cliente usando o tamanho que o próprio cliente informava no pacote, não o tamanho real da senha configurada. Um atacante podia mandar um pedido de acesso com um campo de senha de 1 byte só e, se aquele byte batesse com o primeiro caractere da senha verdadeira, aquele trecho da verificação passava. Na prática, isso transformava uma força bruta que deveria crescer exponencialmente com o tamanho da senha numa sequência de tentativas curtas, byte a byte, reconstruindo a senha aos poucos em vez de adivinhar tudo de uma vez.

text
# File & Print Sharing - share-level authentication
# SMB request carrying a 1-byte password field:
PasswordLength = 1
Password       = "A"     # one guessed byte at a time
# the server compares only PasswordLength bytes against the real password
# if "A" == first character, this part of the check passes
# -> recover the password byte by byte instead of full brute force
O servidor valida apenas os bytes que o cliente diz ter enviado, o que permite reconstruir a senha um caractere por vez.

A National Vulnerability Database classifica a falha como CVE-2000-0979, CVSS 2.0 de 6,4 (AV:N/AC:L/Au:N/C:P/I:P/A:N; não existe pontuação CVSS v3 porque a falha é anterior a esse padrão). A Microsoft corrigiu o problema só em 10 de outubro de 2000, pelo boletim MS00-072, cinco anos depois do lançamento do Windows 95.

3. RC4 de 32 bits nos arquivos .PWL

Os arquivos .PWL guardavam em cache as credenciais que o usuário já tinha digitado (senha de rede, de discagem, de compartilhamento, de impressora) pra não pedir de novo a cada sessão. O Windows 95 cifrava esse cache com RC4, mas limitava o espaço de chave efetivo a apenas 32 bits, muito pouco pra resistir a força bruta. Em dezembro de 1995, poucos meses depois do lançamento do sistema e quatro anos antes de existir o sistema CVE, o pesquisador neozelandês Peter Gutmann publicou a descrição de como o Windows gerava essas chaves RC4 de 32 bits. Dias depois, o programador norueguês Frank Andrew Stevenson foi além e mostrou um ataque prático mais grave que o tamanho da chave: o esquema reutilizava keystream do RC4 entre partes do próprio arquivo, o erro clássico de qualquer cifra de fluxo, XOR de dois textos cifrados com o mesmo keystream revela o XOR dos textos originais. Como os primeiros 20 bytes de todo .PWL eram previsíveis (derivados do nome de usuário em maiúsculas, preenchido com zeros), dava pra recuperar parte do keystream por ataque de texto plano conhecido e usar isso pra decifrar o resto do arquivo. Ferramentas como o Glide, da época, automatizavam a quebra.

text
# .PWL: the same RC4 keystream is reused across parts of the file
C1 = P1 XOR KS
C2 = P2 XOR KS
C1 XOR C2 = P1 XOR P2      # the keystream cancels out
# the first ~20 bytes of P1 are predictable (uppercase username + nulls)
# -> recover KS via known-plaintext, then decrypt the rest
Reutilizar keystream é o erro clássico de cifra de fluxo: o XOR entre dois textos cifrados cancela a chave.

A Microsoft respondeu estendendo a chave para 128 bits, mas o patch (descrito no artigo Q132807 da base de conhecimento) não corrigia o problema de fundo, a reutilização de keystream continuava lá, então o ataque de Stevenson seguia funcionando independente do tamanho da chave. É a mesma lição que volta hoje em qualquer avaliação de criptografia caseira: aumentar o tamanho da chave não resolve um design de cifra de fluxo que reaproveita keystream.

4. Um pacote derruba o sistema: WinNuke (CVE-1999-0153)

WinNuke explorava como o Windows tratava dados out-of-band (OOB) do TCP na porta 139, usada pelo NetBIOS. O ataque montava um segmento TCP com a flag URG ativada e o ponteiro de dados urgentes (urgent pointer) apontando pra fora dos dados realmente enviados. O parser de OOB no kernel do Windows não validava essa condição direito e travava o sistema, geralmente numa tela azul.

  • Segmento TCP direcionado à porta 139 (NetBIOS Session Service)
  • Flag URG ativada no cabeçalho TCP
  • Urgent pointer com valor além do tamanho real dos dados enviados
  • Payload mínimo, só o suficiente pra acionar o tratamento de OOB no kernel
python
import socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((target, 139))          # NetBIOS Session Service
s.send(b"Bye", socket.MSG_OOB)    # out-of-band data, sets the URG flag
s.close()
MSG_OOB marca os dados como urgentes (flag URG) na porta 139, o gatilho do travamento.

O código foi publicado em 9 de maio de 1997 por alguém usando o pseudônimo '_eci', e se popularizou rápido, surgiram variantes como fedup, killwin, liquidnuke e winnuker02, e o verbo 'nukar' virou vocabulário comum em salas de IRC da época. A NVD registra o caso como CVE-1999-0153, CVSS 2.0 de 5,0 (AV:N/AC:L/Au:N/C:N/I:N/A:P). A correção veio pela atualização Winsock 2 no Windows 95 e pelo Service Pack 3 do Windows NT 4.0; o Windows 98, a partir do RC0, já nasceu imune a essa variante específica.

5. Ping of Death (CVE-1999-0128)

O protocolo IP define 65.535 bytes como tamanho máximo de um pacote. O Ping of Death enviava um pacote ICMP (o mesmo protocolo do comando ping) fragmentado de um jeito que, na remontagem, o tamanho total calculado passava desse limite. A maioria das implementações de pilha TCP/IP da época não validava esse limite durante a remontagem dos fragmentos, o que causava estouro de buffer, travamento ou reinicialização do sistema. Não exigia sessão, não exigia autenticação, um único pacote malformado bastava, e o problema não era exclusivo do Windows, atingia praticamente qualquer pilha TCP/IP da época, de roteador a estação de trabalho Unix. A NVD classifica a falha como CVE-1999-0128, CVSS 2.0 de 5,0 (AV:N/AC:L/Au:N/C:N/I:N/A:P).

bash
# Windows: oversized ICMP echo, overflows on reassembly
ping -l 65510 target

# Unix equivalent
ping -s 65510 target
Um echo ICMP acima do limite do protocolo: a soma dos fragmentos ultrapassa 65.535 bytes na remontagem.

6. A família de fragmentação: Teardrop, LAND e Bonk

O Ping of Death não foi caso isolado, era sintoma de um problema mais amplo: pilhas TCP/IP da época confiavam demais nos campos de offset e tamanho que o próprio remetente informava, sem checar se faziam sentido. O Teardrop (CVE-1999-0015) explorava exatamente isso: mandava fragmentos IP com campos de offset sobrepostos de propósito, de um jeito que o algoritmo de remontagem não conseguia calcular corretamente onde cada pedaço deveria entrar, o que travava a pilha de rede. Uma variante posterior, batizada de Teardrop-2 (CVE-1999-0104), repetia o mesmo princípio contra implementações que já tinham recebido patch parcial contra o Teardrop original.

text
# Teardrop: overlapping IP fragment offsets
frag 1:  MF=1  offset=0    len=36
frag 2:  MF=0  offset=24   len=4      # falls INSIDE fragment 1
# a vulnerable stack computes (end - offset) and underflows the length

# LAND: TCP SYN with source == destination
source      = 10.0.0.5:139
destination = 10.0.0.5:139           # the host answers itself, in a loop
Offsets de fragmento sobrepostos (Teardrop) e origem igual ao destino (LAND): o mesmo defeito de validação de cabeçalho.

Duas outras variantes seguiam padrão parecido. O LAND (CVE-1999-0016) usava um pacote TCP SYN com IP e porta de origem iguais aos de destino, fazendo a máquina alvo responder pra si mesma e entrar num laço que consumia recursos até travar, o mesmo defeito de fundo (falta de validação de cabeçalho), só que mirando o endereço de origem em vez do offset de fragmento. O Bonk (CVE-1999-0258) era uma variação do Teardrop mirando fragmentos UDP com campos de offset e tamanho conflitantes entre si, mesma classe de erro, alvo de protocolo diferente. Todas receberam CVSS 2.0 de 5,0 na NVD, mesma faixa do WinNuke e do Ping of Death.

O padrão comum entre WinNuke, Ping of Death, Teardrop, LAND e Bonk é o mesmo: nenhuma dessas pilhas de rede validava direito os metadados que o remetente controlava (offset, tamanho, endereço, ponteiro urgente) antes de confiar neles pra remontar ou processar um pacote. Afetava não só o Windows, o mesmo defeito de remontagem de fragmentos derrubava versões do kernel Linux anteriores à 2.0.32 e 2.1.63, e outras pilhas TCP/IP da época.

7. Nomes de dispositivo DOS no caminho: o \con\con (CVE-2000-0168)

O MS-DOS reservava um punhado de nomes especiais de dispositivo, CON (console), PRN (impressora), AUX, NUL e as séries COM1 a COM9 e LPT1 a LPT9, que não podiam virar nome de arquivo porque o sistema operacional os redirecionava direto pro hardware ou periférico correspondente, não pro sistema de arquivos. O Windows 9x herdou essa reserva de nomes e sabia bloquear a criação de um arquivo ou pasta chamado, por exemplo, CON. O que ele não verificava era o caso de um caminho conter mais de um nome de dispositivo reservado ao mesmo tempo, como c:\con\con. Ao tentar resolver esse caminho, o sistema tentava um acesso a recurso ilegal, tratando-o como dois dispositivos físicos aninhados, e travava.

html
<!-- CVE-2000-0168: the victim only has to open the page -->
<img src="c:\con\con">
Bastava uma tag de imagem apontando pro caminho para travar o navegador da vítima, sem clique nem download.

A National Vulnerability Database registra o caso como CVE-2000-0168, 'DOS Device in Path Name', CVSS 2.0 de 5,0 (AV:N/AC:L/Au:N/C:N/I:N/A:P), publicada em março de 2000. O boletim MS00-017 da Microsoft, de 16 de março de 2000, descreve o que tornava essa falha mais perigosa que um erro de digitação no prompt de comando: como o navegador da época processava caminho de arquivo em qualquer atributo que apontasse pra um recurso, bastava uma página web com uma tag <img src="c:\con\con"> pra travar o computador de qualquer visitante rodando Windows 95 ou 98, sem clique, sem download, sem exigir privilégio nenhum. A vítima só precisava abrir a página.

O padrão de fundo não desapareceu com o fim do suporte ao Windows 9x. Nomes de dispositivo DOS continuam reservados até hoje, tentar criar um arquivo chamado CON.txt ou NUL num Windows moderno ainda falha, e por décadas ferramentas de terceiros esbarraram nesse tratamento especial de formas inesperadas. Mais amplamente, é a mesma classe de bug, um parser de caminho que trata parte da entrada como caso especial em vez de validar a entrada inteira, que hoje aparece como path traversal, abuso de Alternate Data Streams do NTFS, ou falha de normalização de nome curto (8.3) explorada pra escapar de sandbox ou de diretório de upload.

O padrão que sobrevive até hoje

Nenhuma dessas sete falhas é explorável num sistema com suporte hoje, mas o padrão por trás de cada uma continua aparecendo, só que em código mais novo. Falta de separação de privilégio virou escape de container e vulnerabilidade de driver de kernel assinado. Validação de entrada que confia no que o remetente diz sobre o próprio pacote ainda é causa raiz de boa parte dos RCEs e DoS que aparecem em advisory a cada mês, incluindo em software crítico de infraestrutura moderna. Um parser de caminho que trata nome reservado como exceção pontual, em vez de validar a entrada inteira, ainda vira path traversal e abuso de nome de arquivo especial em aplicação atual. Criptografia caseira que reaproveita keystream ou reduz artificialmente o espaço de chave ainda aparece em pentest, geralmente em sistema legado que ninguém revisou desde que foi escrito. E uma lógica de autenticação que compara só parte do que deveria comparar continua sendo o tipo de achado que separa um scanner automatizado de uma revisão manual de verdade.

Testar hoje não é procurar CIH ou WinNuke, é procurar a próxima versão do mesmo erro: uma rota que confia demais no que o cliente informa, um parser que não valida limite antes de alocar memória, uma implementação de criptografia que parece robusta até alguém olhar o design de perto. É exatamente esse tipo de falha, silenciosa até alguém mandar a entrada errada, que pentest de rede e fuzzing de protocolo existem pra encontrar antes de um atacante encontrar primeiro. Trinta e um anos depois, a lição do Windows 95 ainda é a mesma: segurança não é feature que se adiciona depois, é decisão de arquitetura que se paga, de um jeito ou de outro, muito mais tarde.

← Voltar ao blog

Quer essa mesma profundidade aplicada ao seu ambiente?

Conte o que você precisa validar e a equipe monta um escopo de pentest sob medida.

Falar no WhatsApp

Pronto para avaliar o risco da sua empresa?