Je le poste ici quand même - sans me soucier des éventuels soucis de formatage.
Source : http://nibbles.tuxfamily.org/?p=409
L'IAT - ou Import Address Table - est une section souvent présente dans les fichiers exécutables windows structurés par le format PE. Pour les connaisseurs, on peut la trouver sous le nom de ".idata" voire de ".rdata". Il existe des techniques de piratage de cette section, comme le hooking IAT (que je n'expliciterai malheureusement pas, n'ayant pas *encore* les connaissances requises pour).
Dans les fichiers exécutables, les sections sont des blocs de données regroupés par utilisation. On a généralement :
- .text : la section "code" de notre fichier exécutable, où se retrouvent les instructions séquentielles qui seront exécutées par le processeur (du binaire pur, donc) ;
- .data : la section qui contient, en vrac, des données au préalable initialisées (à notifier que, dans la section .code, l'algorithme généré par le compilateur va se démerder pour adresser ses données) ;
- .bss : la section qui contient des données non-initialisées : généralement présente sous des octets nuls, l'algorithme présent dans la section .code va pouvoir y stocker des données ;
- .idata : la section "importations", que nous allons étudier (sans déconner ?).
Nous allons, tout d'abord, définir ce qu'est la section importation (je dirai IAT par la suite), étudier son intérêt, pour enfin passer à un peu de pratique pour faire joujou avec (Demi-Reversing car on a des données théoriques, et parce qu'on va faire de la pratique).
J'oubliais de préciser : il faut connaître un minimum le format "PE" pour assurer une plus ou moins bonne compréhension de l'article.
"IAT" ? C'est quoi, ça ?!
Littéralement, Import Address Table. Cette section a sa fonction particulière : elle consiste à regrouper le nom des fonctions "dynamiques" - si je puis dire - présente dans les "DLL" - kernel32.dll, user32.dll... - afin de pouvoir les utiliser dans le programme. Par exemple, beaucoup de programmes auront besoin de la fonction ExitProcess(), programmée dans la bibliothèque "kernel32.dll". Il va donc falloir préciser dans l'IAT que l'on aura besoin de cette fameuse fonction.
Pourquoi ?
Parce que c'est la vie et tu la boucles les fonctions sont adressées différemment en fonction des systèmes d'exploitation, que ce soit Windows XP ou Windows Vista (32 bits, dans nos futurs exemples). Si les adresses étaient toutes les mêmes, on n'aurait pas besoin de préciser le nom des fonctions à importer. Leur adresse aurait suffit.
Qui va trouver les adresses, alors ?
Le "loader" de Windows, tout simplement. Lorsque ce "loader" va charger votre programme en mémoire, il va aller chercher les adresses des fonctions dont il a trouvé le nom, et va écrire ces adresses dans la section IAT. L'endroit de l'écriture est défini lui-même dans la section IAT. Tout est paramétré dedans. Nous allons l'étudier.
Structure de l'IAT
Pour la suite, nous allons nous servir d'un binaire basique qui ne fait qu'afficher "Hello". Vous pouvez l'obtenir à cette adresse : http://nibbles.tuxfamily.org/wp-content/uploads/2009/07/hello.exe
L'IAT commence toujours par des structures de type "IMAGE_IMPORT_DESCRIPTOR". Sa définition est la suivante :
#include
typedef struct _IMAGE_IMPORT_DESCRIPTOR {
union {
DWORD Characteristics; // 0 for terminating null import descriptor
DWORD OriginalFirstThunk; // RVA to original unbound IAT (PIMAGE_THUNK_DATA)
};
DWORD TimeDateStamp; // 0 if not bound,
// -1 if bound, and real date\time stamp
// in IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT (new BIND)
// O.W. date/time stamp of DLL bound to (Old BIND)
DWORD ForwarderChain; // -1 if no forwarders
DWORD Name;
DWORD FirstThunk; // RVA to IAT (if bound this IAT has actual addresses)
} IMAGE_IMPORT_DESCRIPTOR;
typedef IMAGE_IMPORT_DESCRIPTOR UNALIGNED *PIMAGE_IMPORT_DESCRIPTOR;Énumérons, un à un, les membres de cette structure :
- Characteristics / OriginalFirstThunk (DWORD, donc 4 octets) : il s'agit d'une adresse virtuelle relative - se référer à l'ImageBase dans l'en-tête PE pour récupérer l'adresse virtuelle absolue - indiquant un pointeur vers le tableau des fonctions à importer. Ces fonctions sont définies par la structure suivante :
#include
typedef struct _IMAGE_IMPORT_BY_NAME {
WORD Hint;
BYTE Name[1];
} IMAGE_IMPORT_BY_NAME, *PIMAGE_IMPORT_BY_NAME;
Avec :
- Hint (WORD : 2 octets) : le numéro identifiant de la fonction. Il n'est pas explicitement valide, et sert à titre indicatif pour le "loader" windows ;
- Name (Byte : 1 octet) : le nom de la fonction. On ne se servira pas de ce membre de structure pour obtenir le nom de la fonction ; sa taille est inconnue, tant la chaîne de caractère se termine de toute évidence par un octet nul. - TimeDataStamp (DWORD : 4 octets) : indique la date de création du fichier. Ce membre ne contient pas forcément une valeur valide (0 dans plusieurs cas) ;
- ForwarderChain (DWORD : 4 octets) : un champ qu'on voit souvent à 0. J'ai essayé de me renseigner sur Google, je n'ai vu que des "c'est un truc avancé, on en parlera après", alors qu'il n'en parlent jamais. De ce fait, on va oublier ce champ puisqu'il ne gênera pas pour la suite ;
- Name (DWORD : 4 octets) : adresse virtuelle relative codée sur 4 octets et qui pointe sur une chaîne de caractère terminée par un octet nul. Cette chaîne correspond au nom de la DLL dont les fonctions proviennent ;
- FirstThunk : à peu près pareil que OriginalFirstThunk. Je dis bien "à peu près", parce qu'il m'est plus fiable lors de la manipulation de l'IAT sur plusieurs exécutables différents.
On ne connait pas exactement le nombre de structures "IMAGE_IMPORT_DESCRIPTOR". On sait juste que la dernière structure contient tout ses membres non-initialisés (donc = 0).
Grâce au membre OriginalFirstThunk, on va pouvoir pointer sur un lot d'adresses virtuelles relatives qui correspondent à des pointeurs sur des structures "IMAGE_IMPORT_BY_NAME". On ne connait pas le nombre exact de structures présentes ; il faut simplement s'arrêter jusqu'à ce qu'on tombe sur une adresse virtuelle relative égale à 0. Grâce à ça, on va pouvoir connaître les fonctions importées de la DLL. Le champ FirstThunk - le dernier de la structure - quant à lui, lorsque le programme est chargé en mémoire, pointe sur un lot d'adresses virtuelles qui correspondent aux adresses mémoires des fonctions. Ce sont ses adresses qui seront appelées par le programme dans la section .text (ou CODE pour les programmes compilés avec un environnement Borland).
Prenons notre "désassembleur" favori : ollydbg. Ouvrons notre exécutable "hello.exe", faisons Alt + M (ou View > Memory) et cliquons sur. Vous avez ça :
[caption id="" align="aligncenter" width="444" caption="OllyDbg : visualisation de l'IAT"]
[/caption]J'ai tracé, en rouge, le chemin engendré par les pointeurs pour arriver au nom de la première fonction importée par le programme. Notez bien que les adresses mémoires sont écrites à l'envers, car c'est comme ça que sont représentés les entiers en dur dans la mémoire. On a donc, en fin de trajet, le "Hint" de la fonction ainsi que son nom, encadrés en bleus. En vert, j'ai tracé le chemin pour mettre en évidence le pointage vers la DLL Kernel32.dll.
Voyons maintenant vers quoi pointe FirstThunk au lieu de OriginalFirstThunk :
[caption id="attachment_413" align="aligncenter" width="444" caption="OllyDbg : visualisation de l'IAT"]
[/caption]Ce champ pointe vers les adresses mémoires des fonctions. Pour être sûr de ce que j'avance, je vais utiliser arwin pour vérifier que la fonction AddAtom() ait bien l'adresse 0x7C835535. Vous pouvez télécharger l'utilitaire ici : http://nibbles.tuxfamily.org/wp-content/uploads/2009/07/arwin.exe ; en voici son code source :
#include
#include
/***************************************
arwin - win32 address resolution program
by steve hanna v.01
vividmachines.com
shanna@uiuc.edu
you are free to modify this code
but please attribute me if you
change the code. bugfixes & additions
are welcome please email me!
to compile:
you will need a win32 compiler with
the win32 SDK
this program finds the absolute address
of a function in a specified DLL.
happy shellcoding!
***************************************/
int main(int argc, char** argv)
{
HMODULE hmod_libname;
FARPROC fprc_func;
printf("arwin - win32 address resolution program - by steve hanna - v.01\n");
if(argc < 3)
{
printf("%s
\n",argv[0]);
exit(-1);
}
hmod_libname = LoadLibrary(argv[1]);
if(hmod_libname == NULL)
{
printf("Error: could not load library!\n");
exit(-1);
}
fprc_func = GetProcAddress(hmod_libname,argv[2]);
if(fprc_func == NULL)
{
printf("Error: could find the function in the library!\n");
exit(-1);
}
printf("%s is located at 0x%08x in %s\n",argv[2],(unsigned int)fprc_func,argv[1]);
}On l'exécute, et voici ce qu'on a en retour :
C:\Program Files\Slayers Online2>arwin.exe kernel32.dll AddAtomA
arwin - win32 address resolution program - by steve hanna - v.01
AddAtomA is located at 0x7c835535 in kernel32.dll
On en déduit que le loader a bien placé l'adresse des fonctions dans la table d'importations.
Un peu de prog ?
Depuis quelques temps, je bosse sur une bibliothèque pour me permettre d'analyser et manipuler le format PE de sorte à mieux le cerner. Je vais vous communiquer trois sources : main.c (prog principal), pe.h (définitions de fonctions) et pe.c (les fonctions).
main.c :
#include
#include
#include
#include "pe.h"
int main(int argc, char **argv)
{
if(argc < 2) {
printf("Utilisation : %s \n", argv[0]);
exit(EXIT_FAILURE);
}
PortableExecutable MyPe;
MyPe.fp = fopen(argv[1],"rb");
if(!MyPe.fp) {
printf("Erreur lors de l'ouverture de %s : abandon.\n", argv[1]);
exit(EXIT_FAILURE);
}
PE_Init(&MyPe,argv[1]);
Pe_ShowFunctionsUsed(MyPe);
PE_Destroy(&MyPe);
return 0;
}pe.h :
#ifndef PE_H
#define PE_H
#include
typedef struct {
FILE *fp; // Pointeur vers le fichier exécutable
IMAGE_DOS_HEADER ImageDosHeader; // structure 'MZ'
IMAGE_FILE_HEADER ImageFileHeader; // structure 'Pe'
IMAGE_OPTIONAL_HEADER ImageOptionalHeader; // Structure de l'en-tête optionelle
PIMAGE_SECTION_HEADER ImagesSectionHeader; // Pointeur sur les structures d'en-têtes de sections
} PortableExecutable;
// Constructeur & Destructeur
int PE_Init(PortableExecutable *Pe, char *filename);
VOID PE_Destroy(PortableExecutable *MyPe);
// Osef pour le moment
int PE_Create(char *filename);
// Requisition de données
DWORD Pe_GetOffsetIat(PortableExecutable Pe);
DWORD Pe_GetVirtualAddressOfIat(PortableExecutable Pe);
DWORD Pe_GetOffsetFromSection(PortableExecutable Pe, char *name);
// Fonctions d'affichage
VOID PE_DmpSectionHeaders(PortableExecutable MyPe);
VOID Pe_ShowDllsImported(PortableExecutable Pe);
VOID Pe_ShowFunctionsUsed(PortableExecutable Pe);
VOID Pe_ShowImageImportDescriptors(PortableExecutable Pe);
#endifpe.c :
#include
#include
#include "pe.h"
/* Cette fonction permet d'initialiser
* notre structure PortableExecutable ;
* elle va lire les en-tête dans les
* différentes structures.
* @param *MyPe (PortableExecutable) : structure PortableExecutable
* @param *filename (char*) : chaîne de caractères correspondant au
* fichier exécutable à ouvrir en lecture binaire.
*/
int PE_Init(PortableExecutable *MyPe, char *filename)
{
MyPe->fp = fopen(filename,"rb");
if(!MyPe->fp) {
printf("Erreur lors de l'ouverture de %s : abandon.\n", filename);
return -1;
}
DWORD MagicWord;
// Lecture de l'en-tête DOS
fread(&MyPe->ImageDosHeader, sizeof(IMAGE_DOS_HEADER), 1, MyPe->fp);
// Déplacement jusqu'à l'en-tête PE
fseek(MyPe->fp, MyPe->ImageDosHeader.e_lfanew, SEEK_SET);
// Lecture du mot magique
fread(&MagicWord, sizeof(DWORD), 1, MyPe->fp);
// Lecture de l'en-tête PE
fread(&MyPe->ImageFileHeader, sizeof(IMAGE_FILE_HEADER), 1, MyPe->fp);
// Est-ce bien un PE ?
if(MagicWord != IMAGE_NT_SIGNATURE) {
return -2;
}
// L'en-tête optionnelle existe ?
if(MyPe->ImageFileHeader.SizeOfOptionalHeader != 0) {
// On la lit
fread(&MyPe->ImageOptionalHeader, sizeof(IMAGE_OPTIONAL_HEADER), 1, MyPe->fp);
}
// On va lire toutes les sections
MyPe->ImagesSectionHeader =
(PIMAGE_SECTION_HEADER)malloc(MyPe->ImageFileHeader.NumberOfSections * sizeof(IMAGE_SECTION_HEADER));
int i;
for(i = 0; i ImageFileHeader.NumberOfSections; i++)
fread(&MyPe->ImagesSectionHeader[i], sizeof(IMAGE_SECTION_HEADER), 1, MyPe->fp);
return 1;
}
/*
* Cette fonction affiche les en-têtes de chaque section.
* @param MyPe (PortableExecutable) : structure PortableExecutable
*/
VOID PE_DmpSectionHeaders(PortableExecutable MyPe) {
int i;
// +4 "PE#0#0"
fseek(MyPe.fp, 4 + MyPe.ImageDosHeader.e_lfanew + sizeof(MyPe.ImageFileHeader)
+ MyPe.ImageFileHeader.SizeOfOptionalHeader, SEEK_SET);
for(i = 0; i ImagesSectionHeader);
}
/*
* Cette fonction retourne l'offset - en dur dans le fichier -
* d'une section dont on connait le nom.
* @param MyPe (PortableExecutable) : structure PortableExecutable.
* @param *name (char*) : nom de la section
*/
DWORD Pe_GetOffsetFromSection(PortableExecutable MyPe, char *name) {
int i = 0;
while(strcmp(MyPe.ImagesSectionHeader[i++].Name, name) &&
i < MyPe.ImageFileHeader.NumberOfSections);
return --i < MyPe.ImageFileHeader.NumberOfSections ?
MyPe.ImagesSectionHeader[i].PointerToRawData : 0;
}
/*
* Cette fonction affiche les dll importées par l'exécutable
* @param MyPe (PortableExecutable) : structure PortableExecutable.
*/
VOID Pe_ShowDllsImported(PortableExecutable Pe) {
if(!Pe.ImageOptionalHeader.NumberOfRvaAndSizes) {
printf("Aucune section IAT dans le programme.\n");
} else {
DWORD OffsetIat = Pe_GetOffsetIat(Pe);
DWORD VirtualAddressIat = Pe_GetVirtualAddressOfIat(Pe);
// Compteur de descripteurs d'images d'importation
int cpt = 0;
// Nom courant de la DLL
char dllname[255];
// Variable qui représente notre descripteur d'image d'importation
IMAGE_IMPORT_DESCRIPTOR CurrentImage;
// On se déplace au début de l'IAT
fseek(Pe.fp, OffsetIat, SEEK_SET);
// Lecture de l'image
fread(&CurrentImage, sizeof(IMAGE_IMPORT_DESCRIPTOR), 1, Pe.fp);
// On boucle jusqu'à lire un IMAGE_IMPORT_DESCRIPTOR complètement mis
// à 0
while(CurrentImage.Characteristics != 0
|| CurrentImage.TimeDateStamp != 0
|| CurrentImage.ForwarderChain != 0
|| CurrentImage.Name != 0
|| CurrentImage.FirstThunk != 0) {
// On va se déplacer à l'offset du nom de la DLL
// CurrentImage.Name contient, en effet, l'adresse
// virtuelle et relative de la chaîne - terminée par
// null-byte - du nom de la DLL
fseek(Pe.fp, (CurrentImage.Name - VirtualAddressIat)
+ OffsetIat, SEEK_SET);
fread(dllname, sizeof(char), 254, Pe.fp);
printf("%s ;\n", dllname);
// On se déplace à l'image suivante
fseek(Pe.fp, OffsetIat
+ (++cpt*sizeof(IMAGE_IMPORT_DESCRIPTOR)), SEEK_SET);
// On la lit
fread(&CurrentImage, sizeof(IMAGE_IMPORT_DESCRIPTOR), 1, Pe.fp);
}
}
}
/*
* Cette fonction affiche les fonctions importées par le prog (peut bugger...)
* @param MyPe (PortableExecutable) : structure PortableExecutable.
*/
VOID Pe_ShowFunctionsUsed(PortableExecutable Pe) {
if(!Pe.ImageOptionalHeader.NumberOfRvaAndSizes) {
printf("Aucune section IAT dans le programme.\n");
} else {
DWORD VirtualAddressOfIat = Pe_GetVirtualAddressOfIat(Pe);
DWORD OffsetOfIat = Pe_GetOffsetIat(Pe);
int n = 0;
// On se déplace dans l'IAT
fseek(Pe.fp, OffsetOfIat, SEEK_SET);
// On lit chaque IMAGE_IMPORT_DESCRIPTOR jusqu'à en trouver une
// donc chaque champ est mis à 0
IMAGE_IMPORT_DESCRIPTOR CurrentImage;
// La structure représentant le Hint & le
// nom de la fonction
IMAGE_IMPORT_BY_NAME CurrentFunction;
// L'adresse virtuelle relative des IMAGE_IMPORT_BY_NAME
DWORD RelativeVirtualAddress;
// La variable qui contiendra l'offset en fonction de la RVA
DWORD Offset;
// chaines qui contiendront le nom de la dll et le nom de la fonction
char dllname[255], funcname[255];
fread(&CurrentImage, sizeof(IMAGE_IMPORT_DESCRIPTOR), 1, Pe.fp);
int cpt = 0;
while(CurrentImage.OriginalFirstThunk != 0
|| CurrentImage.TimeDateStamp != 0
|| CurrentImage.ForwarderChain != 0
|| CurrentImage.Name != 0
|| CurrentImage.FirstThunk != 0) {
fseek(Pe.fp, (CurrentImage.Name - VirtualAddressOfIat)
+ OffsetOfIat, SEEK_SET);
fread(dllname, sizeof(char), 254, Pe.fp);
printf("[*] %s :\n", dllname);
// On se déplace à "OriginalFirstThunk"
Offset = OffsetOfIat + (CurrentImage.FirstThunk - VirtualAddressOfIat);
fseek(Pe.fp, Offset, SEEK_SET);
// On lit l'offset de la première IMAGE_IMPORT_BY_NAME
fread(&RelativeVirtualAddress, sizeof(DWORD), 1, Pe.fp);
while(RelativeVirtualAddress != 0) {
// On s'y déplace
Offset = OffsetOfIat + (RelativeVirtualAddress - VirtualAddressOfIat);
fseek(Pe.fp, Offset, SEEK_SET);
// Lecture de l'IMAGE_IMPORT_BY_NAME
fread(&CurrentFunction.Hint, sizeof(WORD), 1, Pe.fp);
// Affichage du nom de la fonction
fread(funcname, sizeof(char), 255, Pe.fp);
printf("\t- %s\n", funcname);
// On revient aux RVAs des fonctions
Offset = OffsetOfIat + (CurrentImage.OriginalFirstThunk - VirtualAddressOfIat);
fseek(Pe.fp,(++cpt) * sizeof(DWORD) + Offset, SEEK_SET);
fread(&RelativeVirtualAddress, sizeof(DWORD), 1, Pe.fp);
}
printf("\n");
cpt = 0;
fseek(Pe.fp,(++n)*sizeof(IMAGE_IMPORT_DESCRIPTOR) + OffsetOfIat, SEEK_SET);
fread(&CurrentImage, sizeof(IMAGE_IMPORT_DESCRIPTOR), 1, Pe.fp);
}
}
}
// Osef pour l'instant
int PE_Create(char *filename) {
FILE *fp = NULL;
if((fp = fopen(filename,"wb")) == NULL) {
return -1;
}
}
/*
* Cette fonction affiche les structures d'images d'importations -
* de la section IAT - avec leurs membres.
* @param MyPe (PortableExecutable) : structure PortableExecutable.
*/
VOID Pe_ShowImageImportDescriptors(PortableExecutable Pe) {
IMAGE_IMPORT_DESCRIPTOR CurrentImage;
DWORD OffsetIat = Pe_GetOffsetIat(Pe);
fseek(Pe.fp, OffsetIat, SEEK_SET);
fread(&CurrentImage, sizeof(IMAGE_IMPORT_DESCRIPTOR), 1, Pe.fp);
printf("---------------------------------------------\n");
while(CurrentImage.OriginalFirstThunk != 0
|| CurrentImage.TimeDateStamp != 0
|| CurrentImage.ForwarderChain != 0
|| CurrentImage.Name != 0
|| CurrentImage.FirstThunk != 0) {
printf("OriginalFirstThunk = 0x%08X\n"
"TimeDateStamp = 0x%08X\n"
"ForwarderChain = 0x%08X\n"
"Name = 0x%08X\n"
"FirstThunk = 0x%08X\n"
"---------------------------------------------\n"
, CurrentImage.OriginalFirstThunk, CurrentImage.TimeDateStamp,
CurrentImage.ForwarderChain, CurrentImage.Name, CurrentImage.FirstThunk);
fread(&CurrentImage, sizeof(IMAGE_IMPORT_DESCRIPTOR), 1, Pe.fp);
}
}
/* Cette fonction récupère l'offset pur et dur - donc dans le fichier -
* de la section IAT
* @param MyPe (PortableExecutable) : structure PortableExecutable.
*/
DWORD Pe_GetOffsetIat(PortableExecutable Pe) {
IMAGE_DATA_DIRECTORY Iat;
int i = 0;
fseek(Pe.fp, Pe.ImageDosHeader.e_lfanew + 0x80, SEEK_SET);
fread(&Iat, sizeof(IMAGE_DATA_DIRECTORY), 1, Pe.fp);
while(Pe.ImagesSectionHeader[i++].VirtualAddress <= Iat.VirtualAddress);
i -= 2;
return Pe.ImagesSectionHeader[i].PointerToRawData +
(Iat.VirtualAddress - Pe.ImagesSectionHeader[i].VirtualAddress);
}
/* Cette fonction récupère l'adresse virtuelle
* de la section IAT
* @param MyPe (PortableExecutable) : structure PortableExecutable.
*/
DWORD Pe_GetVirtualAddressOfIat(PortableExecutable Pe) {
IMAGE_DATA_DIRECTORY Iat;
fseek(Pe.fp, Pe.ImageDosHeader.e_lfanew + 0x80, SEEK_SET);
fread(&Iat, sizeof(IMAGE_DATA_DIRECTORY), 1, Pe.fp);
return Iat.VirtualAddress;
}Résultat :
C:\Documents and Settings\Geoffrey\C\PE\bin\Debug>PE.exe hello.exe
[*] KERNEL32.dll :
- AddAtomA
- ExitProcess
- FindAtomA
- GetAtomNameA
- SetUnhandledExceptionFilter
[*] msvcrt.dll :
- __getmainargs
- __p__environ
- __p__fmode
- __set_app_type
- _assert
- _cexit
- _iob
- _onexit
- _setmode
- abort
- atexit
- free
- malloc
- memset
- printf
- signal
Le code C est très crasseux et reste à améliorer, autant sur la revue des algos que sur l'ajout de nouvelles fonctionnalités.
Vous pouvez obtenir l'archive contenant sources + exécutable + fichier projet Code::Blocks ici : http://nibbles.tuxfamily.org/wp-content/uploads/2009/07/PE.zip
Conclusion
La section IAT reste intéressante en elle. Son fonctionnement est tel qu'elle puisse importer des fonctions de n'importe quelle DLL - tant qu'elle existe - sans en connaître ses adresses mémoires. Simplement ses noms. Il existe cependant d'autres méthodes pour faire appel à des fonctions sans passer par l'IAT, telles que l'utilisation des fonctions LoadLibrary() et GetProcAddress().
Liens :
- 0vercl0k's blog.: API Hooking - IAT patching (premier sur Google, bravo à notre ami pendule !) ;
- 0vercl0k's blog.: Dump Own iat., un article aux objectifs semblables. Toujours d'0vercl0k.
(Désolé pour les bavures sur le formatage des différents codes sources. L'archive reste *normalement* à disposition).