Autoři malware mají snahu ukrývat své výtvory nejen před zraky uživatelů, ale taktéž i před ‚zraky‘ různých analyzérů a detektorů. Důvod je zřejmý: Udržet svůj kód co nejdéle neviditelný znamená jeho vyšší životnost. K tomuto účelu autoři nejen oprašují a renovují již dříve známé techniky, ale vyvíjejí i nové. Jednou ze starších technik je skrývání modulů v běžícím procesu s využitím struktury PEB.

Cílem článku není popsat tuto a následující struktury. Proto zde budou zmíněny jen detaily nutné k pochopení problematiky. Pro získání znalostí hlubšího charakteru je dobré dohledat si materiály zaměřující se na konkrétní pasáže.

Základní znalosti
PEB[1], neboli Process Environment Block, je datová struktura v prostředí procesů na platformě Windows, a jak již název napovídá, popisuje prostředí běžícího procesu. Vzhledem k tomu, že se jedná o vnitřní strukturu operačního systému Windows, oficiálně není tato struktura úplně nejlépe popsána a zdokumentována. Naštěstí dokumentace neoficiálního charakteru je poměrně bohatá a více než dostačující.
Jak tedy získat umístění struktury PEB v běžícím procesu? Zájemci o hlubší vědomosti mohou využít strukturu TIB/TEB[2] – Thread Information/Environment Block. Na začátek struktury TIB ukazuje segmentový registr fs. Skutečnou adresu struktury TIB je možné zjistit buď z GDT[3] – Global Descriptor Table – nebo LDT[4] – Local Descriptor Table (to jsou ta náročnější řešení a vyhneme se jim), případně, úplně jednoduše, ze samotné struktury TIB. Nahlédnutím do neoficiální dokumentace zjistíme, že 24 bajtů (0x18) od začátku struktury TIB je pole obsahující lineární adresu samotné struktury TIB:

mov eax, fs:[0x18]    ; v registru eax je adresa TIBu

Pokud by někoho napadlo zkusit získat adresu struktury TIB pomocí instrukce lea:

lea eax, fs:[0]

můžu ho ubezpečit, že se mu to nepodaří. Důvodem je skutečnost, že instrukce lea neumí pracovat se segmentovymi registry, takže výše zmíněný kód bude zpracován na:

lea eax, 0

Nás ovšem zajímá především adresa struktury PEB, která je 48 bajtů (0x30) od počátku struktury TIB:

mov eax, fs:[0x30]    ; v registru eax je adresa PEBu

V této struktuře pro nás může být zajímavá hodnota typu BOOL vzdálená 3 bajty od počátku struktury PEB – BeingDebugget – jenž udává, zda je v rámci daného procesu aktivní debugger (tato hodnota je velice často používaná jako jedna z jednoduchých a snadno odhalitelných anti-debug metod jak v malware, tak v různých anti-crackerských protektorech). Nás ale hlavně bude zajímat adresa struktury PEB_LDR_DATA[5], která je 12 bajtů (0x0C) za počátkem struktury PEB.

mov eax, fs:[0x30]
mov eax, [eax + 0x0C]

nebo přehledněji

mov eax, fs:[0x30]
add eax, 0x0C
mov eax, [eax]

Struktura PEB_LDR_DATA popisuje všechny moduly nahrané do aktuálního procesu. Těmito moduly jsou myšleny nejen samotné DLL knihovny, ale i samotná binárka, v jejímž kontextu jsou tyto DLL knihovny do paměti načteny. Tato struktura obsahuje kromě jiného tři adresy na struktury LIST_ENTRY[6]. Tyto tři adresy popisují moduly v pořadí:
– v jakém byly nalodovány pomocí loaderu – InLoadOrderModuleList
– v jakém byly inicializovány – InInitializationOrderModuleList
– tak, jak jsou seřazeny v paměti – InMemoryOrderModuleList

Struktura LIST_ENTRY je obousměrný seznam a měla by tvořit nekonečnou smyčku, neboli poslední položka by měla ukazovat opět na tu první – tzv. hlavu obousměrného seznamu. V případě, že seznam nebude obsahovat žádný záznam, bude ukazovat struktura sama na sebe. Struktura LIST_ENTRY obsahuje pouze dvě položky – flink (forward link) a blink (backward link). Tyto odkazují na strukturu LDR_DATA. Tato struktura už obsahuje takové informace jako jsou Base Address modulu, velikost modulu, jméno modulu, absolutní cestku k modulu a další. Právě položky FullDllName a BaseDllName jsou struktury UNICODE_STRING[7]. Struktura UNICODE_STRING obsahuje informace o řetězci, který je, jak název napovídá, uložen jako UNICODE string, tedy každý znak má více než jen 8 bitů (1 bajt).

Teoretická realizace ukrývání
Protože výše popsané struktury jsou povětšinou neoficiálně dokumentovány a společnost Microsoft neposkytuje jejich kompletní popis, je nutné si vytvořit vlastní struktury. Jako první krok potřebujeme získat adresu struktury PEB_LDR_DATA ze struktury PEB. K tomu si napíšeme jednoduchou funkci:

PPEB_LDR_DATA GetPebLdrDataFromPeb(){
	PPEB_LDR_DATA retrn;
	__asm{
		push eax
		mov eax, fs:[0x30]
		mov eax, [eax + 0x0C]
		mov retrn, eax
		pop eax
	}
 
	return retrn;
}

Ze struktury PEB_LDR_DATA si vybereme některou z položek InLoadOrderModuleList, InMemoryOrderModuleList nebo InInitializationOrderModuleList. Tyto adresy jsou výše zmiňované hlavy obousměrného seznamu typu LIST_ENTRY. Adresu hlavy si uložíme a použijeme ji pro nalezení konce obousměrného seznamu. Tento seznam pak procházíme tak dlouho, dokud nebude opět ukazovat na hlavu, případně nenalezneme to, co hledáme. Tedy naši DLL knihovnu. Pokud dojde k nalezení DLL knihovny v obousměrném seznamu, je třeba tento záznam vymazat. Pokud si uvědomíme, jak obousměrný seznam funguje, není to problém. V obousměrném seznamu vždy aktuální člen ukazuje na předešlý a následující člen seznamu. Pokud tedy chceme aktuální prvek ze seznamu odstranit, musíme řetězec rozpojit a přepojit tak, aby předchozí člen ukazoval až na následující člen a tím eliminoval přítomnost aktuálního členu.

Před rozpojením a přepojením
<-----> x <-----> y <-----> z <----->
Po rozpojení a přepojení
<-----> x         y         z <----->
        ^-------------------^

Z pohledu kódu by mohlo řešení například pro InLoadOrderModuleList vypadat třeba následovně:

pInLoadModuleList->Blink->Flink = pInLoadModuleList->Flink;
pInLoadModuleList->Flink->Blink = pInLoadModuleList->Blink;

Řešení pro zbylé dva seznamy je zcela analogické. Tímto se DLL knihovna stává neviditelnou vůči všem aplikacím, které používají pro získávání informací o procesu funkce využívající data ze struktury PEB. Jednou z nich je například Windows API funkce CreateToolhelp32Snapshot[8] z DLL knihovny kernel32.dll. Po menších úpravách je tento kód využitelný i pro skrývání driverů v kernel space – ring 0[9].

Doprovodné obrázky:
PEB_before
Před spuštěním kódu v procesu

PEB_after
Po spuštění kódu v procesu

Ukázkový kód

#include <windows.h>
#include <stdio.h>
#include <tchar.h>
 
typedef struct{
  USHORT Length;
  USHORT MaximumLength;
  PWSTR Buffer; 
}UNICODE_STRING, *PUNICODE_STRING;
 
typedef struct{
  ULONG Length;
  UCHAR Initialized;
  PVOID SsHandle;
  LIST_ENTRY InLoadOrderModuleList;
  LIST_ENTRY InMemoryOrderModuleList;
  LIST_ENTRY InInitializationOrderModuleList;
  PVOID EntryInProgress;
}PEB_LDR_DATA, *PPEB_LDR_DATA;
 
typedef struct{
  LIST_ENTRY InLoadOrderModuleList;
  LIST_ENTRY InMemoryOrderModuleList;
  LIST_ENTRY InInitializationOrderModuleList;
  PVOID BaseAddress;
  PVOID EntryPoint;
  ULONG SizeOfImage;
  UNICODE_STRING FullDllName;
  UNICODE_STRING BaseDllName;
  ULONG Flags;
  SHORT LoadCount;
  SHORT TlsIndex;
  LIST_ENTRY HashTableEntry;
  ULONG TimeDateStamp;
}LDR_DATA, *PLDR_DATA;
 
PPEB_LDR_DATA GetLdrDataFromPeb();
void HideDll(HINSTANCE hModule);
 
BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpReserved){
  HideDll(hinstDLL);
  /* Insert your code here */
  /* Example: */
  while(1){
    MessageBoxA(NULL, "I'm still here bro!", "Warning!", MB_ICONWARNING);
    Sleep(10000);
  }
  /* Example: */
 
  return TRUE;
}
 
PPEB_LDR_DATA GetLdrDataFromPeb(){
  PPEB_LDR_DATA retrn;
  __asm{
    mov eax, fs:[0x30]
    add eax, 0x0C
    mov eax, [eax]
    mov retrn, eax
  }
  return retrn;
}
 
void HideDll(HINSTANCE hModule){
  PPEB_LDR_DATA pPebLdrData = NULL;
  PLIST_ENTRY pListEntryHead = NULL;
  PLDR_DATA pLdrData = NULL;
 
  pPebLdrData = GetLdrDataFromPeb();
 
  pListEntryHead = (PLIST_ENTRY)&(pPebLdrData->InLoadOrderModuleList);
  pLdrData = (PLDR_DATA)(pPebLdrData->InLoadOrderModuleList.Flink);
 
  while(pLdrData->InLoadOrderModuleList.Flink != pListEntryHead){
    if(pLdrData->BaseAddress == (LPVOID)hModule){
      if(pLdrData->InLoadOrderModuleList.Flink != NULL){
        pLdrData->InLoadOrderModuleList.Flink->Blink = pLdrData->InLoadOrderModuleList.Blink;
        pLdrData->InLoadOrderModuleList.Blink->Flink = pLdrData->InLoadOrderModuleList.Flink;
      }
 
      if(pLdrData->InMemoryOrderModuleList.Flink != NULL){
        pLdrData->InMemoryOrderModuleList.Flink->Blink = pLdrData->InMemoryOrderModuleList.Blink;
        pLdrData->InMemoryOrderModuleList.Blink->Flink = pLdrData->InMemoryOrderModuleList.Flink;
      }
 
      if(pLdrData->InInitializationOrderModuleList.Flink != NULL){
        pLdrData->InInitializationOrderModuleList.Flink->Blink = pLdrData->InInitializationOrderModuleList.Blink;
        pLdrData->InInitializationOrderModuleList.Blink->Flink = pLdrData->InInitializationOrderModuleList.Flink;
      }
    }
    pLdrData = (PLDR_DATA)pLdrData->InLoadOrderModuleList.Flink;
  }
}

Odkazy:
[1] – PEB
[2] – TEB/TIB
[3] – Global Descriptor Table
[4] – Local Descriptor Table
[5] – PEB_LDR_DATA
[6] – LIST_ENTRY
[7] – UNICODE_STRING
[8] – CreateToolhelp32Snapshot
[9] – Ring