[RElog] Analiza działania i modyfikacja programu bez kodu źródłowego

Deasemblacja i „naprawienie” programu przy użyciu instrukcji NOP.

Witam Was serdecznie w kolejnym RElog’u, czyli serii luźno powiązanej z Inżynierią Wsteczną. Dzisiaj zajmiemy się naprawą błędu pozostawionego przez roztargnionego programistę.

Chciałbym też przypomnieć, że ta seria nie jest serią poradnikami, a raczej serią pokazującą pewne rzeczy i mające raczej zachęcić do samodzielnego tematu. Jeżeli ktoś poszukuje poradników to z polskich źródeł polecam produkcje  w wykonaniu Gynvaela Coldwinda[1].

Oczywiście, jeżeli znajdziecie jakiś błąd (może się zdarzyć, ekspertem w tej dziedzinie nie jestem), albo macie jakieś własne metodyki pracy, albo po prostu chcielibyście zobaczyć artykułów tego typu więcej to zapraszam do systemu komentarzy pod tym wpisem. Miłej lektury.

 

Wstęp


Załóżmy, że mamy do czynienia z plikiem binarnym programu, który coś robi (przyjmijmy, że to jakiś edytor tekstu); nie mamy jego kodu źródłowego więc analiza kodu i jego zmiana odpada, musimy zadziałać na tym co posiadamy, czyli plik [exe (Windows)] / [elf (Linux)] (program to minimalna modyfikacja tworu z mojego kursu Qt [source]).

Na nasze nieszczęście autor programu przy wydawaniu programu zapomniał o usunięciu całego kodu testowego informującego o wciśniętej kombinacji klawiszy. Pozostawiona kombinacja klawiszy: [AltGr]+[L] aktywuje MessageBox z komunikatem „Hotkey detected!” jednocześnie uniemożliwiając napisanie litery „ł” (co oczywiście w edytorze tekstowym jest niepożądanym zjawiskiem).

Załóżmy, że bardzo nam brakuje wspomnianej funkcjonalności, sam edytor jest na tyle zaawansowany (przypominam, że to założenie a nie fakt!) i przyjemny, że postanawiamy zająć się tym problemem. Na początku próbowaliśmy napisać do autora z prośbą o poprawkę, lecz ten nie odpisuje, a więc zostaliśmy „zmuszeni” zając się tym sami.

 

Analiza


Po tym wstępie fabularnym czas zająć się konkretami. W ramach pełnej jasności: korzystam z 64 bitowego Linuxa (Mint), a do deasemblacji korzystam z programu [Hopper] (dostępnego także w wersji darmowej), jednakże w przypadku tego zadania równie dobrze sprawdzi się open-source’owe narzędzie [radare2], a nawet powinien wystarczyć debugger, np gdb.

Oprócz tego przyda nam się również HexEditor, na Windowsie polecam [hxd], a na Linuxie [wxHexEditor]. Jeżeli jesteście na Windowsie to fajnie jest ściągnąć mingw i podpiąć pod PATH narzędzie strings.exe, w przypadku Linuxów zazwyczaj jest to już zrobione, o ile posiadacie kompilator gcc.

 

Krok 1 – analiza programu „od zewnątrz”

Pod terminem: „od zewnątrz”, rozumiem analizę programu bez wchodzenia bezpośrednio w kod maszynowy. Warto zrobić ten krok, ponieważ on zazwyczaj mówi całkiem sporo (w bardzo trywialnych przypadkach już ten krok rozwiąże problem, albo prawie rozwiązuje).

Najbardziej podstawową rzeczą jaką możemy teraz zrobić, to użyć program file wchodzący w standardowy pakiet systemów opartych o Debiana (istnieje też port na Windowsa[2]).

Z tego polecenia możemy się dowiedzieć, że jest to plik wykonywalny skompilowany pod 64 bitowe urządzenia, pod procesory x86-64, korzysta z dynamicznie linkowanych bibliotek, pod jądro Linuxa 2.6.24. W tym przypadku te informacje są średnio przydatne, ale zawsze wiemy nieco więcej.

Kolejnym bardzo przydatnym narzędziem jest strings. Dzięki niemu jesteśmy w stanie wyciągnąć stringi z pliku wykonywalnego w nieco przyjaźniejszej formie niż analiza za pomocą hex edytora.

Należy zwrócić uwagę, że wciąż znajduje się w tym pliku wiele śmieci, które należy pominąć. Zachęcam do przeanalizowania zawartości rezultatu samodzielnie, a dopiero później do dalszej lektury tego artykułu.

Linia 4 dostarcza nam już potencjalnie ważnej informacji, mianowicie mówi nam, że program korzysta z biblioteki QtWidgets, po poszukaniu informacji w Internecie dowiadujemy się że jest to zestaw bibliotek C++ do tworzenia cross-platformowych aplikacji okienkowych [3]. Jednak to, że znależliśmy taki wpis to nie oznacza jeszcze, że ten program jest w całości napisany w tej bibliotece, może po prostu używać niektórych funkcji/modułów.

Jednak wyrażenie Qt pojawiające się kilkakrotnie (z nazwami różnymi modułów) i te poniżej upewniają nas w pewności, że przynajmniej okienka zostały napisane w Qt. Świetnie, teraz wiemy z jakiej dokumentacji będziemy musieli korzystać.

Dalej widzimy sekcję z tekstem widocznym w samej aplikacji oraz nazwy, które wyglądają jak nazwy funkcji/metod mających zareagować na odpowiednie zdarzenia, przy okazji widzimy okropny nawyk nazywania obiektów po polsku (wybaczcie, dawno pisałem tamten kurs i miałem okropne nawyki :/ ).

Najbardziej interesującą linią w ramce powyżej jest ta ostatnia: „onHotkeyDetected”, wiemy jak nazywa się metoda wywołująca ten okropny MessageBox!

Dalej widzimy jeszcze jakiś tekst, ale jest on raczej mało istotny, przejdźmy do kolejnego kroku.

 

Krok 2 – analiza poprzez uruchomienie

Jeżeli wiemy, że program jest nieszkodliwy (a taki jest), to możemy go przeanalizować po prostu uruchamiając go i sprawdzając jak działa. My jednak to wiemy (bo to jest nasz ulubiony edytor tekstowy), to co jest warte uwagi to fakt, że stringi zauważone przez nas powyżej, powtarzają się i w samym programie, a po wciśnięciu skrótu AltGr + L wyświetla nam się MessageBox.

scrn-messagebox

Zauważamy również, że nie jesteśmy w stanie pisać po wyświetleniu MessageBoxa, a także nie możemy zmienić focusu ponownie na pole tekstu o ile nie wyłączymy powiadomienia. Warto tutaj nieco poszukać informacji o samym Qt, abyśmy wiedzieli jakiego wywołania funkcji szukamy. Po chwili szukania znajdujemy dokumentację klasy [QMessageBox].

Dla nas interesującą informacją jest to, że do wywołania funkcji potrzebne są minimum 3 argumenty: 2 stringi, które znamy (nagłówek: „Debug Info”, wiadomość: „HotkeyDetected”) oraz jakaś liczba, która jest wskaźnikiem na rodzica. Mając tyle informacji możemy spróbować zmierzyć się z problemem.

 

Krok 3 – analiza kodu maszynowego

W tym kroku chcemy znaleźć miejsce odpowiedzialne za uruchomienie samego MessageBox’a; sprawa (mimo że się taka nie wydaje) to jest dość trywialna, ponieważ miejscem stworzenia komunikatu jest też miejscem wrzucenia na stos znanych nam już stringów (w celu przekazania jako argumenty do funkcji tworzącej msgboxa).

W panelu po lewej wybieram zakładkę Strings wyszukuję „HotkeyDetected”, wybieram go i główny ekran z kodem przeniósł się do miejsca sekcji z linią alokującą ten string.

hopper-1

Jeżeli zwrócimy uwagę na komentarz wygenerowany przez Hopper’a, to zobaczymy nazwy funkcji/referencje do miejsc użycia tego string’a.

Co ważne: referencja do miejsca jest klikalna, dzięki czemu możemy się przenieść w łatwy sposób do interesującej nas funkcji.

hopper-2

Ponownie nie interesuje nas większość tego co się tutaj dzieje, interesują nas głównie wszelkie call’e. Widzimy przygotowanie argumentów i wrzucenie ich na stos. Oprócz tego widzimy naszą znienawidzoną funkcję:

hopper-3

W końcu widzimy miejsce, w którym jest wywołanie tworzące MessageBoxa, znamy jej lokalizację więc możemy się jej pozbyć!

 

Rozwiązanie problemu – podejście 1


Istnieje kilka potencjalnych rozwiązań dręczącego nas problemu:

  1. (do którego dążymy) zaNOPowanie wywołania metody tworzącej komunikat, sposób niezwykle prosty polegający na podmianie instrukcji CALL („wywołaj funkcję i zapisz adres powrotu”), na NOP („nic nie rób, idź do kolejnej instrukcji”). Aby to zrobić musimy zamienić odpowiednie bajty na 0x90, który jest właśnie instrukcją NOP (no operation);
  2. można znaleźć miejsce przypisania skrótu klawiszowego i zamienić skrót lub usunąć jego stworzenie, problemem może być to że nie wiemy czy sam skrót nie został stworzony jakoś inaczej i czy nie zostały wykonane wtedy dodatkowe akcje, to by tłumaczyło brak skrótu [AltGr]+[L] w panelu ze string’ami.

A więc otwórzmy naszą binarkę w ulubionym hex edytorze i zamieńmy naszego calla na 0x90.

Zaznaczony ciąg zamieniamy na 90 90 90 90 90
Zaznaczony ciąg zamieniamy na 90 90 90 90 90

Aby to zrobić, to w przypadku wxHexEditor wystarczy: zaznaczyć interesujący nas fragment -> otworzyć menu kontekstowe -> wybrać opcję: Fill Selection, a w oknie dialogowym wpisać ’90’ (bez znaków ‚ ‚). Następnie zapisujemy plik, wyłączamy hex editor (wxHexEditor ma brzydki zwyczaj blokowania binarki) i uruchamiamy program.

Program uruchomił się, więc jest ok. Wywołujemy skrót [AltGr] + [L] i… nic się nie dzieje, wywołujemy skrót szybko drugi raz: pojawia się litera „ł”.

scrn-success-and-failure

Wnioski: udało nam się, ale tylko połowicznie. Jak można było przypuszczać program wciąż przechwytuje skrót, jednak z wiadomego powodu nie tworzy już komunikatu (usunęliśmy odpowiedzialny za to fragment). Jedno wywołanie kombinacji idzie na skrót klawiszowy, drugie na wypisanie litery. Aby rozwiązać problem (chyba że zadowalamy się faktem, że do napisania ‚ł’ musimy wcisnąć kombinację dwukrotnie) musimy powrócić do analizy i zlikwidować tworzenie się skrótu.

 

 

Rozwiązanie problemu – podejście 2


Po chwili researchu i czytania o Qt dowiadujemy się że skróty klawiszowe mogą powstać przez stworzenie obiektu QKeySequence oraz muszą być połączone przez funkcję connect. Stosując intuicję i nasze doświadczenie w programowaniu stwierdzamy, że te czynności mogły zostać zrobione w np. konstruktorze, więc postanawiamy go znaleźć.

hopper-4

Udało nam się go zlokalizować i co za radość! Znaleźliśmy fragment tworzący obiekt wcześniej wspomnianej klasy oraz łączący event aktywowania go z najprawdopodobniej głównym oknem. Co ciekawe: autor kodu w tym przypadku zamiast skrótu w postaci stringa użył kodu klawiszy, przez co nie byliśmy znaleźć tego skrótu w tabeli z tekstem.

Widzimy także 2 call’e: jeden z nich to „normalny” connect,a  drugi to specjalny dodatek od Qt tworzący MetaObject, sprawdźmy co się stanie po zaNOPowaniu tego pierwszego (nie liczymy na sukces)… Nic się nie dzieje, program się nie uruchamia. Widocznie obiekty meta nie zgadzają się z faktycznym stanem obiektów, więc przy wstępnej weryfikacji program postanawia się zakończyć.

Zróbmy NOP’a także na drugim callu… Program się uruchamia, ale istnieje dokładnie ten sam problem co wcześniej: do wygenerowania polskiej litery potrzebne jest podwójne wywołanie kombinacji klawiszy. Jednak nie poddajemy się, powracamy do dokumentacji i zastanawiamy co może być tego powodem.

Po krótkiej lekturze dokumentacji dowiadujemy się, że w momencie stworzenia skrótu klawiszowego tworzy się także informacja dla programu aby zwracał szczególną uwagę na wciśnięcia klawisza AltGr. Dobra, a więc powracamy do kodu maszynowego i NOPujemy wszystkie calle (w konstruktorze) zawierające wyrażenie „KeySequence”, widzimy 2 takie wywołania.

Zapisujemy plik i go sprawdzamy…

success_kid_meme-www.memegen.com

Yay! Wszystko działa poprawnie, usunęliśmy pozostały „lag” na polskie znaki.

 

Podsumowanie


Mimo woli powstał mi taki mini-poradnik, który radzę traktować z przymrużeniem oka, bo to nie uczenie reverse-engineeringu było celem tego artykułu, a jedynie pokazanie jak ciekawe i złożonym jest zajęciem.

Chciałbym też zwrócić uwagę, że zrobienie tego co ja zrobiłem w tym przypadku jest w większości sytuacji zabronione przez prawo. Deasemblacja programu łamie prawa autorskie, nie mówiąc już nawet o modyfikacji programu (tak, ten sposób może posłużyć do omijania zabezpieczeń, np weryfikujących licencję, itp), dozwolone jest to jedynie w raptem kilku przypadkach[4]).

/*jeżeli ktoś by mógł skompilować kod pod Windowsa i podesłać mi binarke np w komentarzach to byłym wdzięczny */

Zapraszam także do zapoznania się z materiałami dodatkowymi oraz systemu komentarzy.

 

Materiały dodatkowe


  1. [Gynvael Coldwind]
  2. [GnuWin – port programów na Windowsa]
  3. [Qt – opis]
  4. [Dekompilacja, a prawo autorskie]

Code ON!


  • Adam Pajkert

    „czyli plik [.exe (Windows)] / [.elf (Linux)] ” -> PE ( *.exe | Windows), ELF ( brak rozszerzenia, lecz posiada specjalny nagłówek dzięki czemu jest rozpoznawalny | Linux ). Tak dla jasności, żeby nikt nie pomyślał że binarki na Linuchu mają rozszerzenie ELF 😀 W linuxie właściwie są one zbędne. Pozdrawiam 😀

    • Dla uściślenia: Windowsowe pliki binarne też posiadają specjalny nagłówek i do uruchomienia takiego pliku nie potrzebujesz „.exe” (z poziomu konsoli uruchomisz binarke bez problemu, po prostu w trybie „graficznym” Windows nie rozpozna pliku wykonywalnego).
      Pozdrawiam