BROP - Blind Return Oriented Programming
Podstawowe informacje
Celem tego ataku jest wykorzystanie ROP poprzez przepełnienie bufora bez żadnych informacji o podatnym binarnym pliku. Ten atak opiera się na następującym scenariuszu:
Podatność stosu i znajomość sposobu jej wywołania.
Aplikacja serwerowa, która restartuje się po awarii.
Atak
1. Znajdź podatny offset wysyłając jeden dodatkowy znak, aż zostanie wykryta awaria serwera
2. Bruteforce canary aby wyciekło go
3. Bruteforce przechowywane adresy RBP i RIP na stosie, aby je wyciec
Więcej informacji na temat tych procesów można znaleźć tutaj (BF Forked & Threaded Stack Canaries) oraz tutaj (BF Addresses in the Stack).
4. Znajdź gadżet stop
Ten gadżet pozwala potwierdzić, że coś interesującego zostało wykonane przez gadżet ROP, ponieważ wykonanie nie spowodowało awarii. Zazwyczaj będzie to coś, co zatrzymuje wykonanie i znajduje się na końcu łańcucha ROP podczas poszukiwania gadżetów ROP do potwierdzenia, że określony gadżet ROP został wykonany.
5. Znajdź gadżet BROP
Ta technika wykorzystuje gadżet ret2csu. Jest to dlatego, że jeśli uzyskasz dostęp do tego gadżetu w środku niektórych instrukcji, otrzymasz gadżety do kontrolowania rsi
i rdi
:
Są to gadżety:
pop rsi; pop r15; ret
pop rdi; ret
Zauważ, że dzięki tym gadżetom możliwe jest kontrolowanie 2 argumentów funkcji do wywołania.
Zauważ również, że gadżet ret2csu ma bardzo unikalny podpis, ponieważ będzie zdejmował 6 rejestrów ze stosu. Wysyłając łańcuch tak jak:
'A' * offset + canary + rbp + ADDR + 0xdead * 6 + STOP
Jeśli STOP jest wykonany, oznacza to, że został użyty adres, który zdejmuje 6 rejestrów ze stosu. Lub że użyty adres był również adresem STOP.
Aby usunąć tę ostatnią opcję, wykonuje się nowy łańcuch jak poniżej i nie powinien on wykonać gadżetu STOP, aby potwierdzić, że poprzedni zdejmował 6 rejestrów:
'A' * offset + canary + rbp + ADDR
Znając adres gadżetu ret2csu, możliwe jest wywnioskowanie adresów gadżetów do kontrolowania rsi
i rdi
.
6. Znajdź PLT
Tabelę PLT można przeszukiwać od 0x400000 lub od wyciekanego adresu RIP ze stosu (jeśli jest używany PIE). Wpisy w tabeli są oddzielone o 16B (0x10B), i gdy wywoływana jest jedna funkcja, serwer nie ulega awarii nawet jeśli argumenty nie są poprawne. Sprawdzenie adresu wpisu w PLT + 6B również nie powoduje awarii, ponieważ jest to pierwszy kod wykonany.
Dlatego możliwe jest znalezienie tabeli PLT, sprawdzając następujące zachowania:
'A' * offset + canary + rbp + ADDR + STOP
-> brak awarii'A' * offset + canary + rbp + (ADDR + 0x6) + STOP
-> brak awarii'A' * offset + canary + rbp + (ADDR + 0x10) + STOP
-> brak awarii
7. Znajdź strcmp
Funkcja strcmp
ustawia rejestr rdx
na długość porównywanego łańcucha. Zauważ, że rdx
jest trzecim argumentem i musi być większy niż 0, aby później można było użyć write
do wycieku programu.
Możliwe jest znalezienie lokalizacji strcmp
w PLT na podstawie jego zachowania, wykorzystując fakt, że teraz możemy kontrolować 2 pierwsze argumenty funkcji:
strcmp(<nieodczytywalny adres>, <nieodczytywalny adres>) -> awaria
strcmp(<nieodczytywalny adres>, <odczytywalny adres>) -> awaria
strcmp(<odczytywalny adres>, <nieodczytywalny adres>) -> awaria
strcmp(<odczytywalny adres>, <odczytywalny adres>) -> brak awarii
Można to sprawdzić, wywołując każdy wpis w tabeli PLT lub korzystając z wolnej ścieżki PLT, która polega w zasadzie na wywołaniu wpisu w tabeli PLT + 0xb (które wywołuje dlresolve
) a następnie na stosie numeru wpisu, który chcemy sprawdzić (zaczynając od zera), aby przeskanować wszystkie wpisy PLT od pierwszego:
strcmp(<nieodczytywalny adres>, <odczytywalny adres>) -> awaria
b'A' * offset + canary + rbp + (BROP + 0x9) + RIP + (BROP + 0x7) + p64(0x300) + p64(0x0) + (PLT + 0xb ) + p64(ENTRY) + STOP
-> Spowoduje awarięstrcmp(<odczytywalny adres>, <nieodczytywalny adres>) -> awaria
b'A' * offset + canary + rbp + (BROP + 0x9) + p64(0x300) + (BROP + 0x7) + RIP + p64(0x0) + (PLT + 0xb ) + p64(ENTRY) + STOP
strcmp(<odczytywalny adres>, <odczytywalny adres>) -> brak awarii
b'A' * offset + canary + rbp + (BROP + 0x9) + RIP + (BROP + 0x7) + RIP + p64(0x0) + (PLT + 0xb ) + p64(ENTRY) + STOP
Pamiętaj, że:
BROP + 0x7 wskazuje na
pop RSI; pop R15; ret;
BROP + 0x9 wskazuje na
pop RDI; ret;
PLT + 0xb wskazuje na wywołanie dl_resolve.
Znalezienie strcmp
umożliwia ustawienie rdx
na wartość większą niż 0.
Zazwyczaj rdx
będzie już zawierał wartość większą niż 0, więc ten krok może nie być konieczny.
### 8. Znajdowanie funkcji Write lub jej odpowiednika
W końcu potrzebny jest gadżet, który eksfiltruje dane w celu wyeksportowania binarnego. W tym momencie możliwe jest kontrolowanie 2 argumentów i ustawienie rdx
większego niż 0.
Istnieją 3 wspólne funkcje, które mogą być wykorzystane do tego celu:
puts(data)
dprintf(fd, data)
write(fd, data, len(data)
Jednakże, oryginalny dokument wspomina tylko o funkcji write
, więc porozmawiajmy o niej:
Aktualny problem polega na tym, że nie znamy gdzie znajduje się funkcja write w PLT i nie znamy numeru fd, aby wysłać dane do naszego gniazda.
Jednakże, znamy gdzie znajduje się tabela PLT i możliwe jest znalezienie write na podstawie jego zachowania. Możemy utworzyć kilka połączeń z serwerem i użyć wysokiego FD, mając nadzieję, że pasuje do niektórych naszych połączeń.
Sygnatury zachowania do znalezienia tych funkcji:
'A' * offset + canary + rbp + (BROP + 0x9) + RIP + (BROP + 0x7) + p64(0) + p64(0) + (PLT + 0xb) + p64(ENTRY) + STOP
-> Jeśli dane są drukowane, to znaleziono puts'A' * offset + canary + rbp + (BROP + 0x9) + FD + (BROP + 0x7) + RIP + p64(0x0) + (PLT + 0xb) + p64(ENTRY) + STOP
-> Jeśli dane są drukowane, to znaleziono dprintf'A' * offset + canary + rbp + (BROP + 0x9) + RIP + (BROP + 0x7) + (RIP + 0x1) + p64(0x0) + (PLT + 0xb ) + p64(STRCMP ENTRY) + (BROP + 0x9) + FD + (BROP + 0x7) + RIP + p64(0x0) + (PLT + 0xb) + p64(ENTRY) + STOP
-> Jeśli dane są drukowane, to znaleziono write
Automatyzacja Eksploatacji
Referencje
Oryginalny dokument: https://www.scs.stanford.edu/brop/bittau-brop.pdf
Last updated