2 Punkte von GN⁺ 2023-09-29 | 1 Kommentare | Auf WhatsApp teilen
  • Wenn man unter Windows NT 3.1 mit i386kd ein Ctrl-C-Break-in auslöst, erhält man statt des kd>-Prompts einen Neustart des Zielsystems mit 486DX4; Ursache war ein Problem mit der enhanced-486-Kompatibilität
  • KiSaveProcessorControlState und KiRestoreProcessorControlState in NTOSKRNL.EXE verwenden beim Prüfen des CPU-Typs einen Word-Vergleich, wodurch ein 486 mit CPUID-Unterstützung fälschlich als Pentium oder neuer erkannt wird
  • Da CpuType und CpuID im KPRCB gemeinsam gelesen werden, wird die Modellnummer eines CPUID-fähigen 486 um 256 zu groß interpretiert; dadurch erfolgt ein Zugriff auf das auf 486 nicht vorhandene CR4-Register
  • Mit dem ursprünglich eingebauten 486DX-33 und einem write-through Am486DX4-NV8T ohne SMM funktionierte Kernel-Debugging korrekt, wodurch sich das Problem auf enhanced 486 mit CPUID-Befehl eingrenzen ließ
  • Wenn man die beiden Stellen cmp ds:word_FFDFF138, 5 in NTOSKRNL.EXE auf einen Byte-Vergleich patcht, lässt sich sowohl das originale NT 3.1 Advanced Server als auch NT 3.1 SP3 korrigieren

Reproduktionsumgebung und Symptom

  • Auf einem Compaq ProSignia 3080 wurde Windows NT 3.1 installiert und anschließend Kernel-Debugging versucht
    • Dieses System lief tatsächlich produktiv mit Windows NT 3.1 und scheint eine der Maschinen gewesen zu sein, auf die Windows NT ausdrücklich abzielte
    • Der RAM wurde auf 128 MB erweitert, und der gesockelte Intel 486DX-33 wurde durch einen AMD enhanced 486DX4-SV8B ersetzt
    • Diese CPU unterstützt write-back cache und SMM und ist in einem Spannungsadapter-Sockel montiert
  • Die BIOS-Unterstützung für den 486DX4 wurde zunächst ignoriert und die CPU per Jumper auf 2x-Multiplikator gesetzt
    • Ohne L1-write-back-Unterstützung des Chipsatzes und mit 2x-Multiplikator wurde erwartet, dass die Software-Kompatibilität einem Intel 80486DX2-66 entspricht
    • Der Intel 80486DX2-66 ist eine unterstützte Option für dieses System
  • Die Installation von Windows NT 3.1 selbst verlief problemlos
  • Da die Windows-NT-3.1-CD vollständige Debug-Symbole enthält, wurde Kernel-Debugging ausprobiert
    • Ziel war es, herauszufinden, warum NetDDE Fehler ins Ereignisprotokoll schreibt
    • Zusätzlich gab es ein Problem, bei dem das System mit einer bestimmten EISA-Ethernet-Karte abstürzte, sodass ein Hardwaredefekt nicht ausgeschlossen war
  • Statt kd oder ntkd aus dem aktuellen Windows-10-Entwicklungskit musste das mit Windows NT 3.1 gelieferte i386kd verwendet werden, damit die Kernel-Debugging-Konfiguration passt
  • Wenn in i386kd ein Ctrl-C-Break-in versucht wurde, lieferte die Zielmaschine keinen kd>-Prompt, sondern startete neu

Dinge, die als Ursache ausgeschlossen wurden

  • Der Speicher wurde als fehlerfrei bestätigt
  • Es lag keine Beschädigung von Systemdateien vor
  • Es war kein Hardware-Watchdog aktiv, der das System neu startet, wenn der Kernel für Debugging angehalten wird
  • Der USB-Seriell-Adapter auf Host-Seite kommunizierte korrekt
    • Das Gerät scheint zwar ein gefälschter PL2301 zu sein, hat aber nicht versehentlich einen Debugger-Befehl falsch übertragen und dadurch „reboot system“ gesendet
    • Im KD-Protokoll gibt es tatsächlich einen Befehl zum Neustart des Systems
  • Auch mit Remote-Management- oder Warnoptionen des Mainboards hing das Problem nicht zusammen

Die eigentliche Ursache: Inkompatibilität zwischen enhanced 486 und NT-3.1-Kernel

  • Der Windows-NT-3.1-Kernel ist nicht kompatibel mit enhanced-486-Prozessoren
  • Genauer gesagt tritt das Problem bei 486-Prozessoren auf, die den Befehl CPUID bereitstellen
  • Kernel-Debugging funktioniert korrekt auf folgenden CPUs
    • dem ursprünglich eingebauten 486DX-33
    • einem write-through Am486DX4-NV8T mit älterem non-enhanced core ohne SMM
  • Wenn es nur darum geht, NT-3.1-Kernel-Debugging zu testen, ist es sinnvoller, eine CPU zu verwenden, die von Windows NT 3.1 standardmäßig unterstützt wird
  • Wenn man NT selbst korrigieren will, muss der Fehler in der CPU-Typ-Erkennung von NTOSKRNL.EXE gepatcht werden

Der Kernel-Codepfad, auf dem das Problem auftritt

  • Die direkte Ursache der Inkompatibilität ist ein Bug in KiSaveProcessorControlState
    • Die zugehörige Funktion KiRestoreProcessorControlState enthält einen ähnlichen Bug
  • KiSaveProcessorControlState wird an drei Stellen innerhalb von NTOSKRNL.EXE aufgerufen
    • wenn eine Ausnahme über KdpTrap an den Kernel-Debugger reflektiert wird
      • Bei Ctrl-C-Break-in entsteht dabei eine Breakpoint-Ausnahme in der Break-in-Polling-Funktion innerhalb des Timer-Tick-Interrupts
    • wenn KeBugCheckEx aufgerufen wird, also im „Blue Screen“-Fall
    • wenn KiSaveProcessorState aufgerufen wird
      • Wenn IDAs Kontrollflussanalyse von NTOSKRNL.EXE vollständig ist, wird diese Funktion nicht exportiert und auch intern von NTOSKRNL nicht aufgerufen, sodass dieser Fall praktisch nicht eintritt
  • KiSaveProcessorControlState speichert den Prozessor-Steuerzustand in einer erweiterten CONTEXT-Struktur
    • Es speichert CR0, CR2 und CR3
    • Auf Pentium oder neuer wird auch CR4 gespeichert
    • Es speichert die Debug-Register DR0~DR3, DR6 und DR7
    • Außerdem speichert es globale Protected-Mode-Einstellungen wie GDT-Adresse, IDT-Adresse, aktiven TSS-Selektor und LDT-Selektor

Fehlinterpretation von KPRCB-Feldern

  • Zur Bestimmung des Prozessortyps werden Werte aus dem KPRCB verwendet
    • KPRCB ist Teil des KPCR
    • Das KPRCB des Boot-Prozessors oder eines Single-Processor-Systems liegt bei der virtuellen Adresse FFDFF120, was in dieser Methode hartkodiert ist
  • Die von Geoff Chappell zusammengestellten relevanten KPRCB-Felder in NT 3.1 sind:
    • +018 CHAR CpuType
    • +019 CHAR CpuID
    • +01A UShort CpuStep
  • Diese Felder werden in KiSetProcessorType initialisiert
    • Bei 486-Prozessoren wird das Byte an Offset 18 auf 4 gesetzt
    • Bei Pentium-Prozessoren auf 5
    • Bei Pentium Pro sowie Pentium II/III auf 6
    • Das Byte an Offset 19 ist ein boolesches Flag dafür, ob der Prozessor CPUID unterstützt und sich „vernünftig“ verhält
  • Die problematische Vergleichsinstruktion liest kein Byte an Adresse FFDFF138, sondern ein Word
    • FFDFF138 liegt 18h Byte vom Anfang des KPRCB entfernt
    • Dadurch wird nicht nur CpuType, sondern auch das direkt folgende Byte CpuID gemeinsam als Teil der Modellnummer interpretiert
  • Prozessoren mit CPUID-Unterstützung werden dadurch so behandelt, als wäre ihre Modellnummer um 256 höher als tatsächlich
    • Ein CPUID-fähiger 80-4-86 wird unter Windows NT 3.1 wie ein 80-260-86 behandelt
    • 260 ist deutlich größer als der Pentium-Grenzwert 5, daher nimmt der Kernel an, dass dieser Prozessor ein CR4 besitzen muss
    • Da enhanced 486 kein CR4 haben, tritt der Fehler auf dem Break-in-Pfad auf

Patch-Methode

  • Ist der Bug einmal bestätigt, ist die Korrektur einfach
  • In NTOSKRNL.EXE kommt die Instruktion cmp ds:word_FFDFF138, 5 genau zweimal vor
    • KiSaveProcessorControlState
    • KiRestoreProcessorControlState
  • An beiden Stellen muss der Word-Vergleich auf einen Byte-Vergleich geändert werden
  • In einem Hex-Editor wird dazu zweimal die folgende Bytefolge gepatcht
    • Alt: 66 83 3D 38 F1 DF FF 05
    • Neu: 90 80 3D 38 F1 DF FF 05
  • Diese Änderung funktioniert sowohl für die originale NT-3.1-Advanced-Server-Ausgabe von NTOSKRNL.EXE als auch für NT 3.1 SP3

1 Kommentare

 
GN⁺ 2023-09-29
Hacker-News-Kommentare
  • Ich hatte früher einmal einen Port-Check gebaut, um zu prüfen, ob ein Dienst noch lief, und habe allein dadurch, dass ich ein paar TCP-Ports auf den Maschinen geöffnet habe, die halbe Firma lahmgelegt.
    Das waren absurde Zeiten.

    • Es gab eine Zeit, in der Win95 schon durch einen ping auf eine bestimmte Weise abstürzen oder sogar übernommen werden konnte.
      Wir sind wirklich weit gekommen.
      https://en.wikipedia.org/wiki/Ping_of_death
      Auf manchen Maschinen gab es außerdem SMB-Schwachstellen, die über Jahre nicht gepatcht wurden. Das war bereits zu Zeiten von Metasploit, sodass man mit ein paar Befehlen auf den meisten Windows-Hosts im lokalen Netzwerk VNC einschleusen konnte. Heute geht das Patchen zumindest enorm schnell.
    • Ich glaube, es waren deutlich mehr als nur „ein paar“.
  • Ah, das Kaninchenloch des Retro-Computing. Harmlos von realweltlichen Konsequenzen getrennt, aber trotzdem ungeheuer befriedigend.
    Eine echte Honigfalle für Nerds. Am Ende habe ich doch auf den Button „6 weitere Kommentare anzeigen“ geklickt.

    • Ich habe gesehen, wie mein Cursor auf diesen Aufklapp-Button zusteuerte, und ihn gerade noch weggezogen.
      Vielleicht bekomme ich heute doch noch etwas Arbeit erledigt.
    • Bei Computern aus der Zeit vor meiner, etwa einem PET, denke ich mir, dass es Spaß machen würde, damit herumzuspielen. Eine alte IRIX-Kiste wäre auch okay.
      Aber ein 486? Da kommen zu viele Erinnerungen an Bluescreens und endloses Warten hoch, weil der Rechner den Speicher nicht bewältigen konnte und ständig auf die Platte geswappt hat. Dafür ist es für mich wohl noch zu früh.
    • Der Inhalt des Beitrags scheint auch heute noch in gewissem Maß relevant zu sein.
  • Die Lösung ist offensichtlich“ trifft es wirklich.

    • Mein Matheprofessor sagte, es gebe zwei Arten von Problemen: einfache Probleme und Probleme, die man noch nicht verstanden hat.
    • Sobald der Bug identifiziert ist, stimmt das. Ich frage mich nur, wie man solche Bugs findet.
      Vermutlich hat jemand immer weiter die Disassemblierung des Trap-Codes gelesen, bis das Problem auffiel. Ich glaube nicht, dass QEMU oder andere Debugging-Mittel zur Verfügung standen.
  • Man sollte unbedingt auch die Methode lesen, die in den versteckten Kommentaren am Ende vergraben ist.

    • Das automatische Einklappen von Kommentaren bei Stack Overflow verstehe ich jedes Mal nicht.
      Es werden weder die Kommentare mit den wenigsten Stimmen versteckt, noch die neuesten oder ältesten. Es wirkt zufällig und unnötig.
      Wenn Platz das Problem ist, fände ich Paginierung besser als Ausblenden.
  • Diese Slotket-Adapter, wie sie früher auf Abit-, Asus- und vielleicht MSI-Boards aus Kompatibilitätsgründen genutzt wurden, hatten wirkliches Overclocking-Potenzial.
    Entscheidend waren die RAM-Geschwindigkeit und die Frage, wie weit das Mainboard bei den Multiplikator-Einstellungen den Takt mitmachte. Das waren die relativ frühen Overclocking-Zeiten mit Celeron und Pentium II.
    Ich hatte aus Kompatibilitätsgründen einmal einen Celeron 600 in einen Adapter gesteckt und diesen wiederum in einen Slot-II-auf-Socket-370-Adapter, in einer Konfiguration wie [1], und er lief problemlos mit 1,2 GHz. Ich glaube sogar, dass ich ihn unter Windows ME auf 1,4 GHz gebracht habe; am Ende habe ich die CPU dann durchgebrannt.
    [1]http://krick.3feetunder.com/370mod/

    • Overclocking hat Spaß gemacht, aber es ist auch schön, dass moderne CPUs Takt und Spannung inzwischen so präzise steuern können, dass sie die früheren Sicherheitsreserven ausnutzen.
  • Die Antwort auf die Frage war wirklich hervorragend.
    Gutes Thema und eine sehr gute Antwort.

    • Tatsächlich hat er seine eigene Frage beantwortet. Trotzdem großartig.
  • Schön, dass der Fragesteller auch gleich selbst die Antwort gepostet hat.
    So etwas mag ich. Es freut mich wirklich, dass sich Leute für Retro-Computing interessieren.
    Es ist gut, solche Probleme und ihre Lösungen für alle zu dokumentieren.

  • Ich wusste doch, dass dieses CPUID keine gute Idee ist.

    • Witzig, dass die Windows-Entwickler das Gefühl hatten, eine Sektion „trustworthiness“ in die Struktur aufnehmen zu müssen.
  • Warum hatten Frage und Antwort denselben Zeitstempel?

    • Um die Information zu teilen. Man kann sehen, dass Fragesteller und Antwortender dieselbe Person sind.
      Vermutlich hielt er es für besser, diese Information im Frage-und-Antwort-Format von SO zu veröffentlichen, wo sie länger erhalten bleibt, als in einem Blogpost, den niemand findet.
  • Ist es schlimm, dass ich wusste, worum es geht, ohne es zu öffnen?
    Egal, ich muss jetzt die Kids anschreien, sie sollen von meinem Rasen runter.

    • Problemlösung war anders, als wir aufwuchsen.
      Man hatte nur seinen Verstand und, falls nötig, ein paar Gurus, die man per Festnetztelefon anrufen konnte. Es gab kein wunderbares Internet voller Antworten von höchst unterschiedlicher Nützlichkeit.