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

Soyez le premier à donner votre avis sur cette source.

Vue 6 901 fois - Téléchargée 486 fois

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

Ajouter un commentaire

Commentaires

racpp
Messages postés
1910
Date d'inscription
vendredi 18 juin 2004
Statut
Modérateur
Dernière intervention
14 novembre 2014
11
Franchement, je ne me suis jamais penché sur le sujet mais je pense que si on a vraiment besoin de contrer ces techniques, on a la possibilité de faire un hook API en kernel mode. On pourra intercepter et détourner pour notre processus LdrLoadDll() ou même CreateRemoteThread(). Ce n'est qu'une piste et il y en aura sûrement d'autres.
Pistol_Pete
Messages postés
1054
Date d'inscription
samedi 2 octobre 2004
Statut
Membre
Dernière intervention
9 juillet 2013
6
Merci pour ta réponse.
J'ai regardé le code et franchement, je ne vois absolument pas comment contrer cette technique.
Comment tu t'y prendrais?
Mettons qu'au moment de l'attache de la dll, on face une seg fault pour planter le programme cible.
A+
racpp
Messages postés
1910
Date d'inscription
vendredi 18 juin 2004
Statut
Modérateur
Dernière intervention
14 novembre 2014
11
Microsoft a prévu toutes ces fonctions juste pour les besoins de débogage. C'est précisé par MSDN. La technique du CreateRemoteThread() est une idée de Jeffrey Richter qui l'a bien détaillée dans son livre "Programming Applications for Microsoft Windows" publié à la fin des années 90 par Microsoft. Il y a également parlé du sous-classement de fenêtre d'un autre processus mais sans fournir d'exemple de code.
Il est toujours très utile de comprendre comment les choses fonctionnent. Ainsi, si on a besoin de contrer ces techniques dans nos applications, on sait au moins ce qu'on doit faire.
Pistol_Pete
Messages postés
1054
Date d'inscription
samedi 2 octobre 2004
Statut
Membre
Dernière intervention
9 juillet 2013
6
Salut

C'est une très bonne source et très bien commenté comme toujours!
Bravo.
J'ai néanmoins une question plus haut niveau. A quoi peut servir cette technique mise à part l'espionnage et le hackage...
Windows ne devrait t'il pas justement luter contre les injections de code, de dll, au lieu de fournir des API fonctionnelles pour le faire.
Je prend juste l'exemple de la fonction CreateRemoteThread; a t'on vraiment besoin de créer un thread dans un process qui nous appartient pas...
Merci.
racpp
Messages postés
1910
Date d'inscription
vendredi 18 juin 2004
Statut
Modérateur
Dernière intervention
14 novembre 2014
11
Je viens de faire une petite investigation et, effectivement, la fenêtre du visualistaur appartient bien au process explorer.exe. C'est lui qui la crée. Elle n'a donc pas son propre process. Ceci explique pourquoi la dll ne s'injecte que la première fois. Les tentatives suivantes
échouent car la dll est déjà chargée dans le process explorer.exe. Puisque le sous-classement se fait dès le chargement dans DllMain(), cette dernière ne sera plus appelée et donc la fenêtre cible ne sera pas sous-classée. Il vaut donc mieux éviter ce genre de fenêtre et ne faire des tests qu'avec celles ayant leurs propres processus. Une fois fermées, leurs processus seront terminés et notre dll se trouvera libérée.

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.