Przepływ sterowania trampoliną

https://chacker.pl/

Aby lepiej zrozumieć przepływ sterowania programu zinstrumentowanego za pomocą podejścia trampoliny, wróćmy do prawej strony rysunku 9-2, przedstawiającego zinstrumentowany plik binarny i załóżmy, że oryginalna funkcja f1 została właśnie wywołana. Zaraz po wywołaniu f1, trampolina przeskakuje do f1_copy (1), zinstrumentowanej wersji f1. Po trampolinie (2) mogą znajdować się pewne niepotrzebne bajty, ale nie są one wykonywane. Silnik SBI wstawia kilka instrukcji nop w każdym możliwym punkcie instrumentacji w f1_copy (3). W ten sposób, aby zinstrumentować instrukcję, silnik SBI może po prostu nadpisać instrukcje nop w tym punkcie instrumentacji za pomocą jmp lub wywołania fragmentu kodu instrumentacji. Należy pamiętać, że zarówno wstawianie instrukcji nop, jak i instrumentacja są wykonywane statycznie, a nie w czasie wykonywania. Na rysunku 9-2 wszystkie regiony nop są nieużywane, z wyjątkiem ostatniego, tuż przed instrukcją ret, co wyjaśnię za chwilę. Aby zachować poprawność skoków względnych pomimo przesunięcia kodu spowodowanego nowo wstawionymi instrukcjami, silnik SBI poprawia przesunięcia wszystkich instrukcji względnych jmp. Dodatkowo silnik zastępuje wszystkie 2-bajtowe instrukcje względne jmp, które mają 8-bitowe przesunięcie, odpowiadającą im 5-bajtową wersją z 32-bitowym przesunięciem (4). Jest to konieczne, ponieważ podczas przesuwania kodu w funkcji f1_copy przesunięcie między instrukcjami jmp a ich obiektami docelowymi może stać się zbyt duże, aby zakodować je w 8 bitach. Podobnie silnik SBI przepisuje wywołania bezpośrednie, takie jak wywołanie f2, tak aby wskazywały one na funkcję zinstrumentowaną, a nie na oryginalną (5). Biorąc pod uwagę to przepisanie wywołań bezpośrednich, można się zastanawiać, dlaczego trampoliny na początku każdej oryginalnej funkcji są w ogóle potrzebne. Jak wyjaśnię za chwilę, są one niezbędne do obsługi wywołań pośrednich. Załóżmy teraz, że poleciłeś silnikowi SBI instrumentację każdej instrukcji ret. W tym celu silnik SBI nadpisuje instrukcje nop zarezerwowane do tego celu poleceniem jmp lub wywołaniem kodu instrumentacji (6). W przykładzie z rysunku kod instrumentacji to funkcja o nazwie hook_ret, umieszczona w bibliotece współdzielonej i dostępna poprzez wywołanie umieszczone przez silnik SBI w punkcie instrumentacji. Funkcja hook_ret ​​najpierw zapisuje stan (7), taki jak zawartość rejestrów, a następnie uruchamia dowolny określony kod instrumentacji. Na koniec przywraca zapisany stan (8) i wznawia normalne wykonywanie, powracając do instrukcji następującej po punkcie instrumentacji. Teraz, gdy wiesz już, jak podejście trampoliny przepisuje instrukcje bezpośredniego przepływu sterowania, przyjrzyjmy się, jak obsługuje ono pośredni przepływ sterowania.

Podejście trampoliny

https://chacker.pl/

W przeciwieństwie do podejścia int 3, podejście trampoliny nie podejmuje próby bezpośredniej instrumentacji oryginalnego kodu. Zamiast tego tworzy kopię całego oryginalnego kodu i instrumentuje tylko ten skopiowany kod. Idea polega na tym, aby nie uszkodzić żadnego kodu ani odwołań do danych, ponieważ wszystkie one nadal wskazują na oryginalne, niezmienione lokalizacje. Aby zapewnić, że plik binarny uruchamia zinstrumentowany kod, a nie kod oryginalny, podejście trampoliny wykorzystuje instrukcje jmp zwane trampolinami, aby przekierować oryginalny kod do zinstrumentowanej kopii. Za każdym razem, gdy wywołanie lub skok przekazuje kontrolę do części oryginalnego kodu, trampolina w tym miejscu natychmiast przeskakuje do odpowiedniego zinstrumentowanego kodu. Aby wyjaśnić podejście trampoliny, rozważmy przykład pokazany na rysunku . Rysunek przedstawia niezainstrumentowany plik binarny po lewej stronie, a po prawej stronie pokazuje, jak ten plik binarny przekształca się po instrumentacji.

Załóżmy, że oryginalny, niezainstrumentowany plik binarny zawiera dwie funkcje o nazwach f1 i f2. Rysunek pokazuje, że f1 zawiera następujący kod. Zawartość f2 nie jest istotna w tym przykładzie.

<f1>:

test edi,edi

jne _ret

xor eax,eax

call f2

_ret:

ret

Podczas instrumentacji pliku binarnego za pomocą metody trampoliny, silnik SBI tworzy kopie wszystkich oryginalnych funkcji, umieszcza je w nowej sekcji kodu (nazwanej .text.instrum na rysunku) i nadpisuje pierwszą instrukcję każdej oryginalnej funkcji trampoliną jmp, która przeskakuje do odpowiadającej jej skopiowanej funkcji. Na przykład silnik SBI przepisuje oryginalną funkcję f1 w następujący sposób, aby przekierować ją do f1_copy:

<f1>:

jmp f1_copy

; junk bytes

Instrukcja trampoliny to 5-bajtowy jmp, więc może częściowo nadpisać i uszkodzić wiele instrukcji, tworząc „śmieciowe bajty” tuż za trampoliną. Jednak zazwyczaj nie stanowi to problemu w przypadku podejścia trampoliny, ponieważ gwarantuje, że te uszkodzone instrukcje nigdy nie zostaną wykonane. Pod koniec tej sekcji zobaczysz kilka przypadków, w których może to się nie udać.

Rozwiązywanie problemu skoków wielobajtowych za pomocą int 3

https://chacker.pl/

Instrukcja int 3 w architekturze x86 generuje przerwanie programowe, które programy działające w przestrzeni użytkownika, takie jak biblioteki SBI lub debugery, mogą przechwycić (w systemie Linux) w postaci sygnału SIGTRAP dostarczanego przez system operacyjny. Kluczową cechą int 3 jest to, że ma on tylko 1 bajt długości, dzięki czemu można nadpisać nim dowolną instrukcję bez obawy o nadpisanie sąsiedniej instrukcji. Kod operacji dla int 3 to 0xcc. Z punktu widzenia SBI, aby zainstrumentować instrukcję za pomocą int 3, wystarczy nadpisać pierwszy bajt tej instrukcji wartością 0xcc. W przypadku wystąpienia błędu SIGTRAP można użyć interfejsu API ptrace systemu Linux, aby dowiedzieć się, pod którym adresem wystąpiło przerwanie, podając adres punktu instrumentacji. Następnie można wywołać odpowiedni kod instrumentacji dla tego punktu. Z czysto funkcjonalnego punktu widzenia int 3 jest idealnym sposobem na implementację SBI, ponieważ jest łatwy w użyciu i nie wymaga relokacji kodu. Niestety, przerwania programowe, takie jak int 3, są powolne, co powoduje nadmierne obciążenie w instrumentowanej aplikacji. Ponadto podejście z int 3 jest niezgodne z programami, które są już debugowane z użyciem int 3 dla punktów przerwania. Dlatego w praktyce wiele platform SBI korzysta ze skomplikowanych, ale szybszych metod przepisywania, takich jak podejście trampoliny.

Podejście int 3

https://chacker.pl/

Podejście int 3 wzięło swoją nazwę od instrukcji x86 int 3, której debuggery używają do implementacji punktów przerwania oprogramowania. Aby zilustrować potrzebę int 3, rozważmy najpierw podejście SBI, które nie działa w ogólnym przypadku. Naiwna implementacja SBI Biorąc pod uwagę praktyczną niemożność naprawienia wszystkich odwołań do przeniesionego kodu, oczywiste jest, że SBI nie może przechowywać kodu instrumentacji w istniejącej sekcji kodu. Ponieważ nie ma miejsca na dowolną ilość nowego kodu w istniejących sekcjach kodu, wynika z tego, że podejścia SBI muszą przechowywać kod instrumentacji w oddzielnej lokalizacji, takiej jak nowa sekcja lub biblioteka współdzielona, ​​a następnie w jakiś sposób przekazać kontrolę do kodu instrumentacji, gdy wykonywanie osiągnie punkt instrumentacji. Aby to osiągnąć, można opracować rozwiązanie pokazane na rysunku .

Lewa kolumna na Rysunku  przedstawia fragment oryginalnego, nieinstrumentowanego kodu. Załóżmy, że chcesz zinstrumentować instrukcję mov edx,0x1 (1), dodając kod instrumentacji do uruchomienia przed i po tej instrukcji. Aby obejść problem braku miejsca na dodanie nowego kodu w linii, nadpisujesz mov edx,0x1 poleceniem jmp do kodu instrumentacji (2), przechowywanego w oddzielnej sekcji kodu lub bibliotece. Kod instrumentacji najpierw uruchamia dodany kod przedinstrumentacyjny (3), czyli kod uruchamiany przed oryginalną instrukcją. Następnie uruchamia oryginalną instrukcję mov edx,0x1 (4), a następnie kod poinstrumentacyjny (5). Na koniec kod instrumentacji powraca do instrukcji następującej po punkcie instrumentacji (6), wznawiając normalne wykonywanie. Należy pamiętać, że jeśli kod przedinstrumentacyjny lub poinstrumentacyjny zmieni zawartość rejestrów, może to nieumyślnie wpłynąć na inne części programu. Dlatego platformy SBI przechowują stan rejestrów przed uruchomieniem dodanego kodu i przywracają go później, chyba że wyraźnie poinstruujesz platformę SBI o chęci zmiany stanu rejestru. Jak widać, podejście przedstawione na rysunku 9-1 to prosty i elegancki sposób na uruchomienie dowolnej ilości kodu przed lub po dowolnej instrukcji. Jaki jest więc problem z tym podejściem? Problem polega na tym, że instrukcje jmp zajmują wiele bajtów; aby przejść do kodu instrumentacji, zazwyczaj potrzebna jest 5-bajtowa instrukcja jmp, która składa się z 1 bajtu kodu operacyjnego z 32-bitowym przesunięciem. Podczas instrumentacji krótkiej instrukcji, instrukcja jmp do kodu instrumentacji może być dłuższa niż instrukcja, którą zastępuje. Na przykład instrukcjaxor esi,esi w lewym górnym rogu rysunku 9-1 ma tylko 2 bajty, więc jeśli zastąpisz ją 5-bajtową instrukcją jmp, instrukcja jmp nadpisze i uszkodzi część następnej instrukcji. Nie można rozwiązać tego problemu, umieszczając tę ​​kolejną nadpisaną instrukcję w kodzie instrumentacji, ponieważ instrukcja ta może być celem rozgałęzienia. Wszelkie rozgałęzienia celujące w tę instrukcję trafiłyby do środka wstawionej instrukcji jmp, powodując uszkodzenie pliku binarnego. To prowadzi nas z powrotem do instrukcji int 3. Instrukcji int 3 można użyć do instrumentacji krótkich instrukcji, w których nie można wykonać skoków wielobajtowych.

Statyczna instrumentacja binarna

https://chacker.pl/

Statyczna instrumentacja binarna polega na deasemblacji pliku binarnego, a następnie dodaniu kodu instrumentacji w razie potrzeby i trwałym zapisaniu zaktualizowanego pliku binarnego na dysku. Znane platformy SBI to PEBIL i Dyninst (obsługujące zarówno DBI, jak i SBI). PEBIL wymaga symboli, podczas gdy Dyninst ich nie wymaga. Należy pamiętać, że zarówno PEBIL, jak i Dyninst to narzędzia badawcze, dlatego nie są tak dobrze udokumentowane, jak narzędzia o jakości produkcyjnej. Głównym wyzwaniem we wdrażaniu SBI jest znalezienie sposobu na dodanie kodu instrumentacji i przepisanie pliku binarnego bez naruszania istniejącego kodu ani odwołań do danych. Rozważmy dwa popularne rozwiązania tego problemu, które nazywam podejściem int 3 i podejściem trampoliny. Należy zauważyć, że w praktyce silniki SBI mogą zawierać elementy obu tych technik lub wykorzystywać zupełnie inną technikę.

Statyczna a dynamiczna instrumentacja binarna

https://chacker.pl/

Statyczna i dynamiczna instrumentacja binarna rozwiązują problemy związane z wstawianiem i relokacją kodu, stosując różne podejścia. SBI wykorzystuje techniki przepisywania binarnego do trwałej modyfikacji plików binarnych na dysku. Z drugiej strony, DBI w ogóle nie modyfikuje plików binarnych na dysku, lecz monitoruje je podczas wykonywania i wstawia nowe instrukcje do strumienia instrukcji na bieżąco. Zaletą tego podejścia jest uniknięcie problemów z relokacją kodu. Kod instrumentacji jest wstrzykiwany tylko do strumienia instrukcji, a nie do sekcji kodu pliku binarnego w pamięci, dzięki czemu nie powoduje to uszkodzenia odwołań. Wadą jest jednak to, że instrumentacja w czasie wykonywania DBI jest bardziej kosztowna obliczeniowo, co powoduje większe spowolnienia w instrumentowanym pliku binarnym niż w SBI. Tabela 9-1 podsumowuje główne zalety i wady SBI i DBI, przedstawiając zalety symbolem + i wady symbolem –.

Instrumentacja dynamiczna: Instrumentacja statyczna

– Stosunkowo wolna (4 razy lub więcej): + Stosunkowo szybka (10% do 2 razy)

– Zależna od biblioteki i narzędzia DBI: + Samodzielny plik binarny

+ Przezroczysta instrumentacja bibliotek: – Wymaga jawnego instrumentowania bibliotek

+ Obsługa dynamicznie generowanego kodu: – Kod generowany dynamicznie nie jest obsługiwany

+ Możliwość dynamicznego dołączania/odłączania: – Instrumentacja całego wykonania

+ Brak konieczności deasemblacji: – Podatność na błędy deasemblacji

+ Przezroczysta, brak konieczności modyfikowania pliku binarnego: – Podatne na błędy przepisywanie pliku binarnego

+ Brak konieczności używania symboli: – Symbole preferowane w celu minimalizacji błędów

Jak widać, zapotrzebowanie DBI na analizę i instrumentację w czasie wykonywania powoduje spowolnienia czterokrotnie lub więcej, podczas gdy SBI powoduje jedynie spowolnienie od 10% do dwukrotności. Należy pamiętać, że są to wartości orientacyjne, a rzeczywiste spowolnienie może się znacznie różnić w zależności od potrzeb instrumentacji i jakości implementacji narzędzia. Co więcej, pliki binarne z instrumentacją DBI są trudniejsze w dystrybucji: należy dostarczyć nie tylko sam plik binarny, ale także platformę i narzędzie DBI zawierające kod instrumentacji. Z drugiej strony, pliki binarne z instrumentacją SBI są autonomiczne i można je dystrybuować normalnie po zakończeniu instrumentacji. Główną zaletą DBI jest to, że jest znacznie łatwiejszy w użyciu niż SBI. Ponieważ DBI korzysta z instrumentacji w czasie wykonywania, automatycznie uwzględnia wszystkie wykonywane instrukcje, niezależnie od tego, czy są one częścią oryginalnego pliku binarnego, czy bibliotek używanych przez plik binarny. Natomiast w przypadku SBI należy jawnie zinstrumentować i rozpowszechnić wszystkie biblioteki używane przez plik binarny, chyba że chce się pozostawić te biblioteki bez instrumentacji. Fakt, że DBI działa na wykonywanym strumieniu instrukcji, oznacza również, że obsługuje dynamicznie generowany kod, którego SBI nie obsługuje, taki jak kod kompilowany metodą JIT lub kod samomodyfikujący. Ponadto platformy DBI zazwyczaj mogą dynamicznie dołączać się do procesów i odłączać się od nich, tak jak debugery. Jest to wygodne, na przykład, jeśli chcesz obserwować część wykonywania długotrwałego procesu. W przypadku DBI wystarczy po prostu dołączyć się do procesu, zebrać potrzebne informacje, a następnie odłączyć się, pozostawiając proces działający normalnie. W przypadku SBI nie jest to możliwe; albo instrumentujesz całe wykonanie, albo w ogóle nie instrumentujesz. Wreszcie, DBI jest znacznie mniej podatne na błędy niż SBI. SBI instrumentuje pliki binarne, deasemblując je, a następnie wprowadzając wszelkie niezbędne zmiany. Oznacza to, że błędy deasemblacji mogą łatwo spowodować błędy w instrumentacji, potencjalnie powodując nieprawidłowe wyniki, a nawet uszkodzenie pliku binarnego. DBI nie ma tego problemu, ponieważ nie wymaga deasemblacji; Po prostu obserwuje instrukcje w trakcie ich wykonywania, co gwarantuje poprawny strumień instrukcji. Aby zminimalizować ryzyko błędów deasemblacji, wiele platform SBI wymaga symboli, podczas gdy DBI nie ma takiego wymogu. Jak wspomniałem wcześniej, istnieją różne sposoby implementacji przepisywania binarnego w SBI i instrumentacji środowiska wykonawczego w DBI. W kolejnych dwóch sekcjach przyjrzymy się najpopularniejszym sposobom implementacji odpowiednio SBI i DBI.

Interfejsy API instrumentacji binarnej

https://chacker.pl/

Ogólna instrumentacja binarna, która pozwala na dodawanie nowego kodu w każdym miejscu pliku binarnego, jest znacznie trudniejsza do poprawnej implementacji niż proste techniki modyfikacji binarnej, które przedstawiłeś w rozdziale 7. Pamiętaj, że nie możesz po prostu wstawić nowego kodu do istniejącej sekcji kodu binarnego, ponieważ nowy kod przesunie istniejący kod na inne adresy, tym samym przerywając odwołania do tego kodu. Zlokalizowanie i załatanie wszystkich istniejących odwołań po przeniesieniu kodu jest praktycznie niemożliwe, ponieważ pliki binarne nie zawierają żadnych informacji wskazujących, gdzie znajdują się te odwołania, i nie ma sposobu na wiarygodne odróżnienie adresów, do których się odwołują, od stałych, które wyglądają jak adresy, ale nimi nie są. Na szczęście istnieją ogólne platformy instrumentacji binarnej, których możesz użyć, aby poradzić sobie ze wszystkimi trudnościami implementacji, i oferują one stosunkowo łatwe w użyciu interfejsy API, za pomocą których możesz implementować narzędzia instrumentacji binarnej. Te interfejsy API zazwyczaj umożliwiają instalację wywołań zwrotnych do kodu instrumentacji w wybranych punktach instrumentacji. W dalszej części tego rozdziału zobaczysz dwa praktyczne przykłady instrumentacji binarnej z wykorzystaniem platformy Pin, popularnej platformy instrumentacji binarnej. Za pomocą platformy Pin zaimplementujesz profiler, który rejestruje statystyki dotyczące wykonania pliku binarnego, wspomagając optymalizację. Za pomocą platformy Pin zaimplementujesz również automatyczny depaker, który pomaga w deobfuskacji spakowanych plików binarnych. Można wyróżnić dwie klasy platform instrumentacji binarnej: statyczną i dynamiczną. Najpierw omówimy różnice między tymi dwiema klasami, a następnie przyjrzymy się ich działaniu na niskim poziomie.

Czym jest instrumentacja binarna?

https://chacker.pl/

Wstawianie nowego kodu w dowolnym miejscu istniejącego pliku binarnego w celu obserwacji lub modyfikacji jego działania nazywa się instrumentacją pliku binarnego. Punkt, w którym dodaje się nowy kod, nazywa się punktem instrumentacji, a dodany kod – kodem instrumentacji. Załóżmy na przykład, że chcemy wiedzieć, które funkcje w pliku binarnym są wywoływane najczęściej, aby móc skupić się na ich optymalizacji. Aby to sprawdzić, możemy instrumentować wszystkie instrukcje wywołań w pliku binarnym, dodając kod instrumentacji, który rejestruje cel wywołania, tak aby instrumentowany plik binarny generował listę wywoływanych funkcji podczas wykonywania. Chociaż ten przykład dotyczy jedynie obserwacji działania pliku binarnego, można je również modyfikować. Na przykład, można zwiększyć bezpieczeństwo pliku binarnego przed atakami typu control-hijacking, instrumentując wszystkie pośrednie transfery sterowania (takie jak call rax i ret) kodem, który sprawdza, czy cel przepływu sterowania znajduje się w zestawie oczekiwanych celów. W przeciwnym razie przerywasz wykonywanie i generujesz alert.

INSTRUMENTACJA BINARNA

https://chacker.pl/

Poznałeś kilka technik modyfikowania i rozszerzania programów binarnych. Choć są one stosunkowo proste w użyciu, techniki te są ograniczone pod względem ilości nowego kodu, który można wstawić do pliku binarnego, oraz miejsca, w którym można go wstawić. W tym rozdziale poznasz technikę zwaną instrumentacją binarną, która pozwala na wstawienie praktycznie nieograniczonej ilości kodu w dowolnym miejscu pliku binarnego w celu obserwacji lub modyfikacji jego działania. Po krótkim omówieniu instrumentacji binarnej omówię, jak wdrażać statyczną instrumentację binarną (SBI) i dynamiczną instrumentację binarną (DBI), dwa rodzaje instrumentacji binarnej o różnych kompromisach. Na koniec dowiesz się, jak budować własne narzędzia do instrumentacji binarnej za pomocą Pin, popularnego systemu DBI firmy Intel.

Podsumowanie

https://chacker.pl/

Powinieneś teraz czuć się swobodnie korzystając z Capstone, aby rozpocząć tworzenie własnych deasemblerów. Wszystkie przykłady w tym rozdziale są dostępne na maszynie wirtualnej dołączonej do tej książki. Eksperymentowanie z nimi to dobry punkt wyjścia do opanowania API Capstone. Skorzystaj z poniższych ćwiczeń i wyzwań, aby sprawdzić swoje umiejętności w zakresie deasemblacji!

Ćwiczenia

  1. Uogólnianie deasemblera

Wszystkie narzędzia do deasemblacji, które widziałeś w tym rozdziale, skonfigurowały Capstone do deasemblacji tylko kodu x64. Dokonałeś tego, przekazując CS_ARCH_X86 i CS_MODE_64 jako argumenty architektury i trybu do cs_open. Uogólnijmy te narzędzia, aby automatycznie wybierały odpowiednie parametry Capstone do obsługi innych architektur, sprawdzając typ załadowanego pliku binarnego za pomocą pól arch i bits w klasie Binary udostępnianej przez program ładujący. Aby dowiedzieć się, które argumenty architektury i trybu przekazać do Capstone, pamiętaj, że plik /usr/include/capstone/capstone.h zawiera listy wszystkich możliwych wartości cs_arch i cs_mode.

  1. Jawne wykrywanie nakładających się bloków

Chociaż przykładowy rekurencyjny deasembler radzi sobie z nakładającymi się blokami podstawowymi, nie wyświetla on żadnego wyraźnego ostrzeżenia o takim nałożeniu kodu. Rozszerz deasembler, aby poinformować użytkownika, które bloki się nakładają.

  1. Wyszukiwarka gadżetów międzywariantowych

Podczas kompilacji programu ze źródeł, wynikowy plik binarny może się znacznie różnić w zależności od takich czynników, jak wersja kompilatora, opcje kompilacji czy architektura docelowa. Ponadto strategie randomizacji, które zabezpieczają pliki binarne przed wykorzystaniem poprzez zmianę alokacji rejestrów lub przetasowanie kodu, komplikują proces wykorzystania. Oznacza to, że podczas tworzenia exploita (takiego jak exploit ROP), nie zawsze będziesz wiedzieć, który „wariant” binarny programu jest uruchomiony na serwerze docelowym. Na przykład, czy serwer docelowy został skompilowany za pomocą gcc czy llvm? Czy działa na systemie 32-bitowym czy 64-bitowym? Jeśli zgadniesz źle, Twój exploit prawdopodobnie się nie powiedzie. W tym ćwiczeniu Twoim celem jest rozszerzenie wyszukiwarki gadżetów ROP tak, aby przyjmowała jako dane wejściowe dwa lub więcej plików binarnych, reprezentujących różne warianty tego samego programu. Powinna ona wygenerować listę VMA zawierających użyteczne gadżety we wszystkich wariantach. Twój nowy program do wyszukiwania gadżetów powinien być w stanie przeskanować każdy z wejściowych plików binarnych w poszukiwaniu gadżetów, ale wyprowadzać tylko te adresy, w których wszystkie pliki binarne zawierają gadżet, a nie tylko niektóre z nich. Dla każdego zgłoszonego VMA, gadżety powinny również implementować podobne operacje. Na przykład, będą zawierać instrukcję add lub mov. Implementacja użytecznego pojęcia podobieństwa będzie częścią wyzwania. Efektem końcowym powinna być wielowariantowa wyszukiwarka gadżetów, która może być używana do tworzenia exploitów działających jednocześnie na wielu wariantach tego samego programu! Aby przetestować swój program do wyszukiwania gadżetów, możesz utworzyć warianty wybranego programu, kompilując go wielokrotnie z różnymi opcjami kompilacji lub różnymi kompilatorami.