Tutorial sur l'injection de code dans une application windows [provenance]
Rappel de bases :
Qu'est ce que le code ? Dans ce contexte précis le code est une suite d'instructions machine, les instructions machine sont le niveau de programmation le plus bas accessible pour le programmeur lambda, c'est-à-dire c'est le langage que comprend le processeur ! Ce code est généré habituellement par un compilateur puis placer dans un executabe.
Mais alors qu'est ce que c'est que l'injection ? L'injection c'est copier du code machine dans un programme en cours d'execution afin de remplacer ou étendre les fonctions de celui-ci.
Dans quel but ? Le but premier est le contrôle ! Pouvoir contrôler le programme et par extension le système tout entier.
Comment injecter ? Il faut savoir que pour s'executer, un programme doit avant tout être chargé en mémoire. Le système crée un espace mémoire, map le fichier exe dans cet espace puis execute le programme. Pour schématiser, la mémoire est une sorte de bobine, elle possède donc deux extremités et une longueur, le système d'exploitation, quant à lui, gère la bobine et distribue ce fil àchaque application. Le programme, pour se charger, va se répandre le long de ce fil et l'exection se fera le long de celui ci, les instructions étant à la suite les unes des autres. Injecter du code reviendrait à placer des instructions le long du fil d'une application pour que celui ci s'execute.
L'exercice :
Il y a plusieurs techniques pour injecter du code dans une application, la qualité de l'injection dépend de la méthode d'injection du code et d'execution de celui-ci. La plupart utilise les API de windows, cela pose bien sûr le problème de version. Dans notre exemple, nous verrons des API disponibles sur les systèmes Win9x et supérieurs, à l'exception de VirtualAllocFree et VirtualFreeEx qui sont une exclusivité des platformes NT.
Ce que nous allons, faire c'est charger une dll (de notre fabrication) dans une autre application. Pourquoi ? Tout simplement par ce que une fois chargé, nous aurons le contrôle total de cette application. En effet, elle disposera de tout l'espace mémoire de l'application ainsi qu'un accès total à celle ci.
Avant de commencer à coder, il faut savoir quoi injecter ! Comment allons dire à une autre application de charger une dll ? Et bien, nous allons tout simplement appeler "LoadLibrary", cette fonction bien connue charge une dll et pour nous facilité la tache, il faut savoir que le module "Kernel" (c'est la dll qui exporte LoadLibary) est chargé à la même adresse dans toutes les applications, ce qui signifie qu'un pointer vers cette adresse sera valide pour tous les programmes du système !
Le code :
Commençons par nous attaquer à la DLL, cette dll va tout simplement afficher un message lors de son chargement et déchargement pour nous avertir de la réussite de l'injection. Nous allons la realiser en assembleur pour une question de facilité. Voici le code :
MODULE INJECTION.ASM
;========================= ; Assemblé avec Masm32 ;=========================
.386 .model flat, stdcall option casemap :none
include windows.inc include user32.inc include kernel32.inc
includelib user32.lib includelib kernel32.lib
.data
Msg1 db "#Library Loaded" ,0 Msg2 db "#Library Unloaded" ,0
.code
LibMain proc hInstDLL:DWORD, reason:DWORD, unused:DWORD .if reason == DLL_PROCESS_ATTACH invoke MessageBox,NULL,addr Msg1,NULL,MB_OK .elseif reason == DLL_PROCESS_DETACH invoke MessageBox,NULL,addr Msg2,NULL,MB_OK .endif mov eax,1 ret LibMain Endp
End LibMain
Assemblez-le de la manière suivante : MASM32BINML.EXE /c /coff Injection.asm MASM32BINLINK.EXE /SUBSYSTEM:WINDOWS /DLL Injection.obj
Voilà, la première étape est faite. Maintenant, passons au sujet qui nous intéresse : l'injection dans une autre application. Pour l'exemple nous allons injecter la dll dans WordPad.exe. Prochaine étape : capturer le handle de la fenêtre de WordPad, pour cela rien de plus simple que FindWindow :
hWnd2 = FindWindow(vbNullString, "Document - WordPad")
Ensuite on recupère l'id du Process et du Thread de l'application :
ThreadId = GetWindowThreadProcessId(hWnd2, ProcessId) hThread = OpenThread(THREAD_ALL_ACCESS, 0, ThreadId) hProcess = OpenProcess(PROCESS_CREATE_THREAD Or PROCESS_VM_OPERATION Or PROCESS_VM_WRITE Or PROCESS_VM_READ, False, ProcessId)
A partir de là, quoi faire ? Nous disposons des handles de process et thread ce qui va nous permettre de lire et d'écrire dedans. Avant d'écrire, nous allons mettre en pause le programme et récupérer les informations du thread comme par exemple le registre EIP (eip c'est le registre qui pointe les instructions c'est-à-dire l'endroit exact ou le programme vient de se stopper sur notre fameux "fil" de memoire) :
Call SuspendThread(hThread) Call GetThreadContext(hThread, lpContext)
Avant d'écrire, nous allons créer un espace mémoire dans le process de destination pour y écrire notre programme. Cet espace doit être assez grand pour acueillir l'ensemble des données de notre programme :
hVirtual = VirtualAllocEx(hProcess, ByVal 0&, UBound(Data) + 1, MEM_COMMIT, PAGE_READWRITE)
hVirtual est l'adresse (ou l'endroit du fil) sur lequel nous allons écrire. Cette phase utilise un API NT, elle peut être éviter en recherchant à travers la mémoire un espace libre mais cette technique est plus longue et l'article n'en serait que plus compliqué. Nous disposons de tous les accès et de toutes les informations suffisantes pour injecter notre code. Le problème, c'est justement de savoir ce que nous allons injecter. Le but, je vous le rappelle, c'est de charger une dll par le biais de "LoadLibrary". Ce qui donnerait en vb :
Call LoadLibrary("INJECTION.DLL")
Mais ce serait complètement absurde de copier/coller du code vb dans une application. Pensons plutôt en code machine. Que fait le compilateur pour executer cette fonction ? Il empile le nom de la bibliothèque puis appelle la fonction. Il nous faut donc l'adresse de la fonction LoadLibrary, l'adresse de la chaine de caractère "INJECTION.DLL" et enfin l'adresse de l'endroit où le programme s'est stoppé (pour pouvoir continuer l'execution). Construisons notre code...
Récupérer l'adresse de LoadLibrary, rien de plus simple LoadLibrary est à la même adresse dans toutes les applications :
ModHdl = GetModuleHandle("KERNEL32") ProcAddr = GetProcAddress(ModHdl, "LoadLibraryA")
Récupérer l'adresse où s'est stoppé le programme, ça, nous l'avons dans la structure lpContext qui a été rempli par la fonction GetThreadContext :
ResumeAddr = lpContext.Eip
Récupérer l'adresse de la chaine de caractère qui compose le nom de la dll àcharger. Cette adresse, c'est tout simplement l'adresse de base de notre code, soit hVirtual plus la taille de notre code plus un alignement de 32bits pour accelérer le cache des instructions. Soit :
ChaineAddr = hVirutal + CodeSize + Align
Passons au code, il est plutôt simple, il n'utilisera que le registre arithmétique eax :
CODE MACHINE
MNEMONIQUE
B8 XX XX XX XX
MOV EAX, ChaineAddr
50
PUSH EAX
B8 XX XX XX XX
MOV EAX, ProcAddr
FF D0
CALL EAX
B8 XX XX XX XX
MOV EAX, ResumeAddr
FF E0
JMP EAX
Comme vous le constatez, le code ne prend que 20 octets. Nous placerons donc notre chaine de caractère au 32ème octet pour une question d'alignement, et remplirons les xx par les adresses respectives en vb. Cela donnerait :
Dim Data(127) As Byte '-------------------------- Data(0) = &HB8 Addr = hVirtual + 32 CopyMemory Data(1), ChaineAddr, 4 Data(5) = &H50 '-------------------------- Data(6) = &HB8 CopyMemory Data(7), ProcAddr, 4 Data(11) = &HFF Data(12) = &HD0 '-------------------------- Data(13) = &HB8 CopyMemory Data(14), ResumeAddr, 4 Data(18) = &HFF Data(19) = &HE0 '-------------------------- CopyMemory Data(32), ByVal FileName, Len(FileName)
Maintenant, écrivons ce code dans l'application et changons l'adresse d'execution pour que le thread execute notre code :
If WriteProcessMemory(hProcess, ByVal hVirtual, Data(0), UBound(Data) + 1, ByVal 0&) Then Call SetThreadContext(hThread, lpContext) Call ResumeThread(hThread) Call WaitForSingleObject(hThread, 100) MsgBox "Injected!", vbInformation End If
Et voilà, le code a été executé ! Ce n'etait pas si compliqué. Nous venons de placer un code fait-main dans une autre application et nous l'avons executé. N'est-ce pas formidable ?
|