VORVEXAPEX

Windows 95 cumple 31 años: las vulnerabilidades que marcaron la seguridad ofensiva

Con fines de estudio: cómo funcionaban por dentro CIH, WinNuke, Ping of Death y otras fallas históricas de Windows 95, y por qué el mismo patrón de falla sigue apareciendo en sistemas modernos.

Por Equipo VorvexPublicado el 17 de agosto de 202612 min de lectura

Un aviso antes de empezar: todas las vulnerabilidades de este post son históricas, están documentadas públicamente desde hace más de dos décadas, y afectan a sistemas que Microsoft dejó de dar soporte hace mucho tiempo (Windows 95 en 2001, Windows 98 en 2006). Nada de esto es explotable en un sistema actual. El objetivo es educativo: entender cómo funcionaban estas fallas por dentro ayuda a ver por qué prácticas de seguridad que hoy damos por obvias, separación de privilegios, validación de entradas, revisión de protocolos, todavía no existían, y cuánto costó que se volvieran estándar.

Windows 95 cumple 31 años en agosto de 2026. Fue el sistema que llevó Plug and Play, la barra de tareas, el menú Inicio y una pila TCP/IP nativa a la sala de millones de casas, pero llegó en un momento en que la seguridad todavía no era una disciplina de ingeniería reconocible como hoy. No existía un proceso formal de respuesta a vulnerabilidades, no existía CVSS, y el propio sistema de numeración CVE recién nacería en 1999. El resultado fue un sistema operativo completo funcionando sin buena parte de las defensas que hoy consideramos básicas. Siete fallas, entre ellas un virus que todavía se cita como caso de referencia en cursos de seguridad, muestran cómo se veía eso en la práctica.

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
Windows 95 llegó antes de que existiera buena parte del vocabulario de seguridad que hoy damos por sentado.

1. Sin separación real entre ring 3 y ring 0

La peor falla de Windows 95 no tiene número de CVE porque es anterior al propio sistema de numeración, y porque no es un bug puntual, es una decisión de arquitectura. El kernel de Windows 9x no aislaba bien el modo usuario del modo kernel: por compatibilidad con drivers de 16 bits y software de MS-DOS, el sistema permitía que los drivers VxD, y en la práctica buena parte del código en ejecución, llegaran a ring 0, el nivel de privilegio máximo del procesador, sin una barrera real en el camino. No era algo corregible con un parche puntual, era el propio diseño del 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
Del modo usuario directo al kernel: en Windows 9x, la barrera entre ring 3 y ring 0 era más teórica que real.
  • Escaneaba ejecutables PE en busca de espacios vacíos sin usar entre secciones de código
  • Dividía su propio cuerpo en partes que cabían en esos espacios, sin aumentar el tamaño del archivo infectado
  • Usaba un driver VxD como trampolín de ring 3 a ring 0
  • Desde el kernel, interceptaba llamadas al sistema de archivos para reinfectar cualquier ejecutable que se abriera
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
Ilustración simplificada de la técnica: SIDT filtra la base de la IDT, el atacante reescribe un gate de interrupción y cae en ring 0.

Esa fue la brecha que explotó el CIH, también conocido como Chernobyl, a escala global. Descubierto en junio de 1998 y atribuido al estudiante taiwanés Chen Ing-hau, el virus se propagó por canales de software pirata y hasta terminó distribuido, sin querer, en lotes de computadoras comerciales. Su técnica de infección, descrita arriba, lo hacía difícil de detectar revisando el tamaño del archivo. La carga se activaba en fechas específicas, la más conocida el 26 de abril, aniversario del desastre de Chernobyl, de ahí el apodo; el peor brote real ocurrió el 26 de abril de 1999. Tenía dos etapas: sobrescribía el primer megabyte del disco de arranque con basura, destruyendo la tabla de particiones y dejando los datos inaccesibles, y en placas madre con ciertos chips de BIOS Flash regrabables por software, intentaba sobrescribir la BIOS misma, inutilizando la máquina a nivel de hardware, uno de los primeros malware capaces de eso. Estimaciones de la época hablan de unos 60 millones de máquinas infectadas y USD 40 millones en pérdidas. Un detalle refuerza el diagnóstico de falla arquitectónica: Windows NT, con un kernel de verdad y separación de privilegios real, era inmune.

2. Una contraseña de 1 byte alcanzaba (CVE-2000-0979)

El uso compartido de archivos e impresoras de Windows 95, 98 y Me (File and Print Sharing) protegía los recursos compartidos con contraseña a nivel de recurso, el modelo más simple posible, sin usuarios, solo una contraseña para todo el recurso. El problema estaba en la validación: el servidor comparaba la contraseña enviada por el cliente usando la longitud que el propio cliente declaraba en el paquete, no la longitud real de la contraseña configurada. Un atacante podía enviar una solicitud de acceso con un campo de contraseña de apenas 1 byte y, si ese byte coincidía con el primer carácter de la contraseña real, esa parte de la verificación pasaba. En la práctica, eso convertía una fuerza bruta que debía crecer exponencialmente con el largo de la contraseña en una secuencia de intentos cortos, byte a byte, reconstruyendo la contraseña de a poco en vez de adivinarla toda de una 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
El servidor valida solo los bytes que el cliente dice haber enviado, lo que permite reconstruir la contraseña un carácter por vez.

La National Vulnerability Database la registra como CVE-2000-0979, CVSS 2.0 de 6,4 (AV:N/AC:L/Au:N/C:P/I:P/A:N; no existe puntuación CVSS v3 porque la falla es anterior a ese estándar). Microsoft corrigió el problema recién el 10 de octubre de 2000, mediante el boletín MS00-072, cinco años después del lanzamiento de Windows 95.

3. RC4 de 32 bits en los archivos .PWL

Los archivos .PWL guardaban en caché las credenciales que el usuario ya había escrito (contraseña de red, de acceso telefónico, de recurso compartido, de impresora) para no pedirlas de nuevo en cada sesión. Windows 95 cifraba ese caché con RC4, pero limitaba el espacio de clave efectivo a apenas 32 bits, muy poco para resistir fuerza bruta. En diciembre de 1995, pocos meses después del lanzamiento del sistema y cuatro años antes de que existiera el sistema CVE, el investigador neozelandés Peter Gutmann publicó la descripción de cómo Windows generaba esas claves RC4 de 32 bits. Días después, el programador noruego Frank Andrew Stevenson fue más allá y mostró un ataque práctico peor que el tamaño de la clave: el esquema reutilizaba keystream de RC4 entre partes del mismo archivo, el error clásico de cualquier cifrado de flujo, hacer XOR de dos textos cifrados con el mismo keystream revela el XOR de los textos originales. Como los primeros 20 bytes de todo archivo .PWL eran predecibles (derivados del nombre de usuario en mayúsculas, rellenado con ceros), se podía recuperar parte del keystream con un ataque de texto plano conocido y usarlo para descifrar el resto del archivo. Herramientas de la época, como Glide, automatizaban el quiebre.

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 es el error clásico de cifrado de flujo: el XOR entre dos textos cifrados cancela la clave.

La respuesta de Microsoft fue extender la clave a 128 bits, pero el parche (documentado en el artículo Q132807 de la base de conocimiento) no corregía el problema de fondo, la reutilización de keystream seguía ahí, así que el ataque de Stevenson seguía funcionando sin importar el tamaño de la clave. Es la misma lección que vuelve hoy en cualquier revisión de criptografía casera: agrandar la clave no arregla un diseño de cifrado de flujo que reutiliza keystream.

4. Un paquete tumba el sistema: WinNuke (CVE-1999-0153)

WinNuke explotaba cómo Windows manejaba los datos fuera de banda (OOB) de TCP en el puerto 139, usado por NetBIOS. El ataque armaba un segmento TCP con la bandera URG activada y el puntero de datos urgentes (urgent pointer) apuntando más allá de los datos realmente enviados. El analizador de OOB del kernel de Windows no validaba bien esa condición y tumbaba el sistema, casi siempre en una pantalla azul.

  • Segmento TCP dirigido al puerto 139 (NetBIOS Session Service)
  • Bandera URG activada en el encabezado TCP
  • Puntero urgente con un valor más allá del tamaño real de los datos enviados
  • Carga mínima, apenas la necesaria para activar el manejo de OOB en el 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 los datos como urgentes (bandera URG) en el puerto 139, el gatillo del cuelgue.

El código se publicó el 9 de mayo de 1997 bajo el seudónimo '_eci', y se popularizó rápido, aparecieron variantes como fedup, killwin, liquidnuke y winnuker02, y el verbo 'nukear' se volvió vocabulario común en los canales de IRC de la época. La NVD la registra como CVE-1999-0153, CVSS 2.0 de 5,0 (AV:N/AC:L/Au:N/C:N/I:N/A:P). La corrección llegó con la actualización Winsock 2 para Windows 95 y el Service Pack 3 de Windows NT 4.0; Windows 98, desde su RC0, ya nació inmune a esta variante específica.

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

El protocolo IP define 65.535 bytes como el tamaño máximo de un paquete. El Ping of Death enviaba un paquete ICMP (el mismo protocolo detrás del comando ping) fragmentado de tal forma que, al reensamblarlo, el tamaño total calculado superaba ese límite. La mayoría de las implementaciones de pila TCP/IP de la época no validaban ese límite durante el reensamblado de fragmentos, lo que causaba un desbordamiento de búfer, una caída del sistema o un reinicio. No requería sesión ni autenticación, un solo paquete malformado alcanzaba, y el problema no era exclusivo de Windows, afectaba prácticamente a cualquier pila TCP/IP de la época, desde routers hasta estaciones de trabajo Unix. La NVD la clasifica 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
Un echo ICMP por encima del límite del protocolo: los fragmentos suman más de 65.535 bytes al reensamblarse.

6. La familia de fragmentación: Teardrop, LAND y Bonk

El Ping of Death no fue un caso aislado, era síntoma de un problema más amplio: las pilas TCP/IP de la época confiaban demasiado en los campos de offset y tamaño que el propio remitente informaba, sin verificar si tenían sentido. El Teardrop (CVE-1999-0015) explotaba exactamente eso: enviaba fragmentos IP con campos de offset superpuestos a propósito, de forma que el algoritmo de reensamblado no podía calcular correctamente dónde encajaba cada pedazo, lo que tumbaba la pila de red. Una variante posterior, llamada Teardrop-2 (CVE-1999-0104), repetía el mismo principio contra implementaciones que ya habían recibido un parche parcial contra el 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 superpuestos (Teardrop) y origen igual al destino (LAND): el mismo defecto de validación de encabezado.

Otras dos variantes seguían un patrón parecido. El LAND (CVE-1999-0016) usaba un paquete TCP SYN con la IP y el puerto de origen iguales a los de destino, haciendo que la máquina objetivo se respondiera a sí misma y entrara en un bucle que consumía recursos hasta caerse, la misma falla de fondo (falta de validación de encabezado), solo que apuntando a la dirección de origen en vez del offset de fragmento. El Bonk (CVE-1999-0258) era una variante del Teardrop dirigida a fragmentos UDP con campos de offset y tamaño en conflicto, la misma clase de error, apuntando a otro protocolo. Todas recibieron un CVSS 2.0 de 5,0 en la NVD, el mismo rango que WinNuke y Ping of Death.

El patrón común entre WinNuke, Ping of Death, Teardrop, LAND y Bonk es el mismo: ninguna de estas pilas de red validaba bien los metadatos que controlaba el remitente (offset, tamaño, dirección, puntero urgente) antes de confiar en ellos para reensamblar o procesar un paquete. Tampoco era exclusivo de Windows, la misma falla de reensamblado de fragmentos tumbaba versiones del kernel de Linux anteriores a la 2.0.32 y la 2.1.63, y otras pilas TCP/IP de la época.

7. Nombres de dispositivo DOS en la ruta: el \con\con (CVE-2000-0168)

MS-DOS reservaba un puñado de nombres especiales de dispositivo, CON (consola), PRN (impresora), AUX, NUL y las series COM1 a COM9 y LPT1 a LPT9, que no podían convertirse en nombre de archivo porque el sistema operativo los redirigía directo al hardware o periférico correspondiente, no al sistema de archivos. Windows 9x heredó esa reserva de nombres y sabía bloquear la creación de un archivo o carpeta llamado, por ejemplo, CON. Lo que no verificaba era el caso de una ruta que contuviera más de un nombre de dispositivo reservado a la vez, como c:\con\con. Al intentar resolver esa ruta, el sistema intentaba un acceso a recurso ilegal, tratándolo como dos dispositivos físicos anidados, y se caía.

html
<!-- CVE-2000-0168: the victim only has to open the page -->
<img src="c:\con\con">
Bastaba una etiqueta de imagen apuntando a la ruta para tumbar el navegador de la víctima, sin clic ni descarga.

La National Vulnerability Database la registra 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 en marzo de 2000. El boletín MS00-017 de Microsoft, del 16 de marzo de 2000, describe lo que hacía esta falla más peligrosa que un error de tipeo en la línea de comandos: como el navegador de la época procesaba rutas de archivo en cualquier atributo que apuntara a un recurso, bastaba una página web con una etiqueta <img src="c:\con\con"> para tumbar la computadora de cualquier visitante con Windows 95 o 98, sin clic, sin descarga, sin necesitar ningún privilegio. La víctima solo tenía que abrir la página.

El patrón de fondo no desapareció cuando terminó el soporte a Windows 9x. Los nombres de dispositivo DOS siguen reservados hoy, intentar crear un archivo llamado CON.txt o NUL en un Windows moderno todavía falla, y durante décadas herramientas de terceros tropezaron con ese tratamiento especial de formas inesperadas. Más en general, es la misma clase de bug, un analizador de rutas que trata parte de la entrada como caso especial en vez de validar toda la entrada, que hoy aparece como path traversal, abuso de Alternate Data Streams de NTFS, o fallas de normalización de nombre corto (8.3) explotadas para escapar de un sandbox o de un directorio de carga.

El patrón que sobrevive hasta hoy

Ninguna de estas siete fallas es explotable en un sistema con soporte hoy, pero el patrón detrás de cada una sigue apareciendo, solo que en código más nuevo. La falta de separación de privilegios se convirtió en escapes de contenedor y vulnerabilidades de drivers de kernel firmados. La validación de entrada que confía en lo que el remitente dice sobre su propio paquete sigue siendo la causa raíz de buena parte de los RCE y DoS que aparecen en advisories cada mes, incluso en software crítico de infraestructura moderna. Un analizador de rutas que trata un nombre reservado como excepción puntual, en vez de validar toda la entrada, todavía se convierte en path traversal y abuso de nombre de archivo especial en aplicaciones actuales. La criptografía casera que reutiliza keystream o reduce artificialmente el espacio de clave todavía aparece en pentest, casi siempre en sistemas legados que nadie revisó desde que se escribieron. Y una lógica de autenticación que compara solo una parte de lo que debería sigue siendo el tipo de hallazgo que separa un escáner automatizado de una revisión manual de verdad.

Probar hoy no es buscar CIH o WinNuke, es buscar la próxima versión del mismo error: una ruta que confía demasiado en lo que informa el cliente, un analizador que no valida un límite antes de reservar memoria, una implementación de criptografía que parece sólida hasta que alguien revisa el diseño de cerca. Es exactamente ese tipo de falla, silenciosa hasta que alguien manda la entrada equivocada, la que el pentest de red y el fuzzing de protocolo existen para encontrar antes de que un atacante la encuentre primero. Treinta y un años después, la lección de Windows 95 sigue siendo la misma: la seguridad no es una función que se agrega después, es una decisión de arquitectura que se paga, de una forma u otra, mucho más tarde.

← Volver al blog

¿Quieres esa misma profundidad aplicada a tu entorno?

Cuéntanos qué necesitas validar y el equipo arma un alcance de pentest a medida.

Hablar por WhatsApp

¿Listo para evaluar el riesgo de su empresa?