Co to jest dezasemblacja?

https://chacker.pl/

Najpierw przyjrzyjmy się procesowi deasemblacji kodu maszynowego. Jest to omówione na różne sposoby w innych miejscach tej książki, ale ważne jest, aby upewnić się, że rozumiesz podstawowy cel dezasemblera . W tym przykładzie używamy skompilowanej wersji programu myAtoi . Użyj narzędzia objdump zainstalowanego na Kali Linux z następującymi opcjami, aby zdemontować pierwsze osiem linii głównej funkcji programu myAtoi. Flaga -j pozwala określić sekcję; w tym przypadku wybieramy segment .text lub „code”. Flaga -d jest opcją demontażu. Grepujemy ciąg „<main>:” i drukujemy osiem wierszy po nim z flagą -A8.

W pierwszym wierszu wyniku w (1) widzimy, że główna funkcja zaczyna się od przesunięcia względnego adresu wirtualnego (RVA) wynoszącego 0x00000000000011ca w całym obrazie binarnym. Pierwsza linia zdezasemblowanego wyjścia w main zaczyna się od przesunięcia 11ca, jak widać w (2), po którym następuje kod operacji języka maszynowego 55, jak widać w (3). Na prawo od kodu operacji w (4) znajduje się odpowiednia zdemontowana instrukcja lub mnemoniczne push, po której następuje operand rbp w (5). Ta instrukcja spowoduje, że adres lub wartość przechowywana w rejestrze rbp zostanie wypchnięta na stos. Każdy z kolejnych wierszy wyjścia dostarcza tych samych informacji, pobierając kody operacji i wyświetlając odpowiedni demontaż. Jest to plik binarny w formacie wykonywalnym i linkującym (ELF) x86-64-bitowy. Gdyby ten program został skompilowany dla innego procesora, takiego jak ARM, kody operacji i zdezasemblowane instrukcje byłyby inne, ponieważ każda architektura procesora ma swój własny zestaw instrukcji. Dwie podstawowe metody demontażu to przeciągnięcie liniowe i zejście rekurencyjne (znane również jako przechodzenie rekurencyjne). Narzędzie objdump jest przykładem deasemblera liniowego, który rozpoczyna się na początku segmentu kodu lub określonego adresu początkowego, dezasemblując kolejno każdy kod operacji. Niektóre architektury mają zestaw instrukcji o zmiennej długości, na przykład x86-64, a inne architektury mają ustawione wymagania dotyczące rozmiaru, takie jak MIPS, gdzie każda instrukcja ma szerokość 4 bajtów. IDA jest przykładem dezasemblera rekurencyjnego, w którym kod maszynowy jest deasemblowany liniowo, aż do osiągnięcia instrukcji zdolnej do modyfikacji przepływu sterowania, takiej jak skok warunkowy lub rozgałęzienie. Przykładem skoku warunkowego jest instrukcja jz, która oznacza skok przy zera. Ta instrukcja sprawdza flagę zerową (zf) w rejestrze FLAGS, aby sprawdzić, czy jest ustawiona. Jeśli flaga jest ustawiona, skok jest wykonywany. Jeżeli flaga nie jest ustawiona, licznik programu przechodzi do kolejnego adresu sekwencyjnego, gdzie wykonanie jest kontynuowane. Aby dodać kontekst, poniższy obraz przedstawia dowolny przykład w IDA Pro skoku warunkowego po zwróceniu sterowania z funkcji alokacji pamięci:

Ten widok graficzny wewnątrz IDA Pro jest w formacie rekursywnego wyświetlania.

Najpierw wywoływana jest funkcja GetProcessHeap (1) . Jak sama nazwa wskazuje, to wywołanie funkcji zwraca adres bazowy lub uchwyt domyślnej sterty procesu. Adres sterty jest zwracany wywołującemu za pośrednictwem rejestru RAX. Argumenty wywołania HeapAlloc są teraz konfigurowane, przy czym pierwszym argumentem jest rozmiar, kopiowany z r14 do r8 w punkcie (2) przy użyciu instrukcji mov. Argument dwFlags jest ustawiany na 0 za pomocą instrukcji xor edx, edx w (3) , wskazując brak nowych opcji dla żądania alokacji. Adres sterty jest kopiowany z rax do rcx w (4) . Teraz, gdy argumenty funkcji HeapAlloc są już ustawione, instrukcja call jest wykonywana w punkcie (5) . Oczekiwany zwrot z wywołania HeapAlloc jest wskaźnikiem do przydzielonego fragmentu pamięci. Wartość przechowywana w rax jest następnie kopiowana do r15 w (6). Następnie wykonywana jest instrukcja test rax, rax w punkcie (7). Instrukcja testowa wykonuje operację bitową i. W tym przypadku testujemy rejestr rax przeciwko sobie samemu. Celem instrukcji testowej w tym przykładzie jest sprawdzenie, czy wartość zwracana z wywołania HeapAlloc wynosi 0, co oznaczałoby błąd. Jeśli rax przechowuje 0, a my i rejestr przeciwko sobie, ustawiana jest flaga zera (zf). Jeśli opcja HEAP_GENERATE_EXCEPTIONS jest ustawiona poprzez dwFlags podczas wywołania HeapAlloc, zwracane są kody wyjątków zamiast 0,1. Ostatnią instrukcją w tym bloku jest instrukcja skoku do zera (jz), at(8) . Jeśli zf jest ustawione, co oznacza, że wywołanie HeapAlloc nie powiodło się, wykonujemy skok; w przeciwnym razie przechodzimy liniowo do następnego adresu sekwencyjnego i kontynuujemy wykonywanie kodu.

Dodaj komentarz

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *