Sous-classement de fenêtre d'un autre process par injection dll

Description

Voici un code source montrant comment sous-classer une fenêtre appartenant à un autre process. La technique de l'injection de code par dll est utilisée. Le but de ce sous-classement est de pouvoir intercepter les messages destinés à n'importe quelle fenêtre cible afin de les traiter de manière personnalisée.
L'injecteur est sous forme de boite de dialogue disposant d'un combobox qui contiendra les noms de toutes les fenêtres visibles à l'écran. Le bouton "Injecter" permet d'injecter et exécuter le code de notre dll dans le process de la fenêtre choisie. Le HWND de cette dernière est transmis via le presse-papier au DllMain de notre dll en utilisant un format privé. Un bouton "Actualiser" permet de rafraichir le contenu du combobox.
La dll contient deux fonctions: Le DllMain et notre procédure de sous-classement. Dans le DllMain, l'adresse de la procédure originale de la fenêtre à sous-classer est sauvegardée dans une propriété assignée pour l'occasion à cette fenêtre grâce à SetProp(). GetProp() permet de retrouver cette adresse originale dans la procédure de sous-classement afin d'y aiguiller les messages qu'on ne désire pas traiter. Ainsi, chaque fenêtre retrouve sa propre procédure originale si le message qui lui parvient n'est pas capturé par notre procédure de sous-classement. Dans cet exemple, le message WM_SYSCOMMAND dont wParam vaut SC_CLOSE est intercepté pour afficher un petit message prouvant que le sous-classement est effectif chaque fois qu'on décide de fermer la fenêtre sous-classée. Libre à vous de modifier le code pour traiter les messages que vous voulez.
Ce code source se compose de deux projets: celui de l'injecteur et celui de la dll. Cette dernière est incluse en ressource binaire de l'exécutable injecteur qui l'extrait dans son dossier s'il ne la trouve pas.
Les deux petits projets ont été faits sous Visual C/C++ 2005 et sont facilement adaptables. Celui de la dll est en pur C.
Il est à noter que par souci de clarté du code source, généralement les valeurs de retour des fonctions ne sont pas traitées.
Pour tester l'injecteur, renommez-le en Injecteur.exe. Des tests ont été faits sans aucun problème sur Windows XP, Vista et 7.
Voilà, j'espère n'avoir rien oublié.
Toutes les remarques, questions et commentaires sont les bienvenus.

Source / Exemple :


//********************** Code de l'injecteur **********************
#include <windows.h>

DWORD Inject(HWND hwndclipboard, HWND hwndcible)
{
	char dllpath[MAX_PATH];
	// Obtenir le chemin complet de notre exécutable:
	GetModuleFileName(0,dllpath,MAX_PATH);
	// Remplacer le nom de l'exécutable par le nom de la dll:
	lstrcpy(strrchr(dllpath,'\\')+1,"wndprocdll.dll");
	// Vérifier l'existence de la dll dans le dossier de l'exe:
	DWORD attrib=GetFileAttributes(dllpath);
	// Extraire la dll depuis les ressouces de l'exe si elle n'existe pas:
	if(attrib==INVALID_FILE_ATTRIBUTES)
	{
		// Trouver la ressource:
		HRSRC hbinresource=FindResource(0,"IDB_DLL","BINARY");
		// Déterminer sa taille:
		DWORD dwtaille=SizeofResource(0,hbinresource);
		// Charger la ressource en mémoire:
		HGLOBAL hResource=LoadResource(0,hbinresource);
		// Obtenir un pointeur sur cette zone mémoire:
		char* pbuf=(char*)LockResource(hResource);
		// Créer le fichier destination:
		HANDLE hFichier=CreateFile(dllpath,GENERIC_WRITE,0,0,CREATE_ALWAYS,FILE_ATTRIBUTE_NORMAL,0);
		// S'assurer que le fichier a bien été créé:
		if(hFichier!=INVALID_HANDLE_VALUE)
		{
			// Copier la ressource dans le fichier:
			DWORD dwecrits;
			WriteFile(hFichier,pbuf,dwtaille,&dwecrits,0);
			// Fermer le fichier:
			CloseHandle(hFichier);
		}
		// Libérer la ressource:
		UnlockResource(hResource);
		FreeResource (hResource);
	}
	// Obtenir l'identificateur du process auquel appartient la fenêtre:
	DWORD dwpid;
	DWORD threadid=GetWindowThreadProcessId(hwndcible,&dwpid);
	// Ouvrir ce process:
	HANDLE hProcess=OpenProcess( PROCESS_ALL_ACCESS, 0,dwpid);
	// Obtenir la longueur du chemin complet de notre dll:
	DWORD dwpathlen=lstrlen(dllpath);
	// Allouer de la mémoire dans ce process pour y stocker le chemin de notre dll:
	char* pmem=(char*)VirtualAllocEx(hProcess,0,dwpathlen+1,MEM_COMMIT,PAGE_READWRITE);
	// Copier le chemin de notre dll vers la mémoire allouée dans le process cible:
	BOOL bret=WriteProcessMemory(hProcess,(LPVOID) pmem,(LPVOID) dllpath, dwpathlen+1, NULL);
	// Obtenir le HMODULE de kernel32.dll:
	HMODULE hkernel32=GetModuleHandle("Kernel32");
	// Obtenir l'adresse de la fonction LoadLibraryA de kernel32.dll:
	PTHREAD_START_ROUTINE pfnThreadRtn=(PTHREAD_START_ROUTINE)GetProcAddress(hkernel32, "LoadLibraryA");
	// Ouvrir le presse-papier:
	OpenClipboard(hwndclipboard);
	// Mettre le HWND de la fenêtre cible dans le presse-papier:
	SetClipboardData(CF_PRIVATEFIRST ,hwndcible);// Format privé
	// Fermer le presse-papier:
	CloseClipboard();
	// Créer et lancer un thread distant dans le process cible:
	HANDLE hThread= CreateRemoteThread(hProcess, NULL, 0,pfnThreadRtn, pmem, 0, NULL);
	// Attendre que le thread soit terminé:
	WaitForSingleObject(hThread, INFINITE);
	// Libérer la mémoire allouée précédemment:
	VirtualFreeEx(hProcess, pmem, 0, MEM_RELEASE);
	// Fermer les handles:
	CloseHandle(hThread);
	CloseHandle(hProcess);
	return 0;
}

// Procédure d'énumération des fenêtres et de remplissage du combobox:
BOOL CALLBACK EnumWindowsProc(HWND hwnd,LPARAM lParam)
{
	static		char buffer[MAX_PATH];
	// S'assurer que la fenêtre est visible:
	if(IsWindowVisible(hwnd))
	{
		// Récupérer le HWND de notre combobox depuis lParam:
		HWND hCombo=(HWND)lParam;
		// S'assurer que la fenêtre a un titre et qu'il ne s'agit pas de celle de notre injecteur:
		if(GetWindowTextLength(hwnd) && hwnd!=GetParent(hCombo))
		{
			// Obtenir le nom de la classe de fenêtre:
			GetClassName(hwnd,buffer,MAX_PATH);
			// S'assurer que le nom de classe est différent de "progman":
			if(lstrcmpi(buffer,"progman"))
			{
				// Obtenir le texte du titre de la fenêtre:
				GetWindowText(hwnd,buffer,MAX_PATH);
				// Ajouter ce texte comme élément au combobox:
				SendMessage(hCombo,CB_INSERTSTRING,0,(LPARAM)buffer);
				// Mettre le HWND de la fenêtre comme valeur associée à l'élément:
				SendMessage(hCombo,CB_SETITEMDATA,0,(LPARAM)hwnd);
			}
		}
	}
	return TRUE;
}

// Procédure de notre boite de dialogue:
BOOL CALLBACK DlgProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	static HWND hCombo,hFermer,hInjecter,hActualiser;
	switch(message)
	{

	case WM_INITDIALOG:
		{
			// Définir le titre de notre boite de dialogue:
			SetWindowText(hwnd,"Injecteur");
			// Créer les contrôles:
			CreateWindowEx(0,"static","Liste des fenêtres :",WS_CHILD | WS_VISIBLE,20,10,360,20,hwnd,0,0,0);
			hCombo=CreateWindowEx(0,"combobox",0,WS_CHILD | WS_VISIBLE | CBS_DROPDOWNLIST | WS_VSCROLL,20,28,360,150,hwnd,0,0,0);
			hActualiser=CreateWindowEx(0,"button","Actualiser",WS_CHILD | WS_VISIBLE ,20,170,80,20,hwnd,0,0,0);
			hInjecter=CreateWindowEx(0,"button","Injecter",WS_CHILD | WS_VISIBLE | WS_DISABLED ,160,170,80,20,hwnd,0,0,0);
			hFermer=CreateWindowEx(0,"button","Fermer",WS_CHILD | WS_VISIBLE ,300,170,80,20,hwnd,0,0,0);
			// Enumérer les fenêtres en remplissant le combobox:
			EnumWindows(EnumWindowsProc,(LPARAM)hCombo);
			// Obtenir la police par défaut des boites de dialogue:
			HFONT hguifont=(HFONT)GetStockObject(DEFAULT_GUI_FONT);
			// Appliquer cette police à nos contrôles:
			HWND child=0;
			while(child=FindWindowEx(hwnd,child,0,0))SendMessage(child,WM_SETFONT,(WPARAM)hguifont,0);
		}
		break;

	case WM_COMMAND:
		// Dégriser le bouton "Injecter" si un élément du combobox a été sélectionné:
		if(HIWORD(wParam)==CBN_SELENDOK) EnableWindow(hInjecter,1);
		// Clic sur "Injecter":
		if((HWND)lParam==hInjecter)
		{
			// Obtenir l'index de la séléction courante du combobox:
			int cursel=(int)SendMessage(hCombo,CB_GETCURSEL,0,0);
			// Récupérer le HWND associé comme valeur à l'élément sélectionné:
			HWND hwndcible=(HWND)SendMessage(hCombo,CB_GETITEMDATA,cursel,0);
			// S'assurer que la fenêtre n'a pas déjà été sous-classée:
			if(GetProp(hwndcible,"PROP_WNDPROC"))
			{
				MessageBox(hwnd,"La fenêtre choisie a déjà été sous-classée.","Injecteur",0);
				return 0;
			}
			// Lancer la fonction d'injection si la fenêtre est valide:
			if(IsWindow(hwndcible))Inject(hwnd,hwndcible);
			// Sinon afficher un message indiquant que le HWND n'est pas valide:
			else MessageBox(hwnd,"Le HWND de la fenêtre choisie n'est plus valide.","Injecteur",0);	
			return 0;
		}
		// Clic sur "Actualier":
		if((HWND)lParam==hActualiser)
		{
			// Vider le combobox:
			SendMessage(hCombo,CB_RESETCONTENT,0,0);
			// Griser le bouton "Injecter":
			EnableWindow(hInjecter,0);
			// Enumérer les fenêtres en remplissant le combobox:
			EnumWindows(EnumWindowsProc,(LPARAM)hCombo);
			return 0;
		}
		// Fermer la boite de dialogue si clic sur "Fermer":
		if((HWND)lParam==hFermer)SendMessage(hwnd,WM_CLOSE,0,0);
		break;

	case WM_CLOSE:
		// Détruire la boite de dialogue:
		EndDialog(hwnd,0);
		break;
	default:
		break;
	}
	return 0;
}
int APIENTRY WinMain (HINSTANCE hinst, HINSTANCE hprev, LPSTR szcmd, int ishow)
{
	// Allouer de la mémoire pour notre DIALOG TEMPLATE:
	LPDLGTEMPLATE lpdt=(LPDLGTEMPLATE)GlobalAlloc(GPTR,512);
	// Définir les dimensions de notre boite dedialogue:
	lpdt->cx=200; lpdt->cy=100;
	// Définir les styles de notre boite de dialogue:
	lpdt->style=DS_CENTER | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX;
	// Lancer notre boite de dialogue:
	DialogBoxIndirectParam(GetModuleHandle(0),lpdt,0,(DLGPROC)DlgProc,0);
	// Libérer la mémoire allouée pour notre DIALOG TEMPLATE:
	GlobalFree(lpdt);
	return 0;
}
//***********************************************************************

//*********************** Code de la DLL ********************************
#include <windows.h>

// Notre procédure de sous-classement:
LRESULT CALLBACK MyWndProc(HWND hwnd,UINT message, WPARAM wParam, LPARAM lParam)
{
	WNDPROC OldWndProc;
	// Récupérer l'adresse de la procédure originale depuis la propriété "PROP_WNDPROC" assignée à la fenêtre:
	OldWndProc=(WNDPROC)GetProp(hwnd,"PROP_WNDPROC");
	switch(message)
	{
	case WM_SYSCOMMAND:
		if(wParam==SC_CLOSE)
		{
			// Afficher le message prouvant le sous-classement de la fenêtre:
			MessageBox(hwnd,"Ce message prouve que la fenêtre a bien été sous-classée.","Sous-Classement par injection de code.",0);
		}
		break;
	case WM_NCDESTROY:
		// Retirer la propriété "PROP_WNDPROC" préalablement assignée à la fenêtre sous-classée:
		RemoveProp(hwnd,"PROP_WNDPROC");
		break;
	default:
		break;
	}
	// Appeler la procédure originale:
	return CallWindowProc(OldWndProc,hwnd,message,wParam, lParam);
}

// La fonction d'entrée DllMain:
int APIENTRY DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved)
{
	if (dwReason == DLL_PROCESS_ATTACH)
	{
		HWND hwnd;
		// Ouvrir le presse-papier:
		OpenClipboard(0);
		// récupérer le HWND de la fenêtre cible transmis par le programme injetceur:
		hwnd=(HWND)GetClipboardData(CF_PRIVATEFIRST);
		// Fermer le presse-papier:
		CloseClipboard();
		// S'assurer que le HWND est valide et que la propriété "PROP_WNDPROC" ne lui est pas assignée:
		if(IsWindow(hwnd) && !GetProp(hwnd,"PROP_WNDPROC"))
		{
			WNDPROC OldWndProc;
			// Récupérer l'adresse de la procédure originale de la fenêtre cible:
			OldWndProc=(WNDPROC)GetWindowLong(hwnd,GWL_WNDPROC);
			//Sauvegarder cette adresse dans la propriété "PROP_WNDPROC" à assigner à la fenêtre: 
			SetProp(hwnd,"PROP_WNDPROC",(HANDLE)OldWndProc);
			// Remplacer la procédure originale par la notre:
			SetWindowLong(hwnd, GWLP_WNDPROC, (LONG)MyWndProc);
		}
	}
	return 1; 
}
//**********************************************************************

Conclusion :


Téléchargez le zip pour voir les deux projets.

Codes Sources

A voir également

Vous n'êtes pas encore membre ?

inscrivez-vous, c'est gratuit et ça prend moins d'une minute !

Les membres obtiennent plus de réponses que les utilisateurs anonymes.

Le fait d'être membre vous permet d'avoir un suivi détaillé de vos demandes et codes sources.

Le fait d'être membre vous permet d'avoir des options supplémentaires.