# E.Remplacer GetModuleHandle par une fonction custom

{% embed url="<https://metehan-bulut.medium.com/understanding-the-process-environment-block-peb-for-malware-analysis-26315453793f>" %}

{% embed url="<https://blog.deeb.ch/posts/how-edr-works/>" %}

{% embed url="<https://mohamed-fakroud.gitbook.io/red-teamings-dojo/windows-internals/peb>" %}

{% embed url="<https://vvinoth.com/post/remote-process-peb/>" %}

### Théorie :&#x20;

On va tenter de comprendre comment getmodulehand fonctionne afin de la coder from scracth et de manière custom :

Le PEB walking est la technique parfait pour obtenir l'adresse des dll qu'ont souhaite car dans cette "interface" il existe un attribut du nom de Ldr qui permet de récupérer des informations sur les DLLs chargé par le système.&#x20;

#### 1. L'accès matériel : Le registre GS et le TEB

Tout commence au niveau du processeur. Sous Windows x64, chaque thread possède une structure appelée TEB (Thread Environment Block).

* Le processeur utilise le registre de segment GS pour pointer en permanence sur ce bloc.
* À l'intérieur de ce TEB, à l'offset 0x60, se trouve l'adresse du PEB.

C'est la seule porte d'entrée "statique" : peu importe où ton code est chargé, le registre GS te donnera toujours le point de départ vers les structures internes de Windows.

***

#### 2. Le Maillage : Les listes chaînées circulaires

Une fois dans le PEB, on accède au LDR (Loader Data). Le défi ici est que Windows ne stocke pas les DLL dans un simple tableau, mais dans une liste doublement chaînée circulaire.

Théorie du chaînage :

* Chaque élément (maillon) n'est pas la DLL elle-même, mais une structure de contrôle (`LDR_DATA_TABLE_ENTRY`).
* Ces maillons sont reliés par des connecteurs `LIST_ENTRY`.
* Le concept de circularité : Le dernier maillon pointe vers le premier. Pour un programmeur, cela signifie qu'il n'y a pas de fin "NULL". La fin, c'est quand on revient à l'adresse de départ. C'est pour cela qu'on définit une "ancre" (`pListHead`) dans ton code.

***

#### 3. Le concept de "Structure Opaque" et Reverse Engineering

C'est ici que ta démarche devient technique. Microsoft publie des fichiers d'en-tête (headers) officiels, mais ils y masquent la réalité.

* Documentation Officielle : Elle affiche des champs nommés `Reserved1`, `Reserved2`, etc. C'est une structure opaque.
* Réalité Système : Ces champs `Reserved` contiennent en fait les données les plus sensibles, comme l'adresse de base de la DLL (`DllBase`) ou le nom complet sur le disque.

Pourquoi est-ce faux dans les docs ? Pour empêcher les logiciels de dépendre de ces emplacements, car Microsoft veut pouvoir les changer sans casser les logiciels "officiels". Coder un loader custom demande donc d'utiliser des structures issues du Reverse Engineering, où la communauté a identifié que la donnée que tu cherches est stockée précisément à l'emplacement de `Reserved2[0]`.

***

#### 4. La gestion des chaînes en mémoire (Unicode)

Théoriquement, Windows NT (le noyau) travaille exclusivement en Unicode (UTF-16).

* Contrairement au C standard (ASCII) où "A" = 1 octet (`0x41`), en Unicode, "A" = 2 octets (`0x41 0x00`).
* Cela signifie que pour comparer le nom d'une DLL dans le PEB, tu ne peux pas utiliser des fonctions de comparaison classiques. Tu dois utiliser des fonctions capables de sauter d'octet en octet de deux en deux (comme `_wcsicmp`).

***

#### Résumé de la chaîne de dépendance théorique :

1. Matériel : Registre `GS` $$→$$ Adresse du TEB.
2. Structure : TEB + 0x60 $$→$$ Adresse du PEB.
3. Loader : PEB $$→$$ Pointeur LDR $$→$$ Tête de liste des modules.
4. Navigation : Suivre les `Flink` jusqu'à trouver le bon nom en Unicode.
5. Extraction : Lire la valeur dans le champ opaque identifié par le reverse engineering (`Reserved2`).

### Code Simple :

```c
/*
On doit récupérer l'adresse de PEB et le PEB se situe dans GS 0x60 : https://en.wikipedia.org/wiki/Win32_Thread_Information_Block
Pour ce faire on utilise la fonction __readgsqword https://learn.microsoft.com/fr-fr/cpp/intrinsics/readgsbyte-readgsdword-readgsqword-readgsword?view=msvc-170
qui va récupérer le contenue et va nous renvoyer l'adresse de début du peb


  typedef struct _PEB {
    BYTE Reserved1[2];
    BYTE BeingDebugged;
    BYTE Reserved2[1];
    PVOID Reserved3[2];
    PPEB_LDR_DATA Ldr;
    PRTL_USER_PROCESS_PARAMETERS ProcessParameters;
    PVOID Reserved4[3];
    PVOID AtlThunkSListPtr;
    PVOID Reserved5;
    ULONG Reserved6;
    PVOID Reserved7;
    ULONG Reserved8;
    ULONG AtlThunkSListPtr32;
    PVOID Reserved9[45];
    BYTE Reserved10[96];
    PPS_POST_PROCESS_INIT_ROUTINE PostProcessInitRoutine;
    BYTE Reserved11[128];
    PVOID Reserved12[1];
    ULONG SessionId;
  } PEB,*PPEB;

*/


PEB *peb = (PEB *)__readgsqword(0x60);
printf("[+] Adresse de debut de PEB : %p \n", peb);

/* Ensuite on doit accéder à la variable LDR de la structure PEB 
   LDR =  Image Loader en gros contient la liste des dll chargé donc on doit y accéder pour trouver ntdll 

*/

PPEB_LDR_DATA ldr = peb->Ldr;
printf("[+] Adresse de Ldr : %p \n", ldr);


/* On a desormais une référence vers Ldr qui est une structure encore et qui a plusieurs champs dont un 
   important qui est InLoadOrderModuleList (liste des dll chargé) qui est de nouveau une structure
   mais en réalité c'est une liste chainé donc quand on va y accéder on va acceder au premiere elemente de la liste chainé qui
   est de type _LDR_DATA_TABLE_ENTRY et qui contient dans sa structure le nom de la dll
   https://learn.microsoft.com/fr-fr/windows/win32/api/winternl/ns-winternl-peb_ldr_data
   https://github.com/conix-security/zer0m0n/blob/master/src/driver/include/nt/structures/LDR_DATA_TABLE_ENTRY.h
*/

PLIST_ENTRY first = ldr->InMemoryOrderModuleList.Flink; // ici je récupere  le premier element de la liste chainé F= forward

LDR_DATA_TABLE_ENTRY *first_module = (LDR_DATA_TABLE_ENTRY *)first; 
//LDR_DATA_TABLE_ENTRY *first_module = (LDR_DATA_TABLE_ENTRY *)((PBYTE)first - 0x10);

printf("Nom de la Dll : %S \n",first_module->FullDllName.Buffer);

PLIST_ENTRY seconde = first->Flink;


LDR_DATA_TABLE_ENTRY *second_module = (LDR_DATA_TABLE_ENTRY *)(seconde);
//LDR_DATA_TABLE_ENTRY *second_module = (LDR_DATA_TABLE_ENTRY *)((PBYTE)seconde - 0x10);
printf("Nom de la Dll : %S  et adresse %p et %p \n",second_module->FullDllName.Buffer,second_module->Reserved2[0]);

/* Ici l'adresse est stocké dans reserved[0] eliot me l'a dit car cette structure n'est pas documenté officiellement mais 
   sur internet il est indiqué

   typedef struct _LDR_DATA_TABLE_ENTRY
{
     LIST_ENTRY InLoadOrderLinks;
     LIST_ENTRY InMemoryOrderLinks;
     LIST_ENTRY InInitializationOrderLinks;
     PVOID DllBase;
     PVOID EntryPoint;
     ULONG SizeOfImage;
     UNICODE_STRING FullDllName;
     UNICODE_STRING BaseDllName;
     ULONG Flags;
     WORD LoadCount;
     WORD TlsIndex;
     union
     {
          LIST_ENTRY HashLinks;
          struct
          {
               PVOID SectionPointer;
               ULONG CheckSum;
          };
     };
     union
     {
          ULONG TimeDateStamp;
          PVOID LoadedImports;
     };
     _ACTIVATION_CONTEXT * EntryPointActivationContext;
     PVOID PatchInformation;
     LIST_ENTRY ForwarderLinks;
     LIST_ENTRY ServiceTagLinks;
     LIST_ENTRY StaticLinks;
} LDR_DATA_TABLE_ENTRY, *PLDR_DATA_TABLE_ENTRY;

```

### Code fonction custom :&#x20;

```c
HMODULE GetModuleHandleCustom(LPCWSTR szModuleName) {
    // 1. Accès au PEB (64 bits)
    PPEB peb = (PPEB)__readgsqword(0x60);

    // 2. Accès au LDR
    PPEB_LDR_DATA ldr = peb->Ldr;

    // 3. Point d'entrée dans la liste "In Memory"
    PLIST_ENTRY pListHead = &ldr->InMemoryOrderModuleList;
    PLIST_ENTRY pCurrent = pListHead->Flink;

    // 4. Parcours de la liste
    while (pCurrent != pListHead) {
        
        LDR_DATA_TABLE_ENTRY *pEntry = (LDR_DATA_TABLE_ENTRY*)pCurrent;


        if (pEntry->FullDllName.Buffer != NULL) {
            // Comparaison du nom de la DLL
            if (_wcsicmp(pEntry->FullDllName.Buffer, szModuleName) == 0) {
                
                // Selon les tests : l'adresse est dans Reserved2[0] à cause du décalage
                return (HMODULE)pEntry->Reserved2[0]; 
            }
        }

        // Passage au maillon suivant
        pCurrent = pCurrent->Flink;
    }

    return NULL;
}

GetModuleHandleCustom(L"ntdll.dll")
```
