F.Remplacer GetProcAddress par une fonction custom

matt pietrek microsoft

Théorie :

Pour pouvoir coder de manière custom la fonction GetProcAdress il faut avoir une bonne connaissance de la structure d'un PE File. Différent lien sont présent sur cette page permettant de décrire un PE file et comment grâce à cette description il est possible de récupérer l'adresse d'une fonction présente dans une DLL :

Dans la capture d'écran précédente on voit qu'on a localisé à l'aide de PE bear NtAllocateVirtualMemory et son RVA dans l'exported Function.

Cette ressource se trouve dans l'optional Header qui se trouve lui même dans le NT Headers :

Il faut donc d'abord trouver un moyen pour accéder à NT Headers :

IMAGE_DOS_HEADER est une structure qui nous permet de récupérer un pointeur vers le PE Header de notre DLL ici ntdll.dll.

Une fois cela fait il faut accéder à NT_Header pour ce faire on aura besoin de la valeur e_lfanew qui se trouve dans la structure précedente et qui est en réalité l'offset pour accéder à NT Header.

Une fois qu'on a récupérer le NT_Header il faut avoir accès à l'optional Header :

Pour y avoir accès il suffit de faire cela :

Après ça il reste à accéder aux exported function qui se situe dans Data Directory :

Donc on souhaite accéder à l'export directory présent dant le tableau Data Directory :

On a une référence vers Export Directory (ce qui est en réalité pas vrai) et désormais on doit récupérer le RVA (Relative Adress) qui est globalement un offset qu'on doit récupérer pour pouvoir par la suite réellement pointer vers l'Export Directory.

Donc on récupère le RVA :

On obtient la même valeur qu'on avait identifié sur PE-Bear :

Désormais on sait que le RVA est en réalité un offset, on va donc faire l'opération suivante pour accéder à l'export directory :

Accès Export directory :

Donc on récupère un pointeur vers l'export Directory et si on compare la structure précédente avec ce qu'on a sur PE Bear on a la même définition :

Désormais ce qui nous intéresse c'est les exported function, qui se trouve 4 octet "plus loin" que AdressOfNameOrdinals.

Finalement quand on regarde le screen précédent nous on veut boucler à partir de 1B8138 et pour accéder à la valeur de cette offset il faut accéder à la valeur AdressOfFunctions (En réalisé on va avoir besoin des valeurs de AdressOfFunctions , Adressofname, Adressofordinal car on ne peut pas boucler directement, l'affirmation que j'ai faite était basé sur une première compréhension et l'idée de bouclé mais en realité chaque colonne qu'on voit sur PE Bear represente un tableau unique):

Il faut désormais récupérer // Le tableau des noms (colonne Name) le tableau des ordinaux (colonne ordinal) et le tableau des adresses (colonne offset) pour pouvoir accéder à chacune de leurs valeurs ce qui nous permettra de récupérer l'adresse de la fonction (en réalité un offset) et le nom :

On déclare un WORD pour le tableau des ordinaux car un ordinal a une taille de 16 bits donc 2 octet donc un WORD :

Par exemple si on veut accéder à la valeur des ordinaux, on fait :

Sauf que la valeur indiqué ne correspond pas à la valeur présente sur PE-BEAR.

Faisons le test pour le tableau name_table :

Donc au final, on récupère en réalité le name RVA, donc encore une Relative Adress donc un offset, alors pour récupérer finalement le nom, on fait :

Ok donc l'idée va être donc de boucler sur tout les noms et de la comparer avec le nom de fonction qu'on veut pour se faire on dispose d'une information importante le nombre de fonction qui nous permet d'avoir notre range dans la boucle (en gros là taille de notre boucle) :

Alors on a le code suivant :

On a réussi à trouver :

On voit bien qu'on obtient les bonnes valeurs, désormais pour avoir l'adresse il reste une dernière opération à réaliser car on a pour le moment obtenu que un RVA donc un offset :

Ainsi on :

Donc en vrai le tableau ordinal ne sert à rien dans mon cas. Ce qui donne au final le code suivante complet :

Bon quand on compare on a pas exactement la bonne valeur, ça c'est à cause du fait que je n'utilise pas le tableau ordinal car je pensais qu'il ne servait à rien, grosse erreur.

Explication :

Dans une DLL, le tableau des noms est trié par alphabet, alors que le tableau des adresses est rangé selon l'ordre du compilateur. L'ordinal est l'unique lien entre les deux. Ainsi on a :

La formule à retenir :

Index Réel = ordinal_table[index_du_nom] RVA = function_table[Index_Réel]

En résumé : Sans l'ordinal, on récupères l'adresse d'une fonction au hasard car les noms et les adresses ne sont pas alignés.

Ainsi le code corrigé est le suivant :

Ainsi on a corrigé et on arrive à récupérer la bonne adresse. Voici désormais la version généralisé de la fonction :

Boom, on a enfin réussi ^^.

Note : Je dois revoir la rédaction et corriger le contenu car celui-ci a été écrit à différents moment de compréhension mais la manière de procéder pour coder from scratch la fonction est la bonne, tout les articles sur internet que j'ai lu donné la fonction sans rentrer dans le détails, là désormais j'ai une bonne compréhension grâce à l'utilisation de PE bear et aux différents échecs survenu.

En réalité ce n'est pas terminé car ma fonction custom ne couvre pas tout les cas. Si je tente par exemple de récupérer la fonction HeapAlloc dans Kernel32.dll je remarque que l'adresse renvoyé n'est pas la bonne :

Ainsi il faut debug et pour ce faire j'utilise PE Bear pour comparer ntdll et kernel32 pour identifier ce qui diffère :

Je remarque que sur ntdll.dll la colonne forwarder était vide tandis que pour kernel32.dll celle-ci ne l'ai pas pour la fonction HeapAlloc.

Cherchons à comprendre qu'est ce qu'un forwarder :

Pour la faire simple, un forwarder permet de dire, pour appeller cette fonction utilise plutôt la fonction présente dans tels DLL et dans cette DLL utilise tels fonction.

En gros, pour notre cas ntdll expose on va dire des fonctions qui sont général et réutilisé par d'autre DLL. Donc ici le HeapAlloc est en réalité remplacé par la fonction RtlAllocHeap

Ainsi j'ai modifié mon code pour géré ce cas :

Ca fonctionne mais c'est vraiment une manière de faire qui n'est pas du tout propre.

Pour faire de la bonne manière il faut remarqué plusieurs choses sur PE bear :

Les fonctions non forwarder sont présente dans la section bleu donc la section text et les fonctions forwarder dans la section verte :

Donc il faut trouver un moyen de faire ce check mais on peut remarquer autre choses, toutes les function RVA des fonctions forwarder sont compris entre le début de l'adresse de l'export directory et l'export directory + la size donc entre A4B60 et (A4B60 + EC4C)

Effectivement comme le montre la figure suivante :

Donc ça donne le code suivant :

Ainsi s'achève le développement custom de la fonction getprocadress

Last updated