• Hallo Gast!
    Noch bis zum 20.07. kannst Du an unserer Umfrage zum Hersteller des Jahres teilnehmen! Als Gewinn verlosen wir unter allen Teilnehmern dieses Mal eine Grafikkarte Eurer Wahl für bis zu 1.000 EUR - über eine Teilnahme würden wir uns sehr freuen!

Leserartikel: PCI Option Rom Modding - Portierung und Reverse Engineering von AMT128

Tzk

Sockel A Legende
Thread Starter
Mitglied seit
13.02.2006
Beiträge
24.137
Ort
Koblenz
Ich eröffne diesen Thread, um meine Software-Basteleien aus dem DDR3 IC Binning Thread auszulagern und dort nicht alles zu fluten.

Achtung: Wall of Text! Es ist doch länger geworden als ich erwartet habe. Außerdem läuft es (noch) nicht, sprich wenn ich weiter mache seid ihr quasi live dabei ;)

Worum geht's hier?​

Konkret möchte ich versuchen die RAM-Chip Test-Software AMT128 von einer PCIe Karte auf eine andere zu portieren. Das hört sich (leider) einfacher an als es ist... Dieser Thread dient einerseits als Dokumentation für mich, andererseits möchte ich euch zeigen wie tief ans Eingemachte es bei so einem Projekt gehen kann.

Warum kaufst du nicht einfach so ne blöde Karte oder nutzt MemTest86+ und gut ist?​

Gute und berechtigte Frage. Zum einen bin ich zu geizig 50€ dafür auszugeben und zum anderen will ich euch einen Einblick geben, was es heißt wenn "wir" (die üblichen Verdächtigen) BIOSe modden. Außerdem schreibe ich für mich selbst Doku, damit ich in ein paar Jahren noch weiß was ich getan habe... :d Drittens ist es eine Fingerübung, um das Reverse Engineering zu üben und so in Zukunft Biose besser modden zu können. Fangen wir mal ganz einfach an, die erste Frage lautet:

Was ist ein PCI Option Rom überhaupt?​

AMT128 wird in Form einer PCI Express Netzwerkkarte (mit Broadcom Chip) geliefert, auf der die originale Firmware ersetzt wurde. Steckt die Karte im System, wird die Firmware gelesen und gestartet, was dann AMT128 ausführt. Diese Firmware heißt PCI Option Rom und kann im Prinzip beliebiger Programmcode sein. Theoretisch könnte man auch Tetris oder ein komplettes OS auf die Karte speichern, sofern genug Platz vorhanden ist.

Damit diese Firmware überhaupt vom Bios ausgeführt wird müssen ein paar Bedingungen erfüllt sein. Die wichtigsten Zwei sind:
  1. Jedes PCI Option Rom muss mit einem Magic Byte (0x55AA) beginnen, um vom Bios des Mainboards als Option Rom erkannt zu werden
  2. Jedes PCI Gerät verfügt über eine Vendor-ID und Device-ID, diese müssen passend zum Gerät im Option Rom hinterlegt sein

Wie erkennt und bootet das Bios nun das Option Rom?​

Jedes PCI Gerät besitzt neben dem Option Rom ein paar Bytes an Konfigurationsdaten. Diese sind meist fest in den Chip eingespeichert und teils nicht veränderlich. Diese Daten bzw. der Speicherbereich heißt PCI Configuration Space. Dort ist für jedes PCI Gerät die Vendor-ID und Device-ID sowie weitere Daten hinterlegt. Das Bios liest beim booten die Daten von allen PCI Geräten ein. Außerdem werden alle Option Roms gelesen. Erkennt das Mainbaord Bios, dass ein Option Rom mit identischer Vendor- und Device-ID wie ein erkanntes PCI Gerät vorhanden ist, wird das Option Rom ausgeführt. In diesem Fall nutzt AMT wie erwähnt eine Broadcom LAN-Karte. Diese hat die Vendor-ID 0x11AB und die Device ID 0x45B9. 11AB ist übrigens dem Hersteller Marvell zugeordnet.

OK, verstanden. Und was jetzt?​

Die grundsätzliche Idee ist nun das AMT Rom von der Broadcom Karte auszulesen, die IDs anzupassen und es dann auf eine andere Karte zu flashen. (Spoiler: So leicht wird's nicht... :fresse: )

Dankenswerterweise hat @TAGG dies bereits erledigt und so habe ich eine 132kib große Binärdatei, die das AMT Rom darstellt. Außerdem gab es im HWBot Discord noch den Fingerzeig, das das Flash-Rom auf der Broadcom Karte merkwürdig funktioniert und aus unerfindlichen Gründen Speicherblöcke mit je 264bytes statt 256bytes besitzt. Das ist uns letztendlich egal, weil wir die Daten bereits haben aber ein nettes Detail am Rande... Als nächstes öffnen wir das Rom also im Hexeditor und schauen mal rein.

Der erste Blick ins AMT Rom​

Als erstes wird Handwerkszeug benötigt. Ich nutze im Verlauf folgende Tools:
  1. den Hex-Editor HxD um die Daten anzusehen
  2. den Emulator QEMU um das Option Rom zu booten
  3. den Disassembler Ghidra um das Rom in lesbaren Code zu wandeln
Wir starten hier erstmal mit einem Blick auf das unveränderte Rom, dazu nutze ich gerne HxD. Und das schaut so aus:

1753014654938.png


Hier fiel mir als erstes auf, dass das Rom mit 0x6699 beginnt. Das verstößt gegen den Standard, da jedes Option Rom (wie oben erwähnt) mit 0x55AA starten müsste. Außerdem sieht man auf der rechten Seite mehrmals "Broadcom" bzw. "BCM" als dekodierten Text. Hier stimmt folglich etwas nicht und ich wurde stutzig. Gemäß Standard müsste außerdem direkt hinter dem Magic Byte die Größe des Roms kodiert sein, auch das ist hier nicht der Fall.

Eine Suche nach 0x55AA führte dann weitere Stellen im Rom zu Tage:

1753014669170.png

Aha, das schaut schon besser aus. Hier steht immerhin was von AMT128, das Magic Byte ist vorhanden und es ist sogar eine Größe angegeben. Die Größe des Roms wird gemäß PCI Standard in Vielfachen von 512 Bytes angegeben. 0x80 ist hexadezimal und entspricht damit umgerechnet 128 Blöcken zu jeweils 512 Bytes. Das sind folglich 64kib. Das ist insofern interessant, als das das Rom insgesamt 132kib groß ist und von vorne bis hinten gefüllt mit Daten.

An dieser Stelle habe ich beschlossen alles vor dem gezeigten Magic Byte (Offset 0x1F50) und alles was hinter der angegebenen Größe des Roms (64kib) liegt als erstes zu löschen. Ich gehe davon aus, das das einfach Reste des Broadcom Roms sind, die nicht vom AMT Entwickler überschrieben wurden. Das bedeutet also, dass wir alles von Offset 0 bis 0x1F50 und alles nach Offset 0x11F50 entsorgen. 0x11F50 setzt sich zusammen aus dem Beginn und der Größe des Roms (0x1F50 + 0x1000). Man glaubt es kaum, aber bei 0x11F50 ist genau eine Lücke in den Daten :bigok:

1753014685805.png


Wir haben nun also ein "neues" Option Rom mit der Größe von exakt 64kib und einem auf den ersten Blick korrekten Header.

PCI Option Roms sind generell mit einer Checksumme versehen. Diese berechnet sich aus der Summe aller Bytes des Roms modulo 256. Bleibt bei dieser Rechnung ein Rest, wird das Rom nicht gestartet. Glücklicherweise bietet mein Hexeditor die Funktion "Checksum-8", die genau diese Rechnung ausführt und das Ergebnis präsentiert. Ist das Ergebnis nicht Null, muss irgendwo im Rom ein Byte angepasst werden, damit das Ergebnis Null wird. Welches Byte dafür genommen wird ist prinzipiell egal, es sollte nur den Programmcode nicht ändern. Ich habe mich für ein Byte ziemlich am Anfang des Roms entschieden (0xF) und nutze dieses als Checksummen-Byte. Also Flott Checksum-8 ausgeführt, das Ergebnis von 0x100 (256) abgezogen und dort eingetragen.

Jetzt ist ein guter Zeitpunkt um mal zu schauen was QEMU zu diesem Rom sagt.


Erster Start in QEMU​

QEMU ist ein sehr praktisches Tool, um (fast) beliebigen Code in einer VM zu starten. Es implementiert ein eigenes Bios und kann auch Options Roms laden. Genau das tun wir jetzt:

Code:
"%programfiles%\qemu\qemu-system-x86_64.exe" -option-rom "PFAD_ZUM_ROM\AMT.bin" -net none


Das Ergebnis schaut halbwegs vielversprechend aus. Generell scheint QEMU das Rom zu erkennen und das Rom scheint auch zu starten. Leider folgt nach dem Splash Screen von AMT nur noch ein drehender Balken, es bootet also nicht korrekt durch. Hier hilft nur noch ein Disassemblierer und eine tiefgreifende Analyse des Roms weiter.

1753014737892.png
1753014741010.png



Exkurs: PCI Option Rom Header und Plug&Play Header​

Jetzt ist ein guter Zeitpunkt die PCI Specification und die Plug&Play Spec zu studieren. Ghidra wandelt zwar Bytes in menschenlesbaren Assembler um, muss dabei aber unterstützt werden. Unter anderen muss der Einprungpunkt in den Code gefunden werden, von dem aus Ghidra dann anfängt den Code zu analysieren. Eingangs war bereits der PCI Option Rom Header erwähnt, dieser und der P'n'P Header beinhalten auch den Einsprungpunkt. Im folgenden Screenshot sind die ersten Bytes des AMT Rom - geöffnet im Hex Editor - zu sehen.

Vier für uns relevante Stellen sind hier gelb markiert:
1. EB 53 -> Direkt auf das Magic Byte 55AA folgt die Größe des Roms 80 und darauf die erste Instruction, hier ein Jump um 0x53 Bytes nach vorne: JMP 0x58
2. 5901 -> Dies ist ein im $PNP Header gespeicherter Offset, der vermutlich als zweiter Einsprungpunkt dient
3. $PNP und PCIR -> zwei Magic Bytes, die dem Bios sagen to der Plug&Play Header und der PCI Option Rom Header starten

1753014762224.png


Wofür genau welche Bytes zuständig sind und welche Werte sie in unserem Fall haben packe ich mal in den Spoiler:

Code:
Hex Offset  Wert im AMT Rom        Kommentar

0x00        0x55AA                 Magic Byte, das die Daten als Option Rom ausweist
0x02        0x80                   Länge des Option Roms in 512byte Blöcken = 64kib insgesamt
0x03        0xEB53                 Einsprungpunkt für Programmcode. Hier: JMP 0x53

0x18        0x40                   PCIR Header Beginn (Offset) im Option Rom
0x1A        0x20                   PNP-Header Beginn (Offset) im Option Rom

0x20        0x24506e50 ("$PNP")    Magic Byte für den Plug and Play Header (PNP-Header)
0x24        0x01                   PNP-Header Version
0x25        0x02                   PNP-Header Länge
0x29        0x38                   PNP-Header Checksumme
0x2E        0xA5                   Offset für Device Name String
0x30        0xAE                   Offset für Device Hersteller String
0x35        0x04                   Device Typ (LAN Karte,...)
0x36        0x00                   PNP-Header Boot Connection Vector (BCV)
0x38        0x00                   PNP-Header Boot Disconnet Vector
0x3A        0x5901                 PNP Bootstrap Entry Vector (hier: Offest 0x159)

0x40        0x50434952 ("PCIR")    Magic Byte für den PCIR Header
0x44        0xAB11                 Vendor ID
0x46        0xB945                 Device ID

Erste Analyse in Ghidra / der erste Einsprungpunkt​

Das Rom bootet generell, aber hängt dann. Das ist ein guter Start, bedarf aber weiterer Untersuchung. Meine Vermutung ist an diesem Punkt, das die AMT Entwickler eine Art Check oder Kopierschutz eingebaut haben, der den vollständigen Start verhindert. Wie genau, das gilt es herauszufinden. Zwei Einsprungpunkte, an denen das PC Bios das Ausführen des Roms beginnt haben wir oben bereits gefunden, an diesen Stellen setzen wir nun an: JMP 0x58 und Offset 0x159 .

Es sei noch erwähnt, dass alles an Benamungen von mir stammt. Ich habe mit die Routinen, Unterprogramme und Sprünge angesehen, diese analysiert und dann basierend auf meinen Vermutungen Namen vergeben. Also diese bitte mit etwas Vorsicht lesen, ich kann mich schließlich auch irren...

1753014807242.png



1753014811331.png



In den obigen Screenshots sieht man den ersten Einsprung. Gestartet wird beim pinken entry_point und es folgt ein Sprung in den eigentlichen Code. Dieser wartet erst kurz, manipuliert dann einen Pointer und sucht anschließend nach dem $PNP Header. Danach folgt je nach Ergebnis (Header Byte gefunden oder nicht) eine Verzweigung. In beiden Fällen wird der Splash Screen angezeigt, aber nur der untere von beiden sorgt dafür, dass der nächste Sprung zu Programmpunkt 0x159 erfolgt. Da das gesamte Rom mit einer Call Instruction vom Bios aufgerufen wurde, sorgen die beiden RETF (return far) Instructions dafür, dass das Rom verlassen und die Kontrolle wieder ans Bios übertragen wird.

Ich vermute diese Routine dient zur Identifikation vom Plug&Play Support des Bios. Unterstützt das Bios Plug&Play, dann übergibt AMT einfach wieder ans Bios und wartet auf das Einlesen des P'n'P Headers (dort ist 0x159 als Einsprungpunkt vermerkt). Unterstützt das Bios kein P'n'P, dann setzt AMT den Einsprungpunkt selbst und übergibt anschließend. Dies ist also erstmal eine Sackgasse, aber zeigt uns, das wir uns Offset 0x159 ansehen sollten.


Analyse des zweiten Einsprungpunkts​

Der nächste Blick muss folglich auf diesen Offset erfolgen. Und das schaut in Ghidra so aus:

1753014833177.png


Wir dröseln das mal kurz auf:
Ganz oben springen wir ein. Dann werden je zwei Bytes, die an Offset 0x44 und 0x46 im Rom gespeichert sind in das DX Register geladen. Etwas weiter oben sind diese beiden Bytes bereits gelistet, denn es sind die Vendor-ID und Device-ID der Broadcom LAN Karte auf der AMT gespeichert ist. Gelb markiert folgt dann eine Schleife, welche (und hier wird es interessant!) folgendes tut:

0. Die Anzahl an Bytes und der Offset im Rom wurde der Schleife vorab mitgegeben:
- Start Offset: SI, 0x2000
- Ziel Adresse: DI, 0x100
- Größe des Roms: CX, 0x8800

1. Nun wird ein Byte des Roms in das Register AL geladen
2. XOR Operation mit dem DL Register (hier ist die Device ID drin!)
3. das geladene und verrechnete Byte in den Ram (Offset 1000:8100) schreiben
3. die Bits im EDX Register eins nach rechts rotieren
5. das ganze Wiederholen bis alle Bytes kopiert sind

Das klingt alles erstmal harmlos, ist aber im Prinzip eine XOR-Verschlüsselung, die beim Kopieren des weiteren Programmcodes gleichzeitig eine Entschlüsselung durchführt. Der Schlüssel ist dabei die Vendor- und Device-ID. Portiert man das Rom nun auf eine andere Karte - dafür müssen diese IDs angepasst werden - scheitert die Entschlüsselung. Es ist also eine Art einfacher Kopierschutz.

An dieser Stelle hatte ich als erstes die Idee einfach andere Bytes als Schlüssel zu laden. Somit könnten die eigentlichen IDs des Roms geändert werden, das Rom würde trotzdem booten und die Entschlüsselung würde mit den alten Broadcom IDs (anderswo gespeichert) trotzdem klappen. Diese Methode hat aber den entscheidenden Nachteil, das der eigentliche Programmcode weiterhin verschlüsselt im Rom abgelegt ist. Sollten dort also noch weitere Checks oder “Überraschungen” im verschlüsselten Teil warten, kann man das nicht analysieren.

Als zweite Idee könnte man den Inhalt entschlüsseln und im Klartext ins Rom speichern. Die Schleife welche aktuell entschlüsselt und kopiert müsste dann auf “nur kopieren” gepatcht werden. Und genau das folgt jetzt :d


Entschlüsselung des Roms​

Nun ist uns bekannt wie der nächste Teil des Roms zu entschlüsseln ist und wo es beginnt. Also habe ich mir (als Hobbyist) das Leben etwas leichter gemacht und die KI meiner Wahl befragt, ob sie mir ein kleines Skript bauen kann, das entschlüsselt. Und tatsächlich habe ich ein Python Skript erhalten, das den Schlüssel ausliest und dann die entschlüsselten Daten separat speichert.

Python:
def ror32(val, bits):
    return ((val >> bits) | (val << (32 - bits))) & 0xFFFFFFFF

# === Load binary ===
with open("AMT64.BIN", "rb") as f:
    data = bytearray(f.read())

# === Step 1: Extract the key ===
# Assuming file is a flat 64K real-mode image
# and CS = 0x0000
high = int.from_bytes(data[0x44:0x46], "little")
low  = int.from_bytes(data[0x46:0x48], "little")
key = (high << 16) | low

print(f"Initial XOR key: {key:08X}")

# === Step 2: Extract the payload ===
offset = 0x2000
length = 0x8800
encrypted = data[offset:offset+length]
decrypted = bytearray()

# === Step 3: Decrypt ===
for b in encrypted:
    decrypted_byte = b ^ (key & 0xFF)
    decrypted.append(decrypted_byte)
    key = ror32(key, 1)

# === Step 4: Save result ===
with open("decrypted_payload.bin", "wb") as f:
    f.write(decrypted)

print("Decryption complete. Payload written to decrypted_payload.bin")


Die zweite Sache die erledigt werden muss ist das rauspatchen der XOR-Entschlüsselung. Das gelingt recht einfach, da es die NOP Instruktion (no operation) gibt. Diese lautet 0x90. Wir "NOPen" also einfach alles was wir nicht haben wollen weg und gut ist. Es entfällt also das Entschlüsseln und Rotieren, kopiert werden die Daten natürlich trotzdem.

1753014884384.png


Damit das funktioniert, muss natürlich das entschlüsselte Ergebnis ins Rom kopiert werden. Der weitere Code im Screenshot zeigt auch direkt wie es weiter geht: Der Block an MOV Instruktionen bereitet einen Sprung in das entschlüsselte Rom vor, das nun im Ram des Systems liegt. Die letzte Instruktion JMPF payload_entry_point führt dann den Sprung aus.

An dieser Stelle machen wir erstmal eine Pause und testen das Rom. Leider (oder glücklicherweise?) bleibt es weiterhin beim Splash Screen und danach dem rotierenden Balken. Ich gehe daher davon aus, dass der aktuelle Code soweit läuft, aber irgendetwas den (ehemals verschlüsselten) Block davon abhält zu starten. Ich habe testweise bereits das PXE Option Rom von meinem DFI Ultra-D mit dem AMT Rom ersetzt und bekomme dort das gleiche Verhalten. Es liegt also vermutlich nicht an der Emulation in QEMU.

Auch erhalte ich eine Meldung "Please use 64bit Cpu", wenn ich QEMU mit 32bit Cpu starte. Diese Meldung stammt aus dem ehemals verschlüsselten Teil, dieser wird also definitiv ausgeführt.

Analyse der entschlüsselten Payload​

Ein Test des Roms steht noch aus, aber dennoch habe ich mir den Code des entschlüsselten Roms vorab angesehen:

1753014899991.png


Hier zu sehen ist anz oben der Einsprung in die entschlüsselte Payload. Darauf folgt eine Abfrage der CPUID und von gewissen Features die die Cpu für AMT mitbringen soll (u.a. 64bit Unterstützung). Interssant wird es im unteren Drittel, den dort erfolgt erneut eine Abfrage der Vendor- und Device-ID. Hinter dem CALL verbirgt sich nämlich die folgende Schleife:

1753014915217.png



Das Entscheidende ist hier MOV ECX,0x5710de denn das ist mal wieder die ID... AMT nutzt diese um den PCI Configuration Space zu durchsuchen und die exakte PCI Adresse der LAN Karte zu finden. Ist sie falsch, dann läuft AMT vermutlich auf einen Fehler.

Mit diesem Wissen habe ich die ID bereits angepasst und werde nun im Anschluss versuchen das ROM nochmals auf realer Hardware zu booten. Unter QEMU wird es wie oben gezeigt nicht klappen, weil QEMU dem Test-Device keine IDs zuweist. Das erklärt dann auch weshalb AMT unter QEMU nicht durchstarten will...

Bis hierher haben wir eine lange Reise hinter uns und ich mache erstmal eine Pause, um das Rom zu testen. Ich habe viel über das Reverse-Engineering gelernt und bin erstaunt wie weit ich mit diesem Projekt gekommen bin. Wer bis hierher gelesen hat und folgen konnte: Hut ab! Ich bin zwischendurch schier verzweifelt, weil man sooo schnell den Faden verliert und sich selbst verzettelt.
 
Hardwareluxx setzt keine externen Werbe- und Tracking-Cookies ein. Auf unserer Webseite finden Sie nur noch Cookies nach berechtigtem Interesse (Art. 6 Abs. 1 Satz 1 lit. f DSGVO) oder eigene funktionelle Cookies. Durch die Nutzung unserer Webseite erklären Sie sich damit einverstanden, dass wir diese Cookies setzen. Mehr Informationen und Möglichkeiten zur Einstellung unserer Cookies finden Sie in unserer Datenschutzerklärung.


Zurück
Oben Unten refresh