relog-obfuscation

[RElog] Ukrywanie danych (string’ów) wewnątrz pliku wykownywalnego

 

Kilka słów o uniemożliwieniu wyciągnięciu np. hasła do bazy danych, z której korzysta program.

Załóżmy, że posiadamy napisaliśmy program, który z jakiegoś powodu musi posiadać jakieś wrażliwe dane (np login i hasło do bazy danych) zapisane „na sztywno” w kodzie.

Jak łatwo zdać sobie sprawę takie dane domyślnie nie są w żaden chronione i są potencjalnie wystawione na atak. W tym wpisie chciałbym się skupić na kilku potencjalnych rozwiązaniach tego problemu.

Przykładowy kod


Dla pełnego zrozumienia problemu poniżej przedstawiam uproszczony program, którego celem jest „połączenie się” z bazą danych.

Jak widzimy dane do logowania trzymamy jako zwykły string, przekonajmy się dlaczego nie jest to dobry pomysł:

Jak widzimy już takie proste narzędzie jak strings umożliwia znalezienie nam stringów przechowujących nasz login i hasło z wnętrza pliku wykonywalnego.

 

Rozwiązania problemu


Poniżej chciałbym przedstawić kilka potencjalnych rozwiązań problemu, które intuicyjnie przychodzą do głowy

  1. Zaszyfrowanie danych.
  2. Zaciemnienie sekcji danych i kodu.
  3. Wymuszenie podania loginu i hasła w trakcie działania programu
  4. Usunięcie bezpośredniego łączenia z bazą danych, komunikacja z bazą danych tylko przez serwer.

Dwa ostatnie rozwiązania są potencjalnie najbezpieczniejsze, a to dlatego że zakładają zrzucenie odpowiedzialności weryfikacji danych na serwer, wrażliwych danych nie ma w pliku wykonywalnym.

Jednakże nie zawsze możemy zastosować to rozwiązanie, a to dlatego że tymi danymi nie zawsze są dane służące do logowania, a np kod wykonujący jakiś sprytny algorytm, dodajmy że program w takim przypadku może pracować w trybie offline.

 

1. Szyfrowanie

Szyfrowanie jest rzeczą, która nasuwa się na samym początku jednakże należy pamiętać, że skoro nasz program jest w stanie dane odszyfrować, to postronna osoba po chwili analizy będzie w stanie zrozumieć nasz program i cały proces odwrócić.

Załóżmy że zaszyfrowaliśmy dane do logowania i proces odszyfrowania odbywa się w 100% po stronie programu chwilę przed logowaniem, załóżmy że teraz nie byliśmy w stanie znaleźć loginu i hasła narzędziem strings ponieważ te dane niczym się nie różniły od innych śmieci.

Spójrzmy na fragment kodu odpowiedzialnego za łączenie z bazą danych (dla zwiększenia czytelności wynik z Hoppera):

Po chwili analizy jesteśmy w stanie stwierdzić, że:

  • username = „EYs]_M@DqEM”
  • password = „EYsBq__[C@N”

Zauważamy również call’e do funkcji o nazwie „_Z7decryptSs”, sama nazwa łudząco przypomina wyrażenie „decrypt”, a więc spróbujmy się w niej zanurzyć aby zrozumieć jak działa.

To co tutaj widzimy to stworzenie pustego obiektu klasy std::string oraz pętlę przechodzącą po całym stringu. Daruję sobie całą analizę i skupmy się na właściwym fragmencie „szyfrującym”:

Mamy tutaj wrzucenie poszczególnego znowu do rejestru eax, następnie zostaje on xorowany z wartością 47 (0x2f), po czym zostanie dodana liczba 3 (0x3)

Jak łatwo się domyślić cały proces jest bardzo łatwo odwrócić operacją odwrotną dla xor jest xor, a dla dodawania odejmowanie (w razie gdybyśmy chcieli odwrócić cały proces), napiszmy sobie mały program, którego zadaniem jest deszyfrowanie:

Jak się przekonamy, to jest kalka tego co widzieliśmy w postaci kodu maszynowego, otrzymane dane zgadzają się z tym co znaleźliśmy wcześniej (my_username:my_password). Szyfrowanie mogło wyglądać w ten sposób:

Jak widzimy nie tylko poznaliśmy zaszyfrowane login i hasło, ale zdołaliśmy także ten proces odwrócić!

Zazwyczaj wystarczy nam jedynie znalezienie sposobu na poznanie odszyfrowanego loginu i hasła (równie dobrze mogliśmy też śledzić w debuggerze proces deszyfrowania i w ten sposób te dane wyciągnąć), największą słabością tego sposobu jest to, że kod maszynowy jest czytelny i w większości przypadków po odpowiednio długiej analizie powinniśmy być w stanie go odtworzyć.

 

2. Obfuskacja

Wikipedia:

Zaciemnianie kodu (także obfuskacja, z ang. obfuscation) to technika przekształcania programów, która zachowuje ich semantykę, ale znacząco utrudnia zrozumienie

Obfuskacja kodu

Jednym ze sposobów zaciemnienie kodu jest wykorzystanie faktu, że instrukcja MOV jest kompletna z punktu widzenia Maszyny Turinga[1] (polecam zajrzeć do tego dokumentu) to możliwe jest zastąpienie wielu innych instrukcji używając wyłącznie instrukcji MOV. Co to oznacza? Otóż dla (nie tylko) przeciętnego reversera znaczniej prostsza jest analiza kodu generowanego normalnie przez kompilator. Przykładowo poniżej widzimy zrzut funkcji main wyświetlającej „Super Secret String” (standardowe opcje gcc).

Poniżej widzimy fragment robiący dokładnie to samo,  skompilowany domyślnymi opcjami movcc

W tym momencie ręczna analiza robi się momentalnie mało przyjemna. Jeżeli kod wykonywałby np. jakieś szyfrowanie to nam byłoby niezwykle trudno zrozumieć ten algorytm analizując wyłącznie jego kod maszynowy.

W powyższych listingach użyłem [movfuscator’a]. Narzędzia tego typu mają swoje ograniczenia (np. movfuscator nie ukrywa stringów, ale w tym przypadku można zastosować np system obfuskacji stringów[2]), ale w przypadku zaciemnienia jedynie fragmentów odpowiadających za jakieś magiczne obliczenia powinien się nadać i niewątpliwie wydłuży przynajmniej w nieznacznym stopniu poznanie wszystkich tajników naszego kodu o czym już wspomniałem.

VM

Innym potencjalnym sposobem na jeszcze dodatkowe zaciemnienie kodu jest utworzenie maszyny wirtualnej, która obsługiwałaby naszą własną składnię języka maszynowego. Całkiem sporo opowiedział o tym [Gynvael Coldwind], w skrócie chodzi o to aby ściśle tajny program był napisany w naszym własnym języku, następnie my taki wygenerowany bajt-kod przekazujemy do naszego normalnego programu, z kolei on uruchomi maszynę wirtualną, a na niej wykona nasz algorytm.

W takim przypadku aby zrozumieć algorytm to należy najpierw zrozumieć jak działa maszyna wirtualna -> napisać disassembler -> (teraz można) zrozumieć algorytm, w dodatku przy tego typu obfuskacji nikt nie mówi, że sama składnia musi być „przyjazna”, co pokazali Gyn i j00ru, a rozbił na części pierwsze [pakt].

 

Podsumowanie


Jak widać istnieje wiele sposobów na utrudnienie pracy reverserowi, jeżeli możemy to powinniśmy zrzucić część którą chcemy chronić od wystawienia na widok np. na serwer, który jedynie zwróci wynik.

Chciałbym też zwrócić uwagę, że ten temat został opisany przeze mnie jedynie pobieżnie (nie poruszyłem np. tematu packerów) i na pewno nie został wyczerpany, zachęcam do samodzielnego zgłębienia tematu i zajrzenia do m.in. „Materiałów dodatkowych”.

 

Materiały dodatkowe


Code ON!


  • Witam. Poradnik napisany zrozumiale. Mniejsza. Nie chcę się podlizywać, a zapytać o to, z jakiej wtyczki korzystasz, by tworzyć kolorowanie składni? Dziękuję – z góry. 🙂

    • Ciężko mi to nazwać poradnikiem, ale niech będzie. Odpowiadając na Twoje pytanie: używam Crayon Syntax Highlighter.