Last update: 29/05/2007


EtatStable
Version1.10
Mise à jour29/05/2007

Projet Hooker
"Hooker" est un template de projet C permettant de rapidement développer des applications effectuant du hookig d'api par injection de thread/patch du header. Ce projet s'appuie sur WiShMaster.

Le hooking par patch du header
Le hooking par patch du header est une technique de hooking consistant à remplacer les premières instructions d'une fonction par un saut vers un code d'analyse.
Cette technique est extrêmement puissante car elle permet d'effectuer un traitement avant et après l'exécution de la fonction.
Les applications sont alors très nombreuses : espionnage des données manipulées par la fonction, modification à la volée des données, ...
Cependant, elle reste relativement délicate à mettre en oeuvre :
  • Bien que les opérations de patch puissent être faites à partir d'un autre processus, il est beaucoup plus simple d'injecter un thread qui va effectuer ces modifications. Le code injecté doit alors être un shellcode.
  • Si l'on veut éviter le chargement d'une dll dans l'espace du processus, les parties d'analyse doivent être des shellcodes
  • Il est nécessaire de déterminer l'alignement des instructions du header de la fonction
  • ...
Principe de Hooker
Hooker est un ensemble de fichiers sources C et de scripts permettant de créer rapidement un outil injectant du code dans un autre processus pour hooker certaines fonctions
Son utilisation est relativement directe :
  • Dans un premier temps, vous exécutez un script PERL qui va patcher certains fichiers et les adapter à votre environnement.
  • Vous compilez alors les fichiers sources avec WiShMaster et obtenez un exécutable qui représente un petit outil pouvant injecter du code dans un processus désigné et hooker les fonctions "CreateFileW", "RegOpenKeyExW" et "RegCreateKeyW".
  • Il vous suffit alors de modifier les sources en fonction des fonctions que vous souhaitez réellement hooker puis à régénérer l'exécutable final avec WiShMaster.
En pratique : Obtenir un outil hookant "send" et "recv" en 5 min.
Voici les étapes pour créer MySniff, un outil qui va logger toutes les données envoyées et recues par un processus via les fonctions "send" et "recv"
Déploiement de HOOKER
  • Téléchargez HOOKER dans un répertoire quelconque.
  • Lancez le script patch.pl et entrez MySniff comme nom de projet :
Enter project name : MySniff
Patching MySniff.ppr
Patching MySniff.xml
Patching MySniff_internalfunctions.xml
Patching build.pl
Patching makefile
Patching sources\MySniff.cpp
Patching integration\build.pl
Patching integration\makefile
Patching integration\sources\main.cpp

Patches successully applied
Press [ENTER] to exit...
  • Lancez WiShMaster, clickez sur open->project et sélectionnez le fichier "MySniff.xml"
  • Cliquez sur "START" et attendez la fin de la génération.
  • Vous venez de générer un exécutable "MySniff.exe" dans "integration\exe". Ce programme prend un nom de d'exécutable en paramètre, recherche la premier processus qui correspond, l'injecte avec un thread. Ce thread va hooker les fonctions "CreateFileW", "RegOpenKeyExW" et "RegCreateKeyW" et afficher des messages sur le debugger noyau lorsqu'elles seront appelées.
Ajout des fonctions de hook de send et recv : les fichiers "injected_functions.*"
Ouvrez le fichier "injected_functions.cpp", et supprimez toutes les fonctions de hook : "HookCreateFileW", "HookRegOpenKeyExW" et "HookRegCreateKeyW".
Ajoutez ensuite la fonction de hook pour send. La fonction de hook de base doit être la suivante :
/* ================================================================================================
	Function HookSend

		Goal:
			Hook point for send

		Parameter:
			SOCKET s
			const char* buf
			int len
			int flags

		Return value:
			-
   ================================================================================================ */
int WINAPI HookSend(SOCKET s, const char* buf, int len, int flags)
{
	LPGLOBAL_DATA pGlobalData;
	pGlobalData = (LPGLOBAL_DATA) 0xdeadbabe;
	UINT uiOldEsp;

	// --------------------  Hook before --------------------

	PrintDebugMsg("Hook in send : before");

	// --------------------  End of hook before --------------------

	// --------------------  Call original function --------------------
	STACK_BUILD
	INT iRes = ((INT (WINAPI *)(VOID))
		&pGlobalData->HookFunctionArray[INDEX_HOOKED_FUNC_SEND].cJmpToFunc)();
	STACK_CLEAN
	// --------------------  End of call original function --------------------

	// --------------------  Hook after --------------------

	PrintDebugMsg("Hook in send : after");

	// --------------------  End of hook after --------------------

	return iRes;
}
Ce template est valable pour TOUTES les fonctions à hooker. Les seuls paramètres variants sont :
  • Le prototype, qui doit être le même que la fonction à hooker. Au niveau du nommage, la fonction doit commencer par "Hook" pour être reconnue comme telle par WiShMaster.
  • La constante "INDEX_HOOKED_FUNC_SEND", qui représente l'index de la fonction de hooking (ici 0).
Ajoutez également le hook pour la fonction recv :
/* ================================================================================================
	Function HookRecv

		Goal:
			Hook point for recv

		Parameter:
			SOCKET s
			char* buf
			int len
			int flags

		Return value:
			-
   ================================================================================================ */
int WINAPI HookRecv(SOCKET s, char* buf, int len, int flags)
{
	LPGLOBAL_DATA pGlobalData;
	pGlobalData = (LPGLOBAL_DATA) 0xdeadbabe;
	UINT uiOldEsp;

	// --------------------  Hook before --------------------

	PrintDebugMsg("Hook in recv : before");

	// --------------------  End of hook before --------------------

	// --------------------  Call original function --------------------
	STACK_BUILD
	INT iRes = ((INT (WINAPI *)(VOID))
		&pGlobalData->HookFunctionArray[INDEX_HOOKED_FUNC_RECV].cJmpToFunc)();
	STACK_CLEAN
	// --------------------  End of call original function --------------------

	// --------------------  Hook after --------------------

	PrintDebugMsg("Hook in recv : after");

	// --------------------  End of hook after --------------------

	return iRes;
}
Ajoutez également les prototypes de ces fonctions dans le fichier "injected_functions.h" (et supprimez ceux des anciennes fonctions de hook).
Ajout des constantes dans "constants.h"
Nous allons maintenant apporter quelques modifiations au fichier "constants.h":
  • NB_HOOK_FUNCTIONS passe de 3 à 2
  • NB_HOOK_DLL passe de 2 à 1 car send et recv sont dans la même dll ws2_32.dll
  • Remplacez les constantes pour les index des dlls :
    #define INDEX_HOOKED_DLL_KERNEL32					0
    #define INDEX_HOOKED_DLL_ADVAPI32					1
    
    Devient :
    #define INDEX_HOOKED_DLL_WS2_32						0
    
  • Remplacez les constantes pour les index des fonctions :
    #define INDEX_HOOKED_FUNC_CREATEFILEW				0
    #define	INDEX_HOOKED_FUNC_REGOPENKEYEXW				1
    #define INDEX_HOOKED_FUNC_REGCREATEKEYW				2
    
    Devient :
    #define INDEX_HOOKED_FUNC_SEND						0
    #define INDEX_HOOKED_FUNC_RECV						1
    
  • Vérifier que les nouvelles fonctions ont bien un nombre de paramètres inférieur à MAX_NB_OF_PARAMS
Mise à jour de l'initialisation de la constante globale
Enfin, nous mettons à jour l'initialisation de GlobalData dans le fichier "global_data.cpp" :
GLOBAL_DATA GlobalData =
{
  {  // HOOKED_FUNCTION[]
    {
      0,                        // iFunctionAddress
      0,                        // pHookFunction
      INDEX_HOOKED_DLL_WS2_32,  // iDllIndex
      "send",                   // szFunctionName[SZ_FUNC_NAME]      // WISHMASTER : SKIP STRINGS
      "",                       // cJmpToFunc[15]                    // WISHMASTER : SKIP STRINGS
      "",                       // cOrigFuncHeader[15]               // WISHMASTER : SKIP STRINGS
      0,                        // iNbOfBytesToPatch
      0,                        // iHookFunctionSize
    },
    {
      0,                        // iFunctionAddress
      0,                        // pHookFunction
      INDEX_HOOKED_DLL_WS2_32,  // iDllIndex
      "recv",                   // szFunctionName[SZ_FUNC_NAME]      // WISHMASTER : SKIP STRINGS
      "",                       // cJmpToFunc[15]                    // WISHMASTER : SKIP STRINGS
      "",                       // cOrigFuncHeader[15]               // WISHMASTER : SKIP STRINGS
      0,                        // iNbOfBytesToPatch
      0,                        // iHookFunctionSize
    },
  },
  {  // HOOKED_DLL[]
    {
      0,                        // hDllAddress
      "ws2_32.dll"              // szDllName[SZ_DLL_NAME]            // WISHMASTER : SKIP STRINGS
    },
  },
  CONST_PROTECT_NONE,           // ulProtect
  0,                            // ulProtectParam
};
Mise à jour de l'entrypoint
Editez le fichier "entrypoint\entrypoint.txt" et modifiez le nom de la première fonction de hook :
	pFunc = (ULONG *) &pGlobalData->HookCreateFileW;
Devient
	pFunc = (ULONG *) &pGlobalData->HookSend;
Génération de la nouvelle version de MySniff
Revenez sur WiShMaster et appuyez sur "ANALYSE" pour lancer l'analyse des sources.
Très important : Affichez alors la liste des fonctions internes ("Internal functions") et modifiez leur ordre pour qu'il corresponde à celui défini par les constantes INDEX_HOOKED_FUNC_*.

En clair, "HookRecv" doit être juste en dessous de "HookSend". A noter que l'ordre sera ensuite sauvegardé et que vous n'aurez plus besoin de refaire cette opération.
Appuyez alors sur "START" pour relancer tous le processus de génération de MySniff.exe (décochez "fast copy" au besoin)
Test de MySniff
Lancez DbgView et un Internet Explorer.
Lancez alors MySniff : MySniff.exe iexplore.exe
Faites quelques accès au web avec le IE, vous devriez voir des traces "Hook in send" dans la fenêtre de DbgView. En revanche, la fonction "recv" ne semble pas être appelée. Après une rapide recherche, nous constatons que Internet Explorer utilise la fonction de l'API Win32 WSARecv et non recv.
Amélioration de MySniff : log des données envoyées
Nous allons ajouter le log de toutes les données envoyées. Copiez-coller la fonction suivante dans "injected_functions.cpp" :
/* ================================================================================================
	Function WriteData

		Goal:
			Write data to log file

		Parameter:
			CHAR * pData
			ULONG ulDataLength

		Return value:
			TRUE	= Data successfully logged
			FALSE	= An error occurs
			
   ================================================================================================ */
BOOL WriteData(CHAR * pData, ULONG ulDataLength)
{
	if((ulDataLength == 0) || (ulDataLength > 0x80000000))
		return TRUE;

	HANDLE hFile;

	// Lock Mutex
	WaitForSingleObject(GlobalData.hMutexWrite, INFINITE);

	// Open Log File
	if((hFile = CreateFile("C:\\temp\\MySniff.txt", GENERIC_WRITE | GENERIC_READ, 0, NULL, 
			OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL)) == INVALID_HANDLE_VALUE)
	{
		PrintDebugMsg("Failed to open log file");
		ReleaseMutex(GlobalData.hMutexWrite);
		return FALSE;
	}

	// Set file pointer at end of file
	if(SetFilePointer(hFile, 0, 0, FILE_END) == INVALID_SET_FILE_POINTER)
	{
		PrintDebugMsg("Failed to set pointer at end of file");
		CloseHandle(hFile);
		ReleaseMutex(GlobalData.hMutexWrite);
		return FALSE;
	}

	DWORD dwWrittenSize;
	// Write data
	if(!WriteFile(hFile, (LPSTR) pData, (DWORD) ulDataLength, &dwWrittenSize, NULL))
	{
		PrintDebugMsg("Failed to write data to log file");
	}

	CloseHandle(hFile);

	// Release Mutex
	ReleaseMutex(GlobalData.hMutexWrite);
	
	return TRUE;
}
Ajoutez l'appel à cette fonction dans HookSend AVANT l'appel à la fonction :
	PrintDebugMsg("Hook in send : before");
	WriteData((char *)buf, len);
Ajoutez l'appel à cette fonction dans HookRecv APRES l'appel à la fonction :
	PrintDebugMsg("Hook in recv : after");
	WriteData((char *)buf, iRes);
Ajoutez le code suivant pour créer le mutex global utilisé pour synchroniser les accès au fichier de log.
INT WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
	PrintDebugMsg("Entering WinMain");

	// Create a global mutex for log file access synchronisation
	if((GlobalData.hMutexWrite=CreateMutex(NULL, FALSE, "MySniffWriteData")) == NULL)
		return -1;
Ajoutez un champ à la fin de la structure GLOBAL_DATA pour le handle de ce mutex :
	HANDLE	hMutexWrite;
	// WISHMASTER : ADD FIELDS
} GLOBAL_DATA;
Ajoutez enfin sa valeur d'initialisation dans global_data.cpp :
	INVALID_HANDLE_VALUE,		// hMutexWrite
};
Test final de MySniff
Reinjectez une nouvelle instance de Interner Explorer et accédez à Google. Dans le fichier C:\temp\MySniff.txt, vous verrez des traces équivalentes à :
GET / HTTP/1.1
Accept: */*
Accept-Language: fr
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 1.1.4322)
Host: www.google.fr
Connection: Keep-Alive
Cookie: PREF=ID=792307e841722b1e:TM=1131809222:LM=1147724470:IG=2:S=4SuphmCFECMxxWYA

!GET /intl/en_com/images/logo_plain.png HTTP/1.1
Accept: */*
Referer: http://www.google.fr/
Accept-Language: fr
Accept-Encoding: gzip, deflate
If-Modified-Since: Fri, 27 Oct 2006 01:24:32 GMT
User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 1.1.4322)
Host: www.google.fr
Connection: Keep-Alive
Cookie: PREF=ID=792307e841722b1e:TM=1131809222:LM=1147724470:IG=2:S=4SuphmCFECMxxWYA

!
FAQ
Mon appli crash ???
En cas de crash de votre appli, voici une liste de vérifications à faire :
  • Vérifier que l'ordre des fonctions de hook définis par les constantes n'est pas le même que celui de WiShMaster.
  • Régénérer complètement l'exécutable avec WiShMaster ("fast copy" décoché ET "full rebuild" activé)
  • Vérifier que le label "// WISHMASTER : ADD FIELDS" dans la structure GLOBAL_DATA définie dans le fichier "structures_prototype.h" est bien en dernier
  • Me contacter ;)
Accès aux ressources
License
Hooker est un freeware. Vous pouvez baser n'importe quel projet sur ce template (freeware/shareware/payant/...)
La seule condition est que l'outil en question face référence à Hooker (lien vers cette page dans la documentation, ...)

L'ensemble des données (scripts, informations, code source) du projet Hooker sont fournis « tels quels » et sans aucune garantie.
Je ne donne aucune garantie à l’effet que les éléments et le contenu du site sont complets, justes, exacts, exhaustifs, fiables et à jours.
Je ne donne de plus aucune garantie à l’effet que les éléments ou contenus fournis peuvent convenir ou être adaptés à une situation particulière précise par un usager.
Je décline toute responsabilité face à une quelconque perte faisant suite à l'utilisation des outils de ce projet.
Cette archive contient tous les scripts et codes sources nécessaires pour générer ces outils.
Une remarque/un bug ?
Ce projet est réellement en développement intense. N'hésitez donc pas à me faire part de vos remarques/suggestions pour l'améliorer: Benjamin CAILLAT