CHAPITRE 3 - L’EXPORT TABLE




Accrochez-vous, L’export table arrive! Comme vous le savez (enfin j’espère;)), un programme win32 utilise des APIs pour fonctionner (on peut dire que ces API sont l’équivalent des interruptions DOS). Un virus informatique ne déroge pas à la règle.

L’adresse d’une API est stockée dans l’EXPORT_TABLE d’une DLL bien précise (une DLL contient plusieurs adresses d’APIs). Quand un exécutable est lancé, il écrit dans son IMPORT_TABLE le nom des DLL dont il a besoin, ainsi que les adresses auxquelles il peut trouver les APIs dans ces DLL (on appelle ces adresses ENTRYPOINT), ces adresses étant stockée dans l’export_table des DLL. Le problème, c’est que ces adresses changent d’un ordinateur à l’autre, il faut donc les calculer pour chaque ordinateur si on veut que notre virus se répande ailleurs que sur la machine où il a été créé.

Il existe un plusieurs moyens pour arriver à nos fins; je vais vous présenter le plus connus, qui permet d’accéder à la quasi totalité des APIS, en tout cas touttes celles dont le virus a besoin. Pour cela, on utilise deux APIs: GetModuleHandle et GetProcAddress. Très brièvement, voici leur fonctionnement: GetProcAddress nous donne l’adresse de l’API qu’on lui demande; cependant, GetProcAddress a besoin d’un handle de la DLL qui exporte l’API voulue; on utilise GetModuleHandle (windoz crée des handle pour tout: un handle est l'identificateur d'un objet, cet objet pouvant être une fenêtre, un fichier... Chaque fois qu'un programme a besoin d'utilise un objet, il doit s'en référer à son identificateur (son handle)) pour obtenir un handle de la DLL voulue, handle qu’on va utiliser ensuite pour fournir les informations nécessaires à GetProcAddress. C’est un peu confus j’en suis conscient, mais j’ai essayé d’expliquer ça du mieux que j’ai pu… Retenez que si les API que le virus utilise sont toutes contenues dans la DLL kernel32, alors vous n’aurez besoin que de GetProcAddress (dont il faudra calculer l’adresse). Si il y a des API venant d’autres DLL, alors il faudra utiliser les 2 API (GetModuleHandle est une API exportée par kernel32).

Une fois ces 2 API trouvées, on obtient les autres fingers in the nose! Il existe 2 façons (à ma connaissance) de trouver ces APIs:


L’inconvénient de la première méthode vient du fait que si le programme que nous voulons infecter n’utilise pas GetProcAddress, on ne pourra pas la localiser, et a fortiori s’en servir.

Sachant que les entrypoint (adresse dans la DLL) de ces 2 API sont dans la DLL kernel32.dll, le plus simple est d’utiliser la deuxième méthode. Mais comment accéder à la DLL kernel32? Eh bien le hasard (ou les ingénieurs de chez Microsoft :P) faisant bien les choses, à chaque fois que nous lançons un exécutable, c’est comme si il était appelé par kernel32.dll via l’API CreateProcessA. Donc on s’y trouve déjà au début de chaque lancement de programme.

Ah! j’allais oublier le plus important : Les DLL sont aussi au format PE, comme les exécutables (avec des RVA, des image_base et tout et tout…)


On peut trouver l’adresse de l’export table à l’offset 00000078h du PE d’une DLL. MAIS! N’oublions pas qu’il s’agit d’une RVA! Il suffit d’y ajouter l’adresse à laquelle elle débute (image_base) pour trouver sa véritable adresse. Nous verrons comment trouver le début de kernel32 (grâce à la signature MZ) un peu plus loin. Pour l’instant, on va regarder à quoi ressemble l’export table, parce qu’il y a encore 2-3 choses dont il faut absolument parler avant de passer à la suite… Je sais que ça peu paraître ardu (en fait ça l’est réellement), mais après vous danserez tout nu quand on passera au code source du virus. Voici à quoi ressemble une export_table:

EXPORT FLAGS

TIME/DATE STAMP

MAJOR VERSION

MINOR VERSION

NAME RVA

NUMBER OF EXPORTED FUNCTIONS

NUMBER OF EXPORTED NAMES

EXPORT ADDRESS TABLE RVA

EXPORT NAME POINTER TABLE RVA

EXPORT ORDINAL RVA






Arghh! Promis c’est la dernière fois ! Seuls les champs en gras nous importent, et les couleurs sont là pour plus de clarté.


Les 3 derniers champs sont des RVA, donc liées à une image_base, en l’occurrence celle de kernel32.dll. La première étape pour obtenir l’adresse d’une API est de savoir quelle position elle occupe dans le champ «export name pointer RVA»: on compare le nom de l’API voulue à celui sur lequel on est en train de pointer (dans le champ précédemment cité), et si les deux nom sont exactement les mêmes on considère qu’on a trouvé l’API.

Bien évidemment, on aura eu la bonne idée d’ajouter un compteur qui s’incrémente à chaque fois qu’on fait une comparaison, tout simplement parce que les 3 derniers champs sont des sortes de «listes parallèles entre elles»: par exemple, la troisième API du champ «export name pointer RVA» correspondra à la troisième adresse contenue dans le champ «export address table RVA» ainsi qu’au troisième ordinal du champ «export ordinal RVA». On peut donc facilement trouver l’adresse et l’ordinal d’une API en ayant simplement son nom et sa position.

La dernière chose à savoir est que les ordinal sont stockés sous forme de WORD et les address sous forme de DWORD, il faudra en tenir compte pour en déduire l’address d’une API à partie de sa position (trouvée grâce au compteur dont je parle plus haut).


Algorithme pour trouver l’adresse d’une API à partir de sa position:


(position*2) + ordinal_table_VA = ordinal de l’API

on multiplie la position par 2 (justification plus haut) et on ajoute l’adresse à laquelle débute l’ordinal table


(ordinal de l’API*4) + address_table_VA + kernel32 image_base = adresse de l’API!!

on multiplie le résultat précédent par 4 (car les adresses sont stockées sous forme de DWORD dont la taille est 4 octets), auquel on ajoute l’adresse à laquelle débute l’address_table: On obtient la RVA de l’API. On ajoute ensuite l’imabe_base de kernel32.dll, et on obtient l’adresse de l’API.


Cette algorithme sera détaillé dans la partie suivante et sera plus facile à comprendre une fois que vous aurez vu comment le coder. S’il y a des survivants on va voir comment se déroule une infection classique.