2 puntos por GN⁺ 2023-09-29 | 1 comentarios | Compartir por WhatsApp
  • En Windows NT 3.1, al hacer un break-in con Ctrl-C usando i386kd, en vez de aparecer el prompt kd> el sistema objetivo 486DX4 se reiniciaba; la causa era un problema de compatibilidad con 486 mejorados
  • KiSaveProcessorControlState y KiRestoreProcessorControlState de NTOSKRNL.EXE usan una comparación de word al comprobar el tipo de CPU, por lo que identifican erróneamente a un 486 con soporte de CPUID como si fuera un Pentium o superior
  • Como CpuType y CpuID de KPRCB se leen juntos, el número de modelo de un 486 con soporte de CPUID se interpreta como 256 mayor, y eso termina provocando un acceso al registro CR4, que no existe en un 486
  • Con el 486DX-33 original y con un Am486DX4-NV8T write-through sin SMM, el kernel debugging funcionaba normalmente, lo que acotó el problema a los 486 mejorados que ofrecen la instrucción CPUID
  • Si se parchean las dos ubicaciones de cmp ds:word_FFDFF138, 5 dentro de NTOSKRNL.EXE para usar byte compare, el problema se puede corregir tanto en NT 3.1 Advanced Server original como en NT 3.1 SP3

Entorno de reproducción y síntoma

  • Se instaló Windows NT 3.1 en una Compaq ProSignia 3080 y luego se intentó hacer kernel debugging
    • Este sistema realmente ejecutó Windows NT 3.1 en operación y parece ser una de las máquinas a las que Windows NT apuntaba explícitamente
    • La RAM se amplió a 128 MB y el Intel 486DX-33 en socket fue reemplazado por un AMD enhanced 486DX4-SV8B
    • Ese CPU soporta caché write-back y SMM, y está montado en un socket adaptador de voltaje
  • Se dejó para después el soporte del BIOS para 486DX4 y se configuró el CPU con multiplicador 2x mediante jumpers
    • Sin soporte del chipset para L1 write-back y configurado a 2x, se esperaba compatibilidad de software con un Intel 80486DX2-66
    • El Intel 80486DX2-66 es una opción soportada por este sistema
  • La instalación de Windows NT 3.1 en sí se completó sin problemas
  • El CD de Windows NT 3.1 incluye símbolos de depuración completos, así que se intentó depurar el kernel
    • El objetivo era investigar por qué NetDDE dejaba errores en el event log
    • También había un problema donde el sistema se caía con cierta tarjeta Ethernet EISA, y seguía existiendo la posibilidad de una falla de hardware
  • En lugar del kd o ntkd de los kits de desarrollo recientes de Windows 10, había que usar i386kd incluido con Windows NT 3.1 para que la configuración de kernel debugging coincidiera
  • Al intentar un break-in con Ctrl-C desde i386kd, la máquina objetivo se reiniciaba en vez de mostrar el prompt kd>

Cosas descartadas como causa

  • Se confirmó que la memoria funcionaba bien
  • No era corrupción de archivos del sistema
  • No estaba activo ningún watchdog de hardware que reiniciara el sistema cuando el kernel se detenía para depuración
  • El adaptador USB-serial del host se comunicaba correctamente
    • Aunque parecía ser un dispositivo PL2301 falsificado, no estaba enviando por error un comando de “reboot system” al depurador
    • El protocolo KD sí incluye un comando real de reboot system
  • Tampoco estaba relacionado con opciones de administración remota o alertas de la motherboard

Causa real: incompatibilidad entre 486 mejorados y el kernel de NT 3.1

  • El kernel de Windows NT 3.1 no es compatible con procesadores 486 mejorados
  • Más concretamente, el problema aparece en procesadores 486 que ofrecen la instrucción CPUID
  • El kernel debugging funciona correctamente en los siguientes CPU
    • El 486DX-33 original instalado
    • Un Am486DX4-NV8T write-through de un core antiguo no mejorado y sin SMM
  • Si el objetivo es solo probar el kernel debugging de NT 3.1, conviene usar un CPU compatible de base con Windows NT 3.1
  • Si se quiere corregir NT en sí, hay que parchear el bug de detección del tipo de CPU en NTOSKRNL.EXE

Ruta de código del kernel donde ocurre el problema

  • La causa directa de la incompatibilidad es un bug en KiSaveProcessorControlState
    • Su función correspondiente, KiRestoreProcessorControlState, tiene un bug similar
  • KiSaveProcessorControlState se llama desde tres lugares dentro de NTOSKRNL.EXE
    • Cuando una excepción se refleja hacia el kernel debugger mediante KdpTrap
      • En el break-in con Ctrl-C, ocurre una breakpoint exception en la función de polling de break-in dentro de la interrupción del tick del temporizador
    • Cuando se llama a KeBugCheckEx, es decir, en una situación de “pantalla azul”
    • Cuando se llama a KiSaveProcessorState
      • Si el análisis de flujo de control de NTOSKRNL.EXE en IDA es correcto, esta función no está exportada ni es llamada desde dentro de NTOSKRNL, así que en la práctica parece no ocurrir
  • KiSaveProcessorControlState guarda el estado de control del procesador en una estructura CONTEXT extendida
    • Guarda CR0, CR2 y CR3
    • En Pentium o superior, también guarda CR4
    • Guarda los registros de depuración DR0~DR3, DR6 y DR7
    • También guarda configuraciones globales de modo protegido como la dirección del GDT, la dirección del IDT, el selector TSS activo y el selector LDT

Error al interpretar campos de KPRCB

  • Para determinar el tipo de procesador se usan valores del KPRCB
    • KPRCB forma parte del KPCR
    • El KPRCB del bootstrap processor o de un sistema monoprocesador está en la dirección virtual FFDFF120, y ese método la tiene hardcodeada
  • Los campos relacionados con KPRCB en NT 3.1, documentados por Geoff Chappell, son los siguientes
    • +018 CHAR CpuType
    • +019 CHAR CpuID
    • +01A UShort CpuStep
  • Estos campos se inicializan en KiSetProcessorType
    • En procesadores 486, el byte del offset 18 se establece en 4
    • En procesadores Pentium, se establece en 5
    • En Pentium Pro y Pentium II/III, se establece en 6
    • El byte del offset 19 es un flag booleano que indica si el procesador soporta CPUID y si este se comporta de forma “razonable”
  • La instrucción de comparación problemática lee un word en la dirección FFDFF138, no un byte
    • FFDFF138 está a 18h bytes del inicio de KPRCB
    • Por lo tanto, no solo interpreta CpuType, sino también el byte siguiente, CpuID, como parte del número de modelo
  • Los procesadores con soporte de CPUID son tratados como si tuvieran un número de modelo 256 mayor que el real
    • Un 80-4-86 con soporte de CPUID es tratado en Windows NT 3.1 como un 80-260-86
    • Como 260 es mucho mayor que 5, que es el umbral de Pentium, el kernel concluye que ese procesador debería tener CR4
    • Los 486 mejorados no tienen CR4, por lo que el problema aparece en la ruta del break-in

Método de parcheo

  • Una vez confirmado el bug, corregirlo es simple
  • En NTOSKRNL.EXE, la instrucción cmp ds:word_FFDFF138, 5 aparece solo dos veces
    • KiSaveProcessorControlState
    • KiRestoreProcessorControlState
  • En ambas ubicaciones hay que cambiar la comparación de word por una comparación de byte
  • Con un editor hexadecimal, se parchea dos veces la siguiente secuencia de bytes
    • Original: 66 83 3D 38 F1 DF FF 05
    • Modificada: 90 80 3D 38 F1 DF FF 05
  • Esta corrección aplica tanto al NTOSKRNL.EXE de la distribución original de NT 3.1 Advanced Server como a NT 3.1 SP3

1 comentarios

 
GN⁺ 2023-09-29
Comentarios de Hacker News
  • Hace tiempo hice un chequeo de puertos para verificar si un servicio seguía activo, y terminé tumbando media empresa con solo abrir algunos puertos TCP en las máquinas
    Eran tiempos absurdos

    • Hubo una época en la que Win95 podía caerse, o incluso ser tomado, con solo enviarle un ping de cierta forma
      Hemos avanzado muchísimo
      https://en.wikipedia.org/wiki/Ping_of_death
      Algunas máquinas también tenían una vulnerabilidad de SMB sin parchear durante años. Ya existía Metasploit en esa época, así que con unos cuantos comandos podías inyectar VNC en la mayoría de los hosts Windows de la red local. Hoy en día, al menos, la velocidad de parcheo es enorme
    • Creo que fueron muchos más que “algunos”
  • Ah, la madriguera de conejo del retrocomputing. Inofensivamente separada de las consecuencias del mundo real, pero aun así muy satisfactoria
    Es una auténtica trampa de miel para geeks. Al final terminé haciendo clic en el botón para expandir “ver 6 comentarios más”

    • Vi el cursor dirigirse a ese botón de expandir y apenas logré apartarlo
      Tal vez hoy sí pueda trabajar un poco
    • Cuando veo computadoras anteriores a mi época, cosas como la PET, pienso que sería divertido jugar con ellas. Una vieja caja IRIX también estaría bien
      ¿Pero una 486? Me trae demasiados recuerdos de pantallazos azules y de esperas interminables mientras hacía swapping al disco porque no podía con la memoria. Creo que todavía es demasiado pronto para mí
    • El contenido del artículo parece seguir siendo relevante hasta cierto punto hoy en día
  • La solución es obvia” es totalmente cierto

    • Mi profesor de matemáticas decía que hay dos tipos de problemas: los fáciles y los que todavía no entendiste
    • Sí, una vez que el bug ya fue identificado. Lo que me intriga es cómo encuentran bugs así
      Probablemente siguió leyendo el desensamblado del código de trap hasta encontrar el problema. No creo que pudiera usar QEMU u otros medios de depuración
  • Hay que leer también el método que quedó enterrado en un comentario oculto cerca del final

    • Nunca entiendo el colapso automático de comentarios de Stack Overflow cuando lo veo
      No es que oculte los comentarios con menos votos, ni los más recientes ni los más antiguos. Se siente aleatorio e innecesario
      Si el problema es el espacio, creo que sería mejor poner paginación en vez de ocultarlos
  • Cosas como los adaptadores slotket que se usaban por compatibilidad en placas viejas de Abit, Asus y quizá MSI realmente tenían capacidad de overclocking
    La clave era la velocidad de la RAM y hasta qué reloj podía aguantar la motherboard en la configuración del multiplicador. Eran los días relativamente tempranos del overclocking, con Celeron y Pentium II
    En algún momento, por compatibilidad, puse un Celeron 600 en un adaptador y luego en un adaptador Slot II a Socket 370, con una configuración como [1], y funcionaba bien a 1.2GHz. Creo que incluso lo subí a 1.4GHz en Windows ME, y al final terminé quemando ese CPU
    [1]http://krick.3feetunder.com/370mod/

    • El overclocking era divertido, pero también está bueno que los CPU modernos puedan controlar con precisión el reloj y el voltaje para aprovechar los márgenes de seguridad de antes
  • La respuesta a la pregunta fue realmente excelente
    El tema era bueno y la respuesta también fue muy buena

    • De hecho, respondió su propia pregunta. Aun así, excelente
  • Es genial que la persona que hizo la pregunta también haya publicado la respuesta
    Me gusta esto. Me alegra mucho que la gente se interese por el retrocomputing
    Documentar este tipo de problemas y soluciones para todos es algo bueno

  • Sabía que esto de CPUID era una mala idea

    • Me da risa que los desarrolladores de Windows sintieran la necesidad de poner una sección de “trustworthiness” en la estructura
  • ¿Por qué la pregunta y la respuesta tenían la misma marca de tiempo?

    • Fue para compartir información. Se puede ver que quien hizo la pregunta y quien dio la respuesta son la misma persona
      Probablemente pensó que publicar esta información en el formato de preguntas y respuestas de SO duraría más que una entrada de blog que nadie encontraría
  • ¿Está mal que haya sabido de qué se trataba sin abrirlo?
    En fin, tengo que ir a gritarles a los chicos de hoy que se salgan de mi césped

    • Resolver problemas cuando crecíamos era distinto
      Solo contábamos con nuestro ingenio y, si hacía falta, con algunos expertos a los que podíamos llamar por teléfono fijo. No existía esa maravillosa Internet llena de respuestas de utilidad variable