Introduction :

Premièrement, je voudrais vous dire que je n'ai rien inventé et que je ne revendique aucune exclusivité sur cet article car d'autres ont déjà parlé en mieux de cette technique, donc pas de "Tu as copié sur untel ou unetelle" ou autre insulte. Deuxièmement, cet article s'adresse à ceux qui souhaitent mieux connaitre leur machine et l'architecture des programmes qui s'executent dessus. Des notions de base sur les API, Win32 et l'assembleur peuvent être nécessaire pour bien comprendre. Cela dit tout devrait bien ce passer ! :P

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 :
MASM32\BIN\ML.EXE /c /coff Injection.asm
MASM32\BIN\LINK.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 ? En tout cas, c'est un vrai plaisir pour moi ! lol



Conclusion:

Retrouvez le code de cet exemple ici. J'espère que vous aurez apprécié ce petit article. Envoyez-moi vos feedback si vous voulez d'autre article de ce genre.

À bientôt,