https://chacker.pl/
Niestandardowy przebieg dezasemblacji jest przydatny, gdy trzeba analizować pliki binarne, które naruszają standardowe założenia dezasemblera, takie jak złośliwe oprogramowanie, zaciemnione lub ręcznie stworzone pliki binarne, lub pliki binarne wyodrębnione ze zrzutów pamięci lub oprogramowania układowego. Co więcej, niestandardowe przebiegi dezasemblacji pozwalają na łatwą implementację specjalistycznych analiz binarnych, które skanują w poszukiwaniu określonych artefaktów, takich jak wzorce kodu wskazujące na potencjalne luki w zabezpieczeniach. Są one również przydatne jako narzędzia badawcze, umożliwiając eksperymentowanie z nowatorskimi technikami dezasemblacji. Jako pierwszy konkretny przypadek użycia niestandardowego dezasemblowania rozważmy konkretny typ zaciemnienia kodu, który wykorzystuje nakładanie się instrukcji. Większość dezasemblerów generuje pojedynczą listę dezasemblacji dla każdego pliku binarnego, ponieważ zakłada się, że każdy bajt w pliku binarnym jest przypisany do co najwyżej jednej instrukcji, każda instrukcja jest zawarta w pojedynczym bloku podstawowym, a każdy blok podstawowy jest częścią pojedynczej funkcji. Innymi słowy, dezasemblery zazwyczaj zakładają, że fragmenty kodu nie nakładają się na siebie. Nakładanie się instrukcji łamie to założenie, aby zmylić dezasemblery, utrudniając inżynierię wsteczną nakładającego się kodu. Nakładanie się instrukcji działa, ponieważ instrukcje na platformie x86 różnią się długością. W przeciwieństwie do niektórych innych platform, takich jak ARM, nie wszystkie instrukcje x86 składają się z tej samej liczby bajtów. W rezultacie procesor nie wymusza żadnego konkretnego wyrównania instrukcji w pamięci, co umożliwia jednej instrukcji zajęcie zestawu adresów kodu już zajętego przez inną instrukcję. Oznacza to, że na x86 można rozpocząć dezasemblację od środka jednej instrukcji, a dezasemblacja da w rezultacie kolejną instrukcję, która częściowo (lub całkowicie) nakłada się na pierwszą. Obfuskatory chętnie wykorzystują nakładające się instrukcje, aby zmylić dezasemblery. Nakładanie się instrukcji jest szczególnie łatwe na x86, ponieważ zestaw instrukcji x86 jest niezwykle gęsty, co oznacza, że prawie każda sekwencja bajtów odpowiada pewnej prawidłowej instrukcji.
Listing przedstawia przykład nakładania się instrukcji. Oryginalne źródło, z którego pochodzi ten listing, można znaleźć w pliku overlapping_bb.c. Aby zdeasemblować nakładający się kod, można użyć flagi -start-address=<addr> w programie objdump, aby rozpocząć deasemblację od podanego adresu.
Listing : Deasemblacja nachodzącego na siebie_bb (1)

Listing przedstawia prostą funkcję, która przyjmuje jeden parametr wejściowy,o nazwie i (1) i ma zmienną lokalną o nazwie j (2). Po wykonaniu pewnych obliczeń funkcja zwraca j. Po bliższym przyjrzeniu się zauważysz coś dziwnego: instrukcja jne pod adresem 40060a (3) warunkowo przeskakuje do środka instrukcji, zaczynając od adresu 400610, zamiast kontynuować wykonywanie od początku którejkolwiek z wymienionych instrukcji! Większość deasemblerów, takich jak objdump i IDA Pro, deasembluje tylko instrukcje pokazane na Listingu 8-1. Oznacza to, że deasemblery ogólnego przeznaczenia pominęłyby nakładającą się instrukcję pod adresem 400612, ponieważ te bajty są już zajęte przez instrukcję osiągniętą w przypadku przejścia przez jne. Ten rodzaj nakładania się umożliwia ukrycie ścieżek kodu, co może mieć drastyczny wpływ na ogólny wynik programu. Rozważmy na przykład następujący przypadek. W Listingu 8-1, jeśli skok pod adresem 40060a nie zostanie wykonany (i == 0), instrukcje dotarte przez przypadek przejścia są obliczane i zwracają wartość 148 (4). Jeśli jednak skok zostanie wykonany (i != 0), wykonywana jest ścieżka kodu ukryta w Listingu . Spójrzmy na Listing 2, który pokazuje tę ukrytą ścieżkę kodu, aby zobaczyć, jak zwraca ona zupełnie inną wartość.
Listing 2: Deasemblacja nakładki_bb (2)


Listing 2 przedstawia ścieżkę kodu wykonywaną w przypadku wykonania instrukcji jne (1). W takim przypadku przeskakuje ona o dwa bajty (400610 i 400611) do adresu 0x400612 (2), który znajduje się w środku instrukcji xor osiągniętej w przypadku przejścia przez jne. Powoduje to inny strumień instrukcji. W szczególności operacje arytmetyczne wykonywane na j są teraz inne, co powoduje, że funkcja zwraca i + 4 (3) zamiast 148. Jak można sobie wyobrazić, tego rodzaju zaciemnianie utrudnia zrozumienie kodu, zwłaszcza jeśli jest stosowane w więcej niż jednym miejscu. Zazwyczaj można nakłonić dezasemblery do ujawnienia ukrytych instrukcji, restartując dezasemblację od innego przesunięcia, tak jak zrobiłem to z flagą -start-address w objdump w poprzednich listingach. Jak widać na Listingu 8-2, ponowne uruchomienie deasemblacji pod adresem 400612 ujawnia ukrytą tam instrukcję. Jednak wykonanie tej czynności powoduje ukrycie instrukcji pod adresem 400610. Niektóre zaciemnione programy są przepełnione nakładającymi się sekwencjami kodu, takimi jak ta pokazana w tym przykładzie, co sprawia, że kod jest niezwykle żmudny i trudny do ręcznego zbadania. Przykład z Listingów 1 i 2 pokazuje, że zbudowanie wyspecjalizowanego narzędzia do deasemblacji, które automatycznie „rozplątuje” nakładające się instrukcje, może znacznie ułatwić inżynierię wsteczną. Zwłaszcza jeśli często musisz odwracać zaciemnione pliki binarne, wysiłek włożony w zbudowanie narzędzia do deasemblacji opłaca się na dłuższą metę. W dalszej części dowiesz się, jak zbudować rekurencyjny deasembler, który poradzi sobie z nakładającymi się blokami podstawowymi, takimi jak te pokazane w poprzednich Listingach.