Entendiendo el PEB

Entenent el PEB

Jacobo


Aquest serà el primer d'una sèrie d'articles on es tractarà de comprendre la teoria bàsica necessària per endinsar-se en el món del malware en entorns Windows. 

És difícil posar un punt de partida per endinsar-nos en aquest àmbit de la ciberseguretat. Un punt que permeti comprendre aspectes complexos i no quotidians i alhora sigui assequible la seva comprensió, que es pugui dir que es parteix d'un nivell 0, bàsic, però que no expliqui massa coses bàsiques que tot informàtic hauria de saber. 

Així, s'ha decidit començar pel PEB, Què és? Per què és important en el desenvolupament de malware?

Una mica de teoria:

És interessant comprendre què hi ha darrere dels arxius executables de Windows, ja sigui un arxiu amb extensió EXE comú per a tots nosaltres o extensions (potser més desconegudes) com DLL o SYS. 

Per començar, cal saber que tots són el mateix tipus d'arxiu, el que Windows anomena Portable Executable Format. Entendre l'estructura d'aquests arxius és l'objecte dels següents articles. Tanmateix, com a mínim cal saber que, quan s'executa un d'aquests arxius, Windows crea un procés, amb almenys un fil, que seran els encarregats d'executar el codi de l'arxiu.

Aquest procés creat, es podria dir que és l'encarregat de donar totes les eines necessàries a l'executable perquè aquest es llanci correctament. 

Un exemple: quan s'escriu un codi en C que imprimeix per pantalla un “Hola món” mitjançant el típic printf, passen coses per sota que els usuaris comuns, fins i tot desenvolupadors, podem arribar a desconèixer. 

Printf és una funció definida i declarada en una llibreria anomenada ucrtbase.dll. Tanmateix, aquesta és una funció a molt alt nivell i sabem que Windows té un kernel que no coneix aquestes funcions. ¿Com s'aconsegueix llavors que el kernel obtingui una informació que pugui digerir? Mitjançant les APIs natives de Windows o WinAPIs. 

En resum, quan creem un programa amb un printf i l'executem, sabem que es crearan una sèrie de crides des de molt alt nivell com printf fins a molt baix nivell com podria ser ZwWriteFile. Entendre en profunditat les crides a funcions de WinAPIs, serà objecte d'un altre article.

De nou la pregunta torna a ser ¿Com un procés proveeix les eines necessàries a l'arxiu executable per a llançar-se correctament? La resposta és senzilla, tenint un control sobre tota la informació que un executable necessita per a llançar-se. És a dir, com hem vist, el nostre programa necessita com a mínim la DLL anomenada ucrtbase.dll. ¿Com obté l'executable (i carrega a la seva memòria) l'adreça de la DLL per usar les seves funcions com printf? Mitjançant el PEB. 

El Process Enviroment Block, és una estructura de dades de Windows que conté informació per a l'arrencada de processos, i per tant executables, com el seu propi PID, si el procés està sent debuggejat i, sobretot, punters a altres estructures com LDR_DATA que acabaran contenint la informació sobre les DLL necessàries per a l'executable com la seva adreça.

Aquesta estructura està documentada parcialment per Windows. Només ens donen informació clara sobre 7 de les seves variables de les 19 que té. 

 


Sabem que totes les variables de tipus byte ocuparan un byte d'espai a la memòria i les variables PVOID seran punters a alguna cosa i per tant ocuparan 8 bytes de memòria. És necessari saber com a mínim la seva mida per poder calcular bé les adreces de memòria i els desplaçaments quan estiguem depurant el codi o intentant accedir-hi en els nostres codis.

Menys mal que tenim la comunitat que investiga aquests temes i ens ofereix dades no oficials, però sí més precises que la pròpia documentació de Microsoft. Un exemple és la pàgina https://www.vergiliusproject.com/ on podrem consultar la gran majoria d'estructures de Windows. Recomanem visitar l'estructura PEB d'aquesta web https://www.vergiliusproject.com/kernels/x64/windows-11/24h2/_PEB. En alguna cosa s'assemblen i serveix per fer-se una idea real de les seves variables. 

Si atenem a la documentació oficial de Windows, el que més interessa (per ara) del PEB és la variable definida com Ldr. Aquesta variable serà novament una estructura de Windows del tipus _PEB_LDR_DATA i com el PEB, està documentada parcialment per Microsoft. 

Segons la documentació oficial aquesta és la seva forma.

 


El nostre objectiu és entendre el PEB, com és l'estructura de control dels processos i com permet als executables (PE) que facin servir totes les DLL que necessitin, entre altres coses. 

PEB_LDR_DATA té una variable de tipus LIST_ENTRY anomenada InMemoryOrderModuleList formada per dos punters. Aquests punters indicaran l'adreça de memòria de totes les DLL que necessiti l'executable, tant la DLL següent com la DLL anterior, creant una llista doblement enllaçada de DLL a carregar. 

 


Aquests punters, segons la documentació de Microsoft, es diuen Flink i Blink. 

En aquest punt és interessant reflexionar sobre com aquesta llista doblement enllaçada dóna tota la informació a un executable sobre quina DLL carregar. Si pensem una mica, com a mínim serà necessari saber el nom de la DLL i la ruta absoluta d'on està emmagatzemada al disc per poder-la carregar a memòria virtual. 

Tenim una llista doblement enllaçada, formada per struct _LIST_ENTRY on flink apunta a l'adreça del següent _LIST_ENTRY del qual s'obtindrà el següent flink i així successivament. On és la informació de cada DLL necessària?

Doncs això és molt senzill. Cada _LIST_ENTRY pertany a una estructura més gran anomenada _LDR_DATA_TABLE_ENTRY. Novament és una estructura de Windows documentada parcialment per Microsoft.

 


Com es pot apreciar, a la segona línia conté l'estructura LIST_ENTRY (compte que no és un punter a l'estructura sinó que els 16 bytes dels dos punters hi són dins). També es pot observar les variables DllBase, EntryPoint o FullDllName que conté, ja sí, tota la informació necessària per anar carregant totes les DLL que l'executable necessiti. 

En resum, el PEB és una estructura de Windows que conté tota la informació necessària del procés. Gràcies a ella, un executable pot carregar totes les DLL necessàries, perquè les funcions del programa i les necessàries per al kernel, es carreguin. Com s'ha après, tot això mitjançant una sèrie d'estructures que mantenen en ordre tota la informació. 

 



Manos a la obra:

Per analitzar i observar el PEB i tota la seva estructura i les seves estructures es farà ús de WinDBG, el depurador oficial de Windows. A més, es crearà un petit programa en C amb ajuda de VisualStudio que corrobori, totes les dades i serveixi per reforçar els coneixements i comprendre, que des d'un executable es pot accedir a ells. 

En primer lloc, començarem fent servir WinDBG. Aquest depurador ens permetrà tant llançar un executable des del disc i analitzar el seu procés, com adherir-nos a un procés d'un executable que estigui corrent.

 


En aquest primer cas, llançarem el nostre programa amb el printf de “Hola món” des de zero.

 


Un cop llançat, farem servir la primera comanda que serà !peb (WinDBG és case sensitive). Amb això, el depurador ens mostrarà tota la informació sobre aquesta estructura i subestructures.

 


Gràcies a la comanda !peb, ja es poden observar totes aquestes estructures i adreces de memòria. En primer lloc, la primera fletxa apunta a l'adreça de memòria del PEB. Encara no s'ha dit, però òbviament aquesta adreça és una adreça en la memòria virtual i el PEB és únic per a cada procés. 

Un procés pot tenir diversos fils o threads. Sabent això i que cada fil tindrà la seva pròpia estructura TEB (Thread Environment Block), cada TEB dels fils d'un mateix procés tindrà el seu propi PEB?

La següent fletxa ens mostra l'adreça de la variable Ldr, que tindrà el punter a la primera estructura LIST ENTRY. 

Finalment, es pot observar com des del PEB s'han obtingut totes les DLL carregades, entre elles ucrtbased.dll, encarregada del printf.

WinDBG ens permet, a més, fer un volcat de memòria i veure el contingut d'aquestes, així que anirem veient la memòria i com emmagatzema aquestes estructures. 

En primer lloc, es començarà accedint a l'estructura PEB (0x00000042393fc000)

 




Després d'un desplaçament de 0x18, es pot arribar a la variable Ldr, que és un punter a l'estructura. Seguirem tant aquesta adreça com les següents fins a arribar al primer mòdul carregat.

 


Aquest és el resultat. L'única pista que donaré serà que amb la comanda du hem fet el volcat de la memòria de la variable FullDllName del primer mòdul carregat. La resta dels quadres, separats per colors, heu de tractar d'identificar-los 😉.



Pròxims passos:

Per què és necessària tota aquesta estructura i saber manejar-se amb el depurador com a desenvolupadors de malware?

Les mesures de seguretat com EDRs o el propi Windows Defender, intenten cada dia evolucionar, detectar coses a nivells més baixos, fins i tot com cadenes repetides d'opcodes. Això fa que hàgim d'entendre el màxim possible del funcionament a baix nivell del que desenvolupem. 

En particular, comprendre l'existència del PEB, quines dades té i com puc accedir-hi ens obre el camí per implementar mesures anti hooking, implementar mesures anti debugging o per realitzar tècniques més avançades com Reflective Loader Injection on serà necessari carregar manualment totes les dependències de l'executable. 

S'ha creat un exe que obté algunes de les dades vistes anteriorment.

 


Es deixa a gust del lector agafar l'arxiu .c i modificar-lo per intentar obtenir informació sobre la resta de les variables de l'estructura PEB.







Tornar al bloc

Deixa un comentari

Tingueu en compte que els comentaris s'han d'aprovar abans que es publiquin.