sobota, 10 września 2016

Obfuskacja funkcji API czyli jak utrudnić analizę złośliwego oprogramowania – część 2

W poprzednim artykule opisaliśmy jedną z najprostszych metod wywoływania funkcji API, które mogą nie być zauważone przez część z narzędzi do analizy malware. Wspomniałem też o tym, że możliwe jest wywołanie zewnętrznych funkcji API bez wykorzystywania Import Address Table (IAT). Dzisiaj zajmiemy się opisaniem tej techniki.

Początkujący twórcy malware często korzystają bezpośrednio z funkcji LoadLibraryA() oraz GetProcAddress() w celu załadowania biblioteki i odczytania adresu wymaganej funkcji API. Osoba analizująca podejrzany plik bez uruchamiania go a przeglądając jedynie IAT może stwierdzić, że ładowane będą dodatkowe biblioteki w celu wykorzystania zewnętrznych funkcji API. Mimo często spotykanego kodowania/szyfrowania nazw funkcji API w malware taka analiza (często z pomocą analizy dynamicznej) jest stosunkowo prosta.

Czasami jednak analizując malware można zauważyć, że adresy zewnętrznych funkcji API identyfikowane są za pomocą struktury PEB (Process Environment Block). Zawiera ona wskaźnik do kolejnej struktury PEB_LDR_DATA, która ma informacje o wszystkich załadowanych bibliotekach w ramach procesu. Jeden z elementów tej struktury to doubly-linked list (jeden z podstawowych mechanizmów „grupowania” obiektów tego samego typu w systemach operacyjnych) wskazujący na strukturę LDR_DATA_TABLE_ENTRY. Tutaj znajdziemy nazwę biblioteki ale też adres w pamięci gdzie biblioteka jest załadowana w ramach procesu (zapisujemy w rejestrze ECX).

Kod w asemblerze będzie następujący:

 mov ecx, dword ptr fs : [30h] //&PEB
 mov ecx, dword ptr[ecx + 0Ch] //PEB->LDR_DATA
 mov ecx, dword ptr[ecx + 1Ch] //PEB->LDR_DATA.ORDER
next_dll:
 mov ecx, dword ptr[ecx]             //następna biblioteka
 mov eax, dword ptr[ecx+20h]         //nazwa biblioteki
 cmp dword ptr[eax + 08h], 320033h   //szukamy user32.dll
 jne next_dll
 cmp dword ptr[eax], 00730055h       //dwa pierwsze znaki z  user32.dll
 jne next_dll
 mov ecx, dword ptr[ecx+8] //adres biblioteki (tu będzie nagłówek MZ)


W powyższym przykładzie mamy dwie instrukcje cmp, które sprawdzają wybrane znaki z nazwy biblioteki user32.dll.

Zamiast tworzyć warunki dotyczące ciągów znaków (ASCII czy UNICODE) można zastosować metodę polegającą na wyliczaniu wartości hash z nazw bibliotek czy nazw funkcji API.

Metoda ta jest też wykorzystywana przy tworzeniu shellcode-ów.

Jest kilka metod tworzenia skrótów. My zastosujemy instrukcje ror i add do utworzenia skrótu mieszczącego się w czterech bajtach.

W języku Python skrót możemy wyliczyć w następujący sposób:

#=======================================================================#
def ror( dword, bits ):
  return ( dword >> bits | dword << ( 32 - bits ) ) & 0xFFFFFFFF
#=======================================================================#
def hash(function, bits=13):
  hash = 0
  for c in str( function ):
    hash  = ror( hash, bits )
    hash += ord( c )
  print ("%s (0x%08X)" % ( function, hash ))
#=======================================================================#
 
>>> hash("MessageBoxA")
MessageBoxA (0xBC4DA2A8)
>>>


W języku asembler funkcja wyliczająca funkcję skrótu będzie następująca:

compute_hash_init:
 xor edi, edi
 xor eax, eax
 cld

compute_hash:
 lodsb        //zaladuj 1 bajt z ciągu do EAX
 test al, al
 jz compute_hash_finished
 ror edi, 0xd    //przesun o 13 bitów
 add edi, eax    //uaktualnij EDI
 jmp compute_hash
compute_hash_finished:


Zanim jednak będziemy mogli wyliczać funkcje skrótu musimy mając adres biblioteki:
  • Znaleźć adres Export Address Table (EAT)  w załadowanym do pamięci pliku PE
  • Odczytać ilość eksportowanych funkcji API oraz odczytać początkowy adres tablicy z nazwami eksportowanych funkcji API

Poniżej kod realizujące te zadania:

 mov ebp, ecx        // base address of dll
 mov eax, dword ptr [ecx + 3Ch] //offset dos header
 mov edx, dword ptr [ecx + eax + 78h]  //offset - RVA of EAT
 add edx, ecx //do RVA dodajemy base address
 mov ecx, dword ptr [edx + 18h] //liczba eksportowanych funkcji
 mov eax, dword ptr [edx + 20h] //table of API names
 add eax, ebp //adres tablicy z nazwami API
 mov ebx, eax
find_function_loop:
 jecxz find_function_finished
 dec ecx
 mov esi, [ebx + ecx *4]    //odczyt RVA adresu nazwy
 add esi, ebp        //adres nazwy funkcji w ESI. Będzie wczytywany przez lodsb do EAX


Po wyliczeniu skrótu funkcji API musimy ją porównać ze skrótem którego szukamy. Dla funkcji MessageBoxA jest to hash = 0xBC4DA2A8

find_function_compare:
 cmp edi, 0xBC4DA2A8
 jne find_function_loop


Ostatnim elementem jest odczytanie adresu funkcji API i zapisanie jej na stos w miejsce przechowywanej wartości EAX. Po wywołaniu instrukcji popad adres szukanej funkcji API będzie znajdował się w tym rejestrze.

 mov ebx, [edx + 0x24]
 add ebx, ebp
 mov cx, [ebx + 2 * ecx]
 mov ebx, [edx + 0x1c]
 add ebx, ebp;
 mov eax, [ebx + 4 * ecx];
 add eax, ebp
 mov [esp + 0x1c], eax


W poprzednim przykładzie adres funkcji MessageBoxA również umieściliśmy w EAX więc połączenie obu fragmentów kodu nie będzie problemem.
Pełny kod źródłowy opisany powyżej został umieszczony na GitHub https://github.com/Prevenity/API_Obfuscation_examples