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