DataHASP

DataHASP to system szyfrowania dowolnych plików aplikacji, dzięki któremu oryginalna treść takich zaszyfrowanych plików widoczna jest jedynie dla aplikacji, które zostały zabezpieczone przez Sentinel HASP Envelope (exe-protector HASPa).

HASP pozwala na szyfrowanie pojedynczych plików jak i całych katalogów z danymi aplikacji poprzez dodatkowe narzędzie:

W samym envelope określa się rodzaj plików, które zostały zaszyfrowane:

Zaszyfrowane pliki aplikacji może odczytać jedynie zabezpieczona aplikacja, realizowane jest to poprzez system hooków na funkcje systemu plików na poziomie usera.

Jeśli aplikacja zostanie rozpakowana, dostęp do oryginalnej treści zaszyfrowanych plików będzie niemożliwy (brakuje tych hooków, które w locie deszyfrują dane).

Jak uzyskać zatem dostęp do oryginalnej treści zaszyfrowanych plików?

Należy do działającej i zabezpieczonej aplikacji wstrzyknąć kod, który wykorzysta jej funkcje systemu plików (na które nałożone są hooki HASPa) i po prostu zaemulować czytanie wybranych plików.

Najprościej można to zrealizować poprzez skrypt ODBScript:

; wolna przestrzen po sekcji kodu
	mov	dump_hasp,008864D5

	mov	eip,dump_hasp
	asmtxt	eip,"dumper.asm"

; alokuj 2 bufory na nazwe pliku wyjsciowego i wejsciowego
	alloc	512
	mov	input_file,$RESULT
	alloc	512
	mov	output_file,$RESULT

; zrzuc 1 plik
	mov	x, "C:\PATH\APP\DATA.DAT"
	call	dump_file

; zrzuc 2 plik
	mov	x, "C:\PATH\APP\CONFIG.DAT"
	call	dump_file

	ret

; funkcja do zrzucania zaszyfrowanych plikow
dump_file:

	fill	input_file, 512, 0
	mov	[input_file], x

; plik wyjsciowy bedzie posiadal rozszerzenie .x
	add	x, ".X"
	fill	output_file, 512, 0
	mov	[output_file],x

; ESI -> sciezka pliku wejsciowego
; EDI -> sciezka pliku wyjsciowego
	mov	esi,input_file
	mov	edi,output_file

; ustaw EIP na adres procedury dumpujacej
	mov	eip,dump_hasp

; exec ... ende za chiny nie dziala u mnie... inaczej bym tu dal call dump_hasp?
; uruchom kod z biezacego EIP do napotkania instrukcji RET
	rtr ;run to return

	mov	x, ""

	ret

Pomocniczy kod w assemblerze dla skryptu:

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
; odczytaj plik wejsciowy
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

; zapamietaj na stosie nazwe pliku wyjsciowego
	push	edi

; otworz plik wejsciowy
	push	0				; hTemplate
	push	0				; attribs
	push	3 ;OPEN_EXISTING		; creation
	push	0				; security
	push	1				; share mode
	push	80000000 ;GENERIC_READ
	push	esi				; lpFileName
	call	[0800004] ;CreateFileA
	mov	ebx,eax

	push	0
	push	ebx				; hFile
	call	[0800008] ;GetFileSize
	mov	edi,eax

; zaalokuj pamiec do odczytania danych pliku wejsciowego
	push	4 ;PAGE_READWRITE
	push	3000 ;MEM_RESERVE or MEM_COMMIT
	push	edi				; size
	push	0
	call	VirtualAlloc
	mov	esi,eax
	
; czytaj plik wejsciowy korzystajac z hookowanych API
	push	0
	mov	eax,esp

	push	0				; dwOverlapped
	push	eax				; &dwRead
	push	edi				; dwSize
	push	esi				; lpBuffer
	push	ebx				; hFile
	call	[0800024] ;adres funkcji ReadFile w zabezpieczonym pliku

	pop	edx

; zamknij plik wejsciowy
	push	ebx
	call	[0800088] ;CloseHandle

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
; zapisz plik wyjsciowy
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

; zdejmij ze stosu nazwe pliku wyjsciowego (odszyfrowany)
	pop	eax

	push	0				; hTemplate
	push	0				; attribs
	push	2 ;CREATE_ALWAYS		; creation
	push	0				; security
	push	0				; share mode
	push	40000000 ;GENERIC_WRITE
	push	eax				; lpFileName
	call	CreateFileA
	mov	ebx,eax

; zapisz odczytana tresc pliku wejsciowego
	push	0
	mov	eax,esp

	push	0				; overlapped
	push	eax				; &dwWritten
	push	edi				; size
	push	esi				; buffer
	push	ebx
	call	WriteFile

	pop	edx

; zamknij plik wyjsciowy
	push	ebx
	call	CloseHandle

; zwolnij pamiec
	push	8000 ;MEM_RELEASE
	push	0
	push	esi
	call	VirtualFree

	ret

Przedstawiona technika pozwala z łatwością odczytać wszystkie zaszyfrowane pliki.

HASP i Visual FoxPro 9

Ostatnio natrafiłem na aplikację stworzoną w środowisku Visual FoxPro 9, zabezpieczona była kluczem sprzętowym HASP Hardlock, a sam plik wykonywalny aplikacji był zaszyfrowany (tzw. envelope).

Struktura aplikacji Visual FoxPro

Z nielicznych kontaktów z oprogramowaniem Visual FoxPro wiedziałem, że każda taka aplikacja składa się z prostego loadera (który uruchamia cały engine VFP znajdujący się w dodatkowych plikach DLL) oraz właściwej aplikacji, która znajduje się na końcu pliku wykonywalnego (zapisana jako overlay), całość jest więc w jednym pliku wykonywalnym. Tak jest w przypadku programów niezabezpieczonych:

W przypadku zabezpieczenia HASP, na końcu zabezpieczonego pliku faktycznie znajdują się jakieś dane, ale nie są to dane aplikacji VFP, gdyż nie można jej zdekompilować oprogramowaniem ReFox.

Zabezpieczenia dla aplikacji z overlayami

Dane overlay (tak zapisane dane są w aplikacjach Flash, Director, Shockwave etc.) nie są mapowane w pamięci procesu i muszą być odczytane bezpośrednio z dysku. Popularną metodą ochrony takich aplikacji jest szyfrowanie tych danych i hookowanie funkcji systemu plików (np. CreateFile, ReadFile etc.) w samej aplikacji, poprzez podstawienie adresów funkcji zastępczych w tabeli importów aplikacji. Jeśli aplikacja otworzy swój własny plik i będzie probowała czytać dane overlaya, nie zostanie odczytane to co jest na dysku, ale to co zostało wczesniej zaszyfrowane.

Takie rozwiązanie przez hookowanie tabeli importów działa jedynie w przypadku samodzielnych plików (bez dodatkowych bibliotek), w przypadku aplikacji VFP, dane z pliku wykonywalnego czytane są przez dodatkowe biblioteki, więc wymagany byłby system wide hook.

Envelope HASP dla aplikacji VFP

System zabezpieczeń HASP dla takich plików, działa w ten sposób, że sterownik systemowy HASP-a podpina się pod funkcje systemowe Windows odpowiedzialne za czytanie plików i obecność oryginalnego overlaya jest emulowana tylko dla zabezpieczonej aplikacji i jej bibliotek. Wygląda to mniej więcej tak:

Odczytanie overlaya

Jak zatem uzyskać obraz całego overlaya? Z przeprowadzonych eksperymentów wynika, że dane nie są nigdzie czytane w całości, więc nie ma takiego punktu w programie (a grzebanie w engine VFP nie należy do najprzyjemniejszych), gdzie by można było zrzucić pamięć całego overlaya.

Jednak jeśli czytanie danych overlaya jest emulowane dla samej zabezpieczonej aplikacji, można wstrzyknąć kod czytający zawartość całego pliku do zabezpieczonej aplikacji i dzięki temu uzyskać dane odszyfrowanego overlaya.

Jest tutaj kilka problemów. Po pierwsze, aby wstrzyknąc kod należy dojść do OEP zabezpieczonej aplikacji, co może sprawić trochę problemów, ja skorzystałem z OllyDbg i pluginu Phant0m, ponieważ envelope HASP-a wykrywa popularne narzędzia do debuggowania, co można zobaczyć na zrzuconym obrazie zabepieczonego pliku:

Znalezienie OEP jest relatywnie proste, wersja dla leniwych — zrzucić działający program z pamięci i wyszukać standardowego entrypoint-a dla aplikacji skompilowanych pod Visual C++ (szukać funkcji GetStartupInfoA). Potem wgrać na nowo plik i ustawić hardware breakpoint na tym adresie.

Będąc na adresie OEP można teraz wstrzyknąć kod odpowiedzialny za czytanie pliku. Aby zaoszczedzić trochę kodu, ja wykorzystałem fakt, że plik aplikacji jest przez nią otwierany w pewnym momencie, dzięki czemu nie trzeba pobierać jego ścieżki i go otwierać, a od razu mamy jego uchwyt, który można wykorzystać do odczytania overlaya:

Następnie skorzystałem ze skryptu ODBScript, żeby pod adresem 4015EE załadować kod, który odczyta zawartość pliku i zapisze go na dysku:

; usun wszystkie breakpointy
        bphwcall
        bc

; ustaw hardware bp na OEP
        bphws   401873,"x"

        run

; ustaw hardware bp na kod po _lopen
        bphws   4015F4,"x"

        run

        bphwcall

; kompiluj kod z pliku pod adres EIP
        asmtxt  eip, "C:\fox_dumper.asm"

; pomocniczy bp
        bphws   401644,"x"

        run

; zrzuc pamiec pod adresem ESI o rozmiarze EBX
        dm esi, ebx, "c:\dump.bin"

        log "VFP dumper zrobione."

        pause

Kod dumpera ładowany jest z zewnętrznego pliku, a nie jest wykorzystywana funkcja ODBScript wykonująca kod assemblera w przestrzeni adresowej aplikacji, ponieważ to po prostu nie działało (jakby ktoś pytał):

; w EAX jest uchwyt pliku
        mov     edi,eax                 ; hFile

; nawiguj na koniec pliku (korzystajac z funkcji
; z tabeli importow aplikacji)
        push    2                       ; FILE_END
        push    0                       ; offset
        push    edi                     ; hFile
        call    [40203C] ;_llseek
        mov     ebx,eax                 ; eax = current offset = size

; alokuj pamiec na rozmiar overlaya
        lea     eax,[ebx-4]             ; na koncu overala zapisany jest DWORD
                                        ; ktory wskazuje offset poczatku overlaya
                                        ; w pliku
        push    0                       ; FILE_BEGIN
        push    eax                     ; offset
        push    edi                     ; hFile
        call    [40203C] ;_llseek

; odczytaj DWORD, ktory okresja polozenie overalaya w pliku
        push    0
        mov     eax,esp

        push    4                       ; size
        push    eax                     ; &memory
        push    edi                     ; hFile
        call    [402040] ;_lread

        pop     eax                     ; esi - raw offset overlaya

        push    0                       ; FILE_BEGIN
        push    0                       ; offset
        push    edi                     ; hFile
        call    [40203C] ;_llseek

; alokuj pamiec na calego overlaya
        push    ebx
        push    40                      ; GMEM_FIXED or GMEM_ZEROINIT
        call    GlobalAlloc
        mov     esi,eax

; odczytaj calego overlaya do nowo zaalokowanej pamieci
        push    ebx                     ; size
        push    esi                     ; &memory
        push    edi                     ; hFile
        call    [402040] ;_lread

; zamknij oryginalny plik
        push    edi
        call    [402038] ;_lclose

; utworz nowy plik
        sub     esp,512
        mov     eax,esp

; "C:\x"
        mov     edx,785C3A43
        mov     [eax],edx
        xor     ecx,ecx
        mov     [eax+4],ecx

; esi - ptr
; ebx - size

        push    0
        call    ExitProcess

Po wykonaniu skryptu, w pliku C:\x otrzymamy zrzut całego pliku (nie tylko overlaya).

Odbudowa oryginalnego pliku wykonywalnego

Posiadając zrzucony overlay (rozpoznamy jego bajty po sygnaturze FE F2 EE), należy go dokleić do odbudowanego loadera VFP. Sam loader po zabezpieczeniu i zrzuceniu wymaga odbudowy importów,  tutaj można trochę oszukać, wszystkie loadery VFP (danej wersji) są takie same, różnią się jedynie wersją językową, ikoną i zasobami z nazwą i numerem wersji aplikacji. Jeśli posiadacie środowisko Visual FoxPro 9, można stworzyć projekt w stylu Hello World, skompilować do pliku wykonywalnego, usunąć oryginalny overlay i wkleić nasz, a aplikacja będzie działała poprawnie (można potem w edytorze zasobów podmienić ikonę i wersję aplikacji).

Wnioski

Było trochę problemów z samą zabezpieczoną aplikacją i jej działaniem w OllyDbg z włączonym pluginem Phant0m (dużo restartów, nie dało się normalnie zamknąć aplikacji), jednak sama metoda jest dosyć szybka i uniwersalna, z późniejszych poszukiwań udało mi się jeszcze znaleźć doskonały artykuł prezentujący szereg innych zabezpieczeń stosowanych w aplikacjach VFP, polecam na koniec:

Security in FoxPro — www.foxpert.com/docs/security.en.htm

PS. Pozdro dla wszystkich burtoniarzy 🙂

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.