Added writeups
This commit is contained in:
174
de/cryptoracle_v1.md
Normal file
174
de/cryptoracle_v1.md
Normal file
@@ -0,0 +1,174 @@
|
||||
# CryptOracle v1
|
||||
|
||||
`CryptOracle v1` ist eine einführende Challenge in Kryptographie und Reverse Engineering, die ein Hardware-Sicherheitsmodul (HSM) simuliert. Das Ziel ist es, eine geheime Flagge abzurufen, die in einem "sicheren" Speicherbereich gespeichert ist, der angeblich vom Benutzer isoliert ist.
|
||||
|
||||
## Informationsbeschaffung
|
||||
|
||||
Wir erhalten ein `cryptOracle_v1.tar.xz` Archiv, das die `crypt_oracle_v1` Binärdatei enthält. Eine erste Analyse bestätigt, dass es sich um eine statisch gelinkte 64-Bit ELF ausführbare Datei handelt.
|
||||
|
||||
```bash
|
||||
$ file crypt_oracle_v1
|
||||
crypt_oracle_v1: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, ...
|
||||
```
|
||||
|
||||
Beim Verbinden mit dem Challenge-Server werden wir mit der folgenden Oberfläche begrüßt:
|
||||
|
||||
```
|
||||
CryptOracle v1.0
|
||||
Setting up memory...
|
||||
0x10000 - 0x11000 : Secure Memory (Keys/ROM)
|
||||
0x20000 - 0x28000 : User Memory
|
||||
Type 'help' for commands.
|
||||
```
|
||||
|
||||
Die Challenge-Beschreibung deutet an, dass sensible Schlüssel an der Adresse `0x10000` gespeichert sind.
|
||||
|
||||
## Reverse Engineering
|
||||
|
||||
Wir verwenden Ghidra, um die Binärdatei zu analysieren und zu verstehen, wie sie den Speicherzugriff verwaltet.
|
||||
|
||||
### 1. Hauptinteraktionsschleife (`main`)
|
||||
|
||||
Die `main`-Funktion initialisiert die HSM-Simulation und verarbeitet Benutzerbefehle.
|
||||
|
||||
```c
|
||||
undefined8 main(void)
|
||||
|
||||
{
|
||||
int iVar1;
|
||||
char *pcVar2;
|
||||
long in_FS_OFFSET;
|
||||
undefined4 local_428;
|
||||
undefined4 local_424;
|
||||
undefined4 local_420;
|
||||
undefined4 local_41c;
|
||||
char local_418 [512];
|
||||
undefined1 local_218 [520];
|
||||
long local_10;
|
||||
|
||||
local_10 = *(long *)(in_FS_OFFSET + 0x28);
|
||||
setvbuf((FILE *)stdout,(char *)0x0,2,0);
|
||||
puts("CryptOracle v1.0");
|
||||
setup_memory();
|
||||
puts("Type \'help\' for commands.");
|
||||
while( true ) {
|
||||
pcVar2 = fgets(local_418,0x200,(FILE *)stdin);
|
||||
if (pcVar2 == (char *)0x0) break;
|
||||
iVar1 = strncmp(local_418,"rm",2);
|
||||
if (iVar1 == 0) {
|
||||
iVar1 = __isoc99_sscanf(local_418,"rm 0x%x %d",&local_420,&local_41c);
|
||||
if (iVar1 == 2) {
|
||||
do_read(local_420,local_41c);
|
||||
}
|
||||
}
|
||||
// ... (andere Befehle) ...
|
||||
}
|
||||
// ...
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
Der `rm`-Befehl nimmt eine hexadezimale Adresse und eine dezimale Größe entgegen und ruft dann die `do_read`-Funktion auf.
|
||||
|
||||
### 2. Speicherinitialisierung (`setup_memory`)
|
||||
|
||||
Die `setup_memory`-Funktion offenbart, wo die Flagge platziert wird.
|
||||
|
||||
```c
|
||||
void setup_memory(void)
|
||||
|
||||
{
|
||||
void *pvVar1;
|
||||
FILE *__stream;
|
||||
size_t sVar2;
|
||||
|
||||
puts("Setting up memory...");
|
||||
pvVar1 = mmap64((void *)0x10000,0x1000,3,0x32,-1,0);
|
||||
// ...
|
||||
pvVar1 = mmap64((void *)0x20000,0x8000,3,0x32,-1,0);
|
||||
// ...
|
||||
memset((void *)0x10000,0,0x1000);
|
||||
memset((void *)0x20000,0,0x8000);
|
||||
__stream = fopen64("flag.bin","rb");
|
||||
if (__stream != (FILE *)0x0) {
|
||||
sVar2 = fread((void *)0x10000,1,0x800,__stream);
|
||||
fclose(__stream);
|
||||
// ...
|
||||
return;
|
||||
}
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
Wir bestätigen, dass die Flagge aus `flag.bin` direkt in den "Secure Memory"-Bereich bei `0x10000` geladen wird.
|
||||
|
||||
### 3. Schwachstellenanalyse (`get_ptr`)
|
||||
|
||||
Die `do_read`-Funktion ruft einen Helfer namens `get_ptr` auf, um den Speicherzugriff zu validieren, bevor Daten gedruckt werden.
|
||||
|
||||
```c
|
||||
void do_read(undefined4 param_1,undefined4 param_2)
|
||||
|
||||
{
|
||||
long lVar1;
|
||||
|
||||
lVar1 = get_ptr(param_1,param_2,0);
|
||||
if (lVar1 == 0) {
|
||||
puts("ERR_ACCESS_VIOLATION");
|
||||
}
|
||||
else {
|
||||
print_hex(lVar1,param_2);
|
||||
}
|
||||
return;
|
||||
}
|
||||
```
|
||||
|
||||
Untersuchen wir nun die Logik in `get_ptr`:
|
||||
|
||||
```c
|
||||
uint get_ptr(uint param_1,int param_2,int param_3)
|
||||
|
||||
{
|
||||
// 1. Integer-Überlaufprüfung: Sicherstellen, dass Adresse + Größe nicht überläuft
|
||||
if (param_2 + param_1 < param_1) {
|
||||
param_1 = 0;
|
||||
}
|
||||
// 2. Bereichsvalidierung: Prüfen, ob der Zugriff innerhalb der gemappten Bereiche liegt
|
||||
else if ((param_1 < 0x10000) || (0x11000 < param_2 + param_1)) {
|
||||
// Wenn nicht im Secure Memory (0x10000-0x11000), prüfe User Memory (0x20000-0x28000)
|
||||
if ((param_1 < 0x20000) || (0x28000 < param_2 + param_1)) {
|
||||
param_1 = 0;
|
||||
}
|
||||
}
|
||||
// 3. Sicherheitsprüfung: Blockiere Zugriff auf den Flaggenbereich (0x10000 - 0x10800)
|
||||
// Dies wird NUR erzwungen, wenn param_3 (check_secure) ungleich Null ist
|
||||
else if ((param_3 != 0) && (param_1 - 0x10000 < 0x800)) {
|
||||
param_1 = 0;
|
||||
}
|
||||
return param_1;
|
||||
}
|
||||
```
|
||||
|
||||
Die Validierungslogik in `get_ptr` funktioniert wie folgt:
|
||||
1. **Integer-Überlauf**: Es wird geprüft, ob `size + addr` überläuft.
|
||||
2. **Bereichsvalidierung**: Es wird sichergestellt, dass der Zugriff innerhalb des Secure Memory (`0x10000-0x11000`) oder User Memory (`0x20000-0x28000`) liegt.
|
||||
3. **Sicherheitsbeschränkung**: Es wird explizit der Zugriff auf die ersten `0x800` Bytes des Secure Memory (wo die Flagge gespeichert ist) blockiert, **NUR WENN** `param_3` (das `check_secure`-Flag) ungleich Null ist.
|
||||
|
||||
Entscheidend ist, dass in der `do_read`-Funktion (die den `rm`-Befehl implementiert) das dritte Argument, das an `get_ptr` übergeben wird, **fest auf `0` gesetzt ist**. Das bedeutet, dass die spezifische Prüfung, die die geheimen Schlüssel schützt, umgangen wird, wenn der Befehl "read memory" verwendet wird.
|
||||
|
||||
## Lösung
|
||||
|
||||
Da der `rm`-Befehl die Isolationsprüfung in `get_ptr` umgeht, können wir das sichere RAM direkt dumpen.
|
||||
|
||||
1. Mit dem Server verbinden.
|
||||
2. Den Speicher an der Adresse `0x10000` lesen.
|
||||
|
||||
```bash
|
||||
> rm 0x10000 32
|
||||
00010000: 7b 66 6c 61 67 3a 20 74 68 61 74 5f 77 61 73 5f {flag: that_was_
|
||||
00010010: 74 6f 6f 5f 65 61 73 79 5f 72 69 67 68 74 3f 7d too_easy_right?}
|
||||
```
|
||||
|
||||
Die Flagge wird enthüllt: `{flag: that_was_too_easy_right?}`
|
||||
|
||||
```
|
||||
Reference in New Issue
Block a user