134 lines
5.3 KiB
Markdown
134 lines
5.3 KiB
Markdown
# 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!}`
|