Jakiś 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.
Maj 9th, 2009 at 2:18 pm
// HASP HL import recovery
var IATstart
var IATend
var IATptr
var lowrange
var highrange
var addr
var count
var pc
// change this vars from target to target
mov IATstart, 1001000 // find these urself
mov IATend, 1001228
mov lowrange, 7e0000 // low range of redirected apis
mov highrange,7f0000 // ..
mov count, 0
mov IATptr, IATstart
sub IATptr, 4
next:
add IATptr, 4
cmp IATptr, IATend
je fin
mov addr, [IATptr]
cmp addr, lowrange
jb next
cmp addr, highrange
ja next
// here we know addr is redirected
eval “Found redirected import at {addr}”
log $RESULT
inc count
log count
mov eip, addr
ticnd “eip > 50000000″
// boom, we break at GetTickCount
// get out
sti
sti
sti
sti
sti
sti
ticnd “eip > 50000000″
// eip = address of original import
mov [IATptr], eip
mov pc, eip
gn eip
eval “{addr} resolved to {pc} – {$RESULT}”
log $RESULT
jmp next
fin:
eval “{count} imports resolved”
log $RESULT
ret
ten ciag komend sti sluzy do przejscia przez spatchowany GetTickCount (jakis plugin mialem wlaczony), dlatego musialbys sprawdzic u siebie jak to bedzie dzialac.
niektore API sa calkowicie emulowane, np GetProcAddress, wiec te trzeba jakos recznie rozpoznac.
HASP HL to ogolnie gowno, ale ta emulacja importow rzeczywiscie im sie udala