Themida IAT rebuilder script

IAT Redirection

Themida to exe-protektor, który posiada opcję ukrywania tabeli importów aplikacji poprzez ukrycie faktycznych adresów funkcji w bibliotekach DLL.

Działa to na tej zasadzie, że w zabezpieczonej aplikacji, po jej uruchomieniu, adresy funkcji w tabeli importów, które normalnie wskazuja na jakieś funkcje, np. na ExitProcess(), sa zamienione adresami kodu, który w końcu wykona skok do przykładowego ExitProcess().

Technika ta ma nazwę API Redirectingu lub inaczej – IAT Redirection, Import table redirection (w obiegu jest kilka nazw).

Celem tej techniki zabezpieczeń jest uniemożliwienie odtworzenia struktury pliku wykonywalnego z pamięci, praktycznie każdy nowoczesny protektor wykrzystuje jakąś formę tej ochrony.

Tablica importów

Struktura tabeli importów w bardzo dużym skrócie przedstawia się następująco (dla bardziej zaciekawionych polecam poszukać IMAGE_IMPORT_DESCRIPTOR):

Tablica importów pliku PE

Tak wygląda ona w niezabezpieczonym pliku, czyli mamy bloki odpowiadające kolejnym bibliotekom, z których korzysta nasza aplikacja, w każdym bloku znajdziemy adresy tych funkcji (po załadowaniu aplikacji, wskazują na bezpośredni kod np w KERNEL32.dll).

Po potraktowaniu pliku protektorem Themida, po jego załadowaniu do pamięci, struktura będzie wyglądać następująco:

IAT Redirection

Przykładowa funkcja ExitProcess() ma taki kod:

KERNEL32.ExitProcess

Widać, że po zabezpieczniu, oryginalne instrukcje z funkcji zostały pomieszane z kodem zaciemniającym.

Jak działa redirecting w Themida

Proces redirectingu wygląda następująco:

  • Themida tworzy tymczasowy bufor pamięci na kod redirectora
  • oryginalna funkcja ExitProcess() poddana jest procesowi deasemblacji
  • w kodzie redirectora umieszczane są bloki i instrukcje zaciemniające działanie (nie robiące nic, taki syf po prostu)
  • początkowe instrukcje z funkcji ExitProcess() poddane są mutacji (jedna instrukcja jest rozbijana na równoważny zestaw innych instrukcji)
  • kod zmutowanych instrukcji oryginalnej funkcji jest pomieszany z kodem zaciemniającym
  • na samym końcu kodu redirectora, umieszczony jest skok do pozostałego fragmentu funkcji ExitProcess()

Całość sprawia, że wywołanie funkcji ExitProcess() z poziomu aplikacji przebiega dokładnie tak samo, jak to ma miejsce w niezabezpieczonej aplikacji.

Jak odtworzyć tak zabezpieczoną tablicę importów?

Publiczne narzędzia jak np. ImpRec posiadają wtyczki potrafiące poradzić sobie z różnymi zabezpieczeniami tabel importów, jednak akurat mój przypadek był taki, że musiałem skorzystać ze skryptu ODbgScript, który służy do sterowania debuggerem OllyDbg.

Jak działa mój skrypt

Kilka słów o działaniu mojego skryptu, kolejne etapy przedstawiają się tak:

Skanowanie obszaru tabeli importów

Aplikacja po załadowaniu do debuggera i dojściu do OEP jest zatrzymywana po czym skrypt skanuje ręcznie wybrany obszar wskazujący na tabelę importów (na tabelę wskaźników do funkcji).

Na tym etapie odrzucane są błędne wskaźniki oraz funkcje, które nie zostały  poddane procesowi zabezpieczenia, są to np. adresy funkcji, które nie są funkcjami, a np. wskaźnikami na dane w bibliotece DLL, przykład to funkcja _acmdln() z rodziny bibliotek MSVCRT.dll).

Odbudowa kodu redirectora

Jeśli znaleziony zostanie kod redirectora dla kolejnej funkcji, poddawany jest on takim procesom jak:

  • optymalizacja mająca na celu usunięcie śmieci z kodu redirectora (tzw. junki)
  • przebudowa adresów docelowych instrukcji jxx (branche), które podczas zabezpieczenia kodu funkcji były rozwinięte do form długich (jxx near), oraz ich skrócenie do form krótkich (jxx short)
  • demutacja rozbitych instrukcji na oryginalne formy, Themida stosuje niewielki zestaw kilku mutacji dla najpopularniejszych instrukcji
  • ponowna optymalizacja kodu, poprawienie branchów, usunięcie śmieci powstałych z demutacji

Tworzenie patternów

Odbudowany kod redirectora po takim procesie będzie w 99% wierną kopią zredirectowanych początkowych instrukcji zabezpieczonej funkcji.

Teraz, mając fragment oryginalnej funkcji, tworzony jest z niej wzorzec binarny (z maskowaniem adresów niektórych instrukcji, np. „E9????????”).

Taki wzorzec jest następnie poszukiwany w pamięci bibliotek DLL aplikacji i jeśli zostanie znaleziony jest to równoznaczne ze znalezieniem poprawnej funkcji, której adres może być umieszczony w pamięci tabeli importów i tym razem bezproblemowo można skorzystać z narzędzi typu ImpRec do odbudowy tabeli importów i symbolicznych nazw wykorzystanych funkcji.

Wady, zalety i efekty uboczne

Do wad skryptu należy to, że w 1% przypadków odbudowany kod niekoniecznie będzie idealnie taki jak kod ze zredirectowanej funkcji i tym samym nie będzie możliwe po jego wzorcu znalezienie tej owej oryginalnej funkcji (zostaje ręczna robota przy tym).

Sporą wadą, chociaż bardziej efektem ubocznym jest też czas wykonywania skryptu w OllyDbg, który może sięgać 5 minut dla zabezpieczonych aplikacji.

Zaletą jest natomiast cała reszta, która pozwala w prosty sposób odbudować tabele importów oraz dokonać błyskawicznych zmian, biorąc poprawki na jakieś wyjątkowe warunki w kodzie.

Kod skryptu

Kod skryptu do ściągnięcia – themida_iat_rebuilder.zip (6 kB)

Wszelkie uwagi mile widziane.

HASP Envelope

Sklonowany klucz HASPJakiś czas temu znajomy poprosił mnie o usunięcie zabezpieczenia HASP Envelope z aplikacji. HASP Envelope to rodzaj exe-protectora, który powoduje, że nie moża uruchomić aplikacji bez włożonego klucza sprzętowego (tzw. dongle) lub tak jak na obrazku, jego klona.

Opcje zabezpieczające systemu HASP Envelope może nie należą do najbardziej zaawansowanych, jakie obecnie są używane na rynku (chociaż jeśli chcemy śledzić kod z OllyDbg, musimy wesprzeć się pluginem ukrywającym jego obecność, najlepiej PhantOm Plugin), jednak utknąłem w pewnym momencie na zabezpieczeniu tabeli importów, które wykorzystuje technikę API redirectingu.

API redirecting w wydaniu firmy Alladin jest jednym z najbardziej uciążliwych w śledzeniu jakie widziałem, większość procedur wypełniających tabelę importów jest zmutowana albo poddana obfuskacji, co sprawia, że codeflow jest nielinearny i ciężki do śledzenia.

Piszę o procedurach wypełniających tabelę importów, bo jest to jedna z moich ulubionych metod uzyskiwania oryginalej tabeli importów, poprzez zpatchowanie procedur wypełniających importy (udało mi się tą metodą otworzyć zaledwie garstkę funkcji).

Na pewno ktoś pomyślał „Dlaczego nie skorzystałeś z ImpRec-a?”, programiści Alladina byli na tyle sprytni, że kod przekierowanych funkcji przechodzi przez masę równie zagmatwanych funkcji oraz wykorzystuje ciekawe mechanizmy jak kopiowanie stanu stosu przed bezpośrednim wywołaniem danej funkcji (czytaj protector musiał znać każdą API i ilość jej parametrów), co skutecznie blokuje wszystkie metody śledzenia wykorzystywane przez rebuilder ImpRec.

Trochę podłamany nieskutecznością standardowych metod, zacząłem kombinować jak do tego podejść. Po wielu próbach postanowiłem wykorzystać proste fakty:

  • znajomość położenia tabeli importów w pliku wykonywalnym
  • każde wywołanie funkcji API w końcu do niej doprowadzi

Tracing z wykorzystaniem ImpRec-a nie dał rezultatów, co nie znaczy, że ta metoda była nieskuteczna, jedynie jej implementacja.

Postanowiłem zbudować własnego tracera w oparciu o język skryptowy ODbgScript przeznaczony oczywiście dla debuggera OllyDbg. Idea jego działania jest następująca:

  • pobierane są kolejne adresy funkcji z tabeli importów
  • zachowywany jest stan rejestrów wejściowych
  • uruchamiane jest śledzenie warunkowe
  • sprawdzane są warunki
  • jeśli warunki się zgadzają, jesteśmy w kodzie funkcji API
  • jej adres jest umieszczany w tabeli importów
  • skok do początku

W trakcie pisania skryptu wyszło kilka faktów zabezpieczenia, wyżej wspomniane kopiowanie parametrów stosu w inne miejsce pamięci, co spowodowało, że tracer nie był w stanie rozpoznać, że jest w kodzie funkcji API, gdyż stan rejestru ESP przed wywołaniem funkcji i w jej kodzie był inny (takie warunki początkowo ustaliłem).

Metoda z kopiowaniem stanu stosu skutecznie uniemożliwia sprawdzanie adresu ESP, jednak stan stosu musi być zachowany dla poprawności wywoływanych funkcji, stąd nowym warunkiem było wprowadzenie na stos 2 fałszywych parametrów, można powiedzieć markerów,  które każdorazowo były sprawdzane, gdy wskaźnik kodu po śledzeniu warunkowym znajdował się poza przestrzenią adresową aplikacji (czyli w kodzie innej biblioteki).

;
; odbudowa tabeli IAT w aplikacjach
; zabezpieczonych HASP Envelope
;
  var     safe_eax
  var     safe_ecx
  var     safe_edx
  var     safe_esi
  var     safe_edi
  var     safe_ebx
  var     safe_esp
  var     safe_ebp

  var     iat_entry
  var     iat_entry_ptr
  var     iat_dword
  var     x
  var     y

  bphwcall                                ; usun bp na hardware id
  bc                                      ; i wszystkie normalne bp

  bphws   06688AF,"x"                     ; hw bp na OEP

  run

; tu jestesmy w OEP

; zachowaj stan rejestrow do call-a
  mov     safe_eax,eax
  mov     safe_ecx,ecx
  mov     safe_edx,edx
  mov     safe_esi,esi
  mov     safe_edi,edi
  mov     safe_ebx,ebx
  mov     safe_esp,esp
  mov     safe_ebp,ebp

; poczatek tabeli importow
  mov     iat_entry,0685000

resolve_next_api:

; kilka funkcji WinAPI musi być pominietych
  cmp     iat_entry,0685108               ; DecodePointer?
  je      resolve_api_skip
  cmp     iat_entry,06851AC
  je      resolve_api_skip

  mov     iat_entry_ptr,[iat_entry]
  cmp     iat_entry_ptr,0
  je      resolve_api_skip

  cmp     iat_entry_ptr,50000000
  je      resolve_api_skip

; czy to instrukcja CALL (1 instrukcja api redirectingu)?
  mov     iat_dword,[iat_entry_ptr]
  and     iat_dword,0FF
  cmp     iat_dword,0E8
  jne     resolve_api_skip

; zachowaj na stosie fałszywe markery
  exec
          push    0AAAAAABB
          push    066AA9F
  ende

  mov     eip,iat_entry_ptr

; run trace into

trace_again:
  ticnd   "eip > 50000000"                ; przerwij śledzenie
                                          ; jesli EIP > 50000000

  cmp     eip,07C80929C                   ; GetTickCount
  je      trace_skip
  cmp     eip,07C813093                   ; IsDebuggerPresent
  je      trace_skip

; sprawdz czy na stosie znajduje sie markery
  mov     y,esp
  add     y,4
  cmp     [y],0AAAAAABB
  je      trace_hit

trace_skip:

  rtu
  jmp     trace_again

trace_hit:

; pause
  bphwcall

  log     eip

; wstaw oryginalny adres funkcji API
  mov     [iat_entry],eip

; przywroc stan rejestrow
  mov     eax,safe_eax
  mov     ecx,safe_ecx
  mov     edx,safe_edx
  mov     esi,safe_esi
  mov     edi,safe_edi
  mov     ebx,safe_ebx
  mov     esp,safe_esp
  mov     ebp,safe_ebp

  jmp     resolve_api_next

resolve_api_skip:

; pause

resolve_api_next:

  add     iat_entry,4
  cmp     iat_entry,685340
  jb      resolve_next_api

resolve_api_exit

  ret

Skrypt nie jest idealny, gdyż kilka funkcji API śledzonych tą metodą powoduje zawieszenie debuggera (należy je ręcznie dodać do omijanych pozycji z tabeli IAT), dodane także musiały być metody sprawdzające czy wskaźnik kodu nie znajduje się w funkcjach WinAPI wykorzystywanych w kodzie API redirectingu (wskutek czego ich adresów nie ma w odbudowanej tabeli IAT i trzeba ręcznie je skorygować).

To by było na tyle, jeśli ktoś zna jakąś ciekawszą metodę na odbudowę tabeli importów w plikach zabezpieczonych HASP Envelope chętnie wysłucham waszych komentarzy.