# CryptOracle v2 `CryptOracle v2` ist die "gehärtete" Version des HSM-Simulators. Die Schwachstelle des direkten Speicherlesens aus v1 wurde behoben, aber eine neue "Kryptographische Engine" wurde hinzugefügt. Das Ziel ist dasselbe: den geheimen Schlüssel aus dem sicheren Speicherbereich bei `0x10000` zu dumpen. ## Informationsbeschaffung Nachdem wir uns mit dem Server verbunden haben, sehen wir eine vertraute Oberfläche mit neuen Befehlen wie `enc` und `dec`. ``` CryptOracle v2.0 (Hardened!) Setting up memory... 0x10000 - 0x11000 : Secure Memory (Keys/ROM) 0x20000 - 0x28000 : User Memory Type 'help' for commands. ``` ## Reverse Engineering Wir öffnen die Binärdatei in Ghidra und überprüfen die Funktionen, die sich geändert haben. ### 1. Der Patch (`do_read`) Die `do_read`-Funktion enthält jetzt eine explizite Prüfung, die jeden Versuch blockiert, von Adressen unterhalb des Benutzerspeicherbereichs zu lesen. ```c void do_read(uint32_t offset,uint32_t len) { uint8_t *data; // Diese Prüfung verhindert, dass wir 0x10000 direkt lesen. if (offset < 0x20000) { puts("ERR_ACCESS_VIOLATION"); } else { // ... (Rest der Funktion) } return; } ``` Das einfache `rm 0x10000 32` aus v1 funktioniert nicht mehr. ### 2. Die Kryptographische Engine (`do_cipher`) Da die Schwachstelle des direkten Lesens aus v1 gepatcht wurde, müssen wir nun andere Befehle als potenzielle Angriffsfläche untersuchen. Die Befehle `enc` und `dec`, die zuvor schon vorhanden waren, rücken nun in unseren Fokus. Sie werden von der `do_cipher`-Funktion behandelt. Sie ermöglicht das Verschlüsseln oder Entschlüsseln von Daten von einer Quelladresse zu einer Zieladresse unter Verwendung eines Schlüssels, der in einem sicheren "Slot" gespeichert ist. ```c void do_cipher(char mode, uint32_t src_off, uint32_t len, int slot, uint32_t dst_off) { // ... // Zeiger auf Quellpuffer abrufen (kann überall sein) src_ptr = get_ptr(src_off, len, 0); // Zeiger auf Zielpuffer abrufen (muss beschreibbar sein) dst_ptr = get_ptr(dst_off, len, 1); // Zeiger auf den Schlüssel aus einem sicheren Slot abrufen // Adresse wird berechnet als 0x10000 + slot * 32 key_ptr = get_ptr((slot + 0x800) * 0x20, 0x20, 0); if (/* alle Zeiger sind gültig */) { // ... führt AES-Verschlüsselung/-Entschlüsselung blockweise durch ... } } ``` Die wichtigste Erkenntnis liegt darin, wie die Zeiger validiert werden, was alles innerhalb der `get_ptr`-Funktion geschieht. ### 3. Schwachstellenanalyse (`get_ptr`) Hier ist die dekompilierte `get_ptr`-Funktion aus Ghidra. Sie nimmt einen Offset, eine Länge und ein Flag `is_write` entgegen, das `1` für Schreiboperationen und `0` für Leseoperationen ist. ```c uint8_t * get_ptr(uint32_t offset,uint32_t len,int is_write) { uint8_t *puVar1; if (len + offset < offset) { puVar1 = (uint8_t *)0x0; } else if ((offset < 0x10000) || (0x11000 < len + offset)) { if ((offset < 0x20000) || (0x28000 < len + offset)) { puVar1 = (uint8_t *)0x0; } else { puVar1 = (uint8_t *)(ulong)offset; } } else if ((is_write == 0) || (0x7ff < offset - 0x10000)) { puVar1 = (uint8_t *)(ulong)offset; } else { puVar1 = (uint8_t *)0x0; } return puVar1; } ``` **Die Schwachstelle:** Die Logik im letzten `else if`-Block ist fehlerhaft. Verfolgen wir sie für eine Leseoperation (`is_write == 0`) im sicheren Flaggenbereich (`offset = 0x10000`). * Der Ausdruck ist `(is_write == 0) || (0x7ff < offset - 0x10000)`. * Da `is_write` `0` ist, ist der erste Teil des ODER `(0 == 0)` **wahr**. * Der gesamte Ausdruck wird `wahr`, und der Zugriff wird gewährt, wodurch ein gültiger Zeiger zurückgegeben wird. Die Prüfung, die das Schreiben in die ersten 2KB des sicheren Speichers (`0x7ff < offset - 0x10000`) verhindern sollte, wird für jede Leseoperation vollständig umgangen. Die `do_cipher`-Funktion missbraucht dies, indem sie ein Lesen (`is_write=0`) auf der sicheren Flagge anfordert, was `get_ptr` erlaubt. Dies erzeugt ein **Confused Deputy**-Szenario, bei dem wir die legitimen Leseberechtigungen des Orakels nutzen, um Daten zu exfiltrieren. ## Lösung Der Angriff ist ein zweistufiger Prozess: 1. **Verschlüsseln**: Verwenden Sie den `enc`-Befehl, um das Orakel anzuweisen, aus dem sicheren Flaggenbereich (`0x10000`) zu lesen und das verschlüsselte Ergebnis (Chiffretext) in den benutzerzugänglichen Speicher (`0x20000`) zu schreiben. 2. **Entschlüsseln**: Verwenden Sie den `dec`-Befehl, um das Orakel anzuweisen, den Chiffretext aus dem Benutzerspeicher (`0x20000`) zu lesen und das entschlüsselte Ergebnis (Klartext) zurück an eine andere Stelle im Benutzerspeicher (`0x20100`) zu schreiben. 3. **Lesen**: Verwenden Sie den jetzt reparierten `rm`-Befehl, um die Klartext-Flagge von `0x20100` zu lesen. ### Ausführung ```bash # Schritt 1: Verschlüsseln der Flagge aus dem sicheren Speicher in den Benutzerspeicher > enc 0x10000 32 30 0x20000 OK # Schritt 2: Entschlüsseln des Chiffretextes aus dem Benutzerspeicher an eine andere Stelle im Benutzerspeicher > dec 0x20000 32 30 0x20100 OK # Schritt 3: Lesen der Klartext-Flagge aus dem Benutzerspeicher > rm 0x20100 32 00020100: 7b 66 6c 61 67 3a 20 73 65 6c 66 5f 65 6e 63 72 {flag: self_encr 00020110: 79 70 74 69 6f 6e 5f 69 73 5f 6e 69 63 65 21 7d yption_is_nice!} ``` Die Flagge wird enthüllt: `{flag: self_encryption_is_nice!}`