HTCinside
Publikując nasze oprogramowanie, wszyscy używamy do pisania w EULA „Użytkownikowi nie wolno dokonywać inżynierii wstecznej, dekompilacji ani dezasemblacji Oprogramowania”. Ale w wielu sytuacjach słowa nie są najlepszą ochroną i naprawdę musisz wprowadzić pewne narzędzia techniczne, aby zapobiec odwróceniu oprogramowania i chronić swoją wiedzę przed ujawnieniem.
Istnieje kilka podejść technologicznych do zapobiegania inżynierii wstecznej oprogramowania: zapobieganie debugowaniu, zapobieganie zrzutom i inne. W tym poście skupimy się na niektórych metodach antydebugowych, ponieważ są one podstawą ochrony przed inżynierią odwrotną. Dołączenie debuggera do badanego procesu krok po kroku do jego realizacji jest bardzo ważnym etapem każdej pracy cofania – spójrzmy więc, jakich narzędzi możemy użyć, aby utrudnić życie odwracającym.
Na samym początku chciałbym wspomnieć o kilku rzeczach. Po pierwsze, nie istnieje uniwersalna ani w 100% kuloodporna ochrona przed inżynierią wsteczną oprogramowania. Zawsze znajdzie się sposób na wejście rewersera, jedyną strategią, jaką mamy, jest sprawienie, aby jego praca była tak ciężka i pracochłonna, jak to tylko możliwe.
Następnie istnieje kilka technik inżynierii wstecznej, a w szczególności metody zapobiegające debugowaniu, w tym ochrona oparta na czasie, a nawet specyficzne technologie osadzone w kodzie, takie jak nanomity. W tym poście rozważymy tylko kilka standardowych podejść specyficznych dla systemów opartych na Windows, tych najbardziej popularnych.
Przedstawione poniżej podejścia są opisane ogólnie.
Zawartość
Systemy Windows dostarczają nam gotowych narzędzi do zbudowania prostej ochrony antydebugowej. Jedna z najprostszych technik antydebugowania polega na wywołaniu funkcji IsDebuggerPresent. Ta funkcja zwraca TRUE, jeśli debuger trybu użytkownika aktualnie debuguje proces.
Funkcja ta odnosi się do PEB (Process Environment Block, zamknięta struktura systemu), a w szczególności do jego pola BeingDebugged. Rewersery przy omijaniu takiej techniki ochrony wykorzystują ten fakt: np. stosując iniekcję DLL, ustawiają wartość BeingDebugged na 0 tuż przed wykonaniem tego sprawdzenia w chronionym kodzie.
Kilka słów o tym, gdzie przeprowadzić taką kontrolę. Główna funkcja nie jest najlepszą opcją: odwracacze zwykle najpierw sprawdzają ją w zdemontowanym listingu. Lepiej jest przeprowadzić kontrolę antydebugową w TLS Callback, ponieważ jest ona wywoływana przed punktem wywołania wejścia głównego modułu wykonywalnego.
Inną opcją sprawdzenia funkcjonalności jest CheckRemoteDebuggerPresent. W przeciwieństwie do funkcji opisanej powyżej, sprawdza, czy inny równoległy proces aktualnie debuguje proces. Opiera się na funkcji NtQueryInformationProcess, aw szczególności na wartości ProcessDebugPort.
Podczas gdy poprzednia grupa metod opierała się na sprawdzaniu obecności debuggera, ta zapewni przed nim aktywną ochronę.
Począwszy od Windows 2000, funkcja NtSetInformationThread otrzymuje nową flagę o nazwie ThreadHideFromDebugger. Jest to bardzo wydajna technika antydebugowania dostępna w systemie operacyjnym Windows. Wątek z ustawioną flagą zatrzymuje się, aby wysyłać powiadomienia o zdarzeniach debugowania, w tym o punktach przerwania i innych, w ten sposób ukrywając się przed dowolnym debugerem. Skonfigurowanie ThreadHideFromDebugger dla głównego wątku znacznie skomplikuje proces dołączania debugera do wątku.
Logiczną kontynuację wprowadzono w systemie Windows Vista z funkcją NtCreateThreadEx. Posiada parametr CreateFlags, który ustawia m.in. flagę THREAD_CREATE_FLAGS_HIDE_FROM_DEBUGGER. Proces z ustawioną flagą zostanie ukryty przed debugerem.
Uruchamianie debugowania można wykryć po zmianie wartości różnych flag w różnych strukturach systemu i procesów.
Windows NT zawiera zmienną globalną o nazwie NtGlobalFlag z zestawem flag używanych do śledzenia i debugowania systemu. Wspomniana powyżej struktura PEB zawiera własne pole NtGlobalFlag. Podczas debugowania ta wartość pola jest zmieniana z kilkoma określonymi flagami ustawionymi. Zaznaczenie tych flag może generować wyzwalacze ochrony przed debugowaniem.
Plik wykonywalny może zresetować flagi NtGlobalFlag struktury PEB za pomocą określonej struktury o nazwie IMAGE_LOAD_CONFIG_DIRECTORY, która zawiera określone parametry konfiguracyjne dla programu ładującego system. Zawiera pole GlobalFlagsClear, które resetuje flagi NtGlobalFlag PEB. Domyślnie ta struktura nie jest dodawana do pliku wykonywalnego, ale można ją dodać później. Fakt, że plik wykonywalny nie ma tej struktury lub wartości GlobalFlagsClear jest równe 0, podczas gdy odpowiednie pole przechowywane na dysku lub w pamięci uruchomionego procesu nie jest zerem, wskazuje na obecność ukrytego debuggera. To sprawdzenie można zaimplementować w kodzie wykonywalnym.
Inną grupą flag jest flaga sterty procesu. W odpowiedniej strukturze _HEAP znajdują się dwa pola: Flags i ForceFlags. Oba zmieniają swoje wartości, gdy odpowiedni proces jest debugowany, a zatem mogą być podstawą kontroli i ochrony antydebugowej.
Jeszcze jedno sprawdzenie flagi, które można wykorzystać do wykrycia debuggera, to sprawdzenie flagi pułapki (TF). Znajduje się w rejestrze EFLAGS. Gdy TF jest równe 1, CPU generuje INT 01h (wyjątek «Single Step») po wykonaniu każdej instrukcji wspierającej proces debugowania.
Punkty przerwania są istotną częścią każdego procesu debugowania i dzięki temu wykrywając je, możemy wykryć i zneutralizować debugger. Taktyki antydebugowania oparte na wykrywaniu punktów przerwania są jedną z najpotężniejszych i najtrudniejszych do ominięcia.
Istnieją dwa rodzaje punktów przerwania: programowe i sprzętowe.
Programowe punkty przerwania są ustawiane przez debugger poprzez wstrzyknięcie do kodu instrukcji int 3h. Zatem metody wykrywania debuggera opierają się na obliczaniu i kontroli sumy kontrolnej odpowiedniej funkcji.
Nie ma uniwersalnej metody walki z tym zabezpieczeniem – haker będzie musiał znaleźć fragment kodu wyliczający sumy kontrolne i zastąpić zwrócone wartości wszystkich odpowiadających im zmiennych.
Sprzętowe punkty przerwania są ustawiane przy użyciu określonych rejestrów debugowania: DR0-DR7. Korzystając z nich, programiści mogą przerwać wykonywanie programu i przekazać kontrolę do debugera. Ochrona antydebugowa może być zbudowana na sprawdzaniu wartości tych rejestrów lub bardziej proaktywnym i wymuszonym resetowaniu ich wartości, aby zatrzymać debugowanie za pomocą funkcji SetThreadContext.
Structured Exception Handling lub SEH to mechanizm umożliwiający aplikacji otrzymywanie powiadomień o wyjątkowych sytuacjach i obsługę ich zamiast systemu operacyjnego. Wskaźniki do programów obsługi SEH są nazywane ramkami SEH i umieszczane na stosie. Po wygenerowaniu wyjątku jest on obsługiwany przez pierwszą ramkę SEH w stosie. Jeśli nie wie, co z nim zrobić, jest przekazywany do następnego w stosie i tak dalej, aż do obsługi systemu.
Gdy aplikacja jest debugowana, debugger powinien przechwycić kontrolę po wygenerowaniu int 3h, w przeciwnym razie przejmie ją funkcja obsługi SHE. Można to wykorzystać do zorganizowania ochrony przed debugowaniem: możemy stworzyć własny program obsługi SEH i umieścić go na szczycie stosu, a następnie wymusić generację int 3h. Jeśli nasz handler przejmie kontrolę, proces nie jest debugowany – w przeciwnym razie możemy wymusić środki antydebugowe po wykryciu debuggera.
To tylko kilka technik zapobiegających debugowaniu z wielu z nich.
Dobrą praktyką jest łączenie różnych technik przeciwdziałania cofaniu, co znacznie utrudnia ominięcie ochrony. Dodatkowe kontrole mogą spowolnić działanie aplikacji, dlatego najsilniejsze techniki ochrony są zwykle stosowane do podstawowych modułów zawierających opatentowane technologie i know-how. Wreszcie jest to kompromis między poziomem bezpieczeństwa kodu a wydajnością aplikacji.
Chciałbym wspomnieć, że inżynieria wsteczna oprogramowania nie zawsze jest nielegalna i czasami może być zastosowana w procesie badawczym do takich zadań jak poprawa kompatybilności, łatanie, nieudokumentowane użycie interfejsu systemu itp. Prawne usługi inżynierii odwrotnej dostarczane przez profesjonalistów również zajmują się ochroną antydebugową ale z drugiej strony – omijaniem jej.