Injection DLL

Sommaire

  • Une DLL quèsaco ?
  • Injection
  • Références

Une DLL quèsaco ?

Une DLL ou une bibliothèque de liens dynamiques contient des fonctions utilitaires, cela présente plusieurs avantages :

  • Lors d’une mise à jour de l’application, seule la dll modifiée seras téléchargée.
  • Il peut y avoir une seule dll pour plusieurs application si celles-ci utilisent les mêmes fonctions.

L’injection de DLL est une technique couramment utilisée par les malwares pour injecter du code dans une application légitime. La charge utile est contenue dans une DLL malveillante.

Dans cette première partie nous allons créer un programme et une dll avec Visual Studio. Commençons par créer une application console en 32 bits :

Le code suivant charge une fonction nommé HelloFromDLL depuis la dll MyDLL.dll. Les fonctions LoadLibraryA et GetProcAddress sont fournies par la winapi (Windows.h). La première charge la dll en mémoire et la deuxième récupère l’adresse de HelloFromDLL.

// Target.cpp : définit le point d'entrée pour l'application console.
//

#include "stdafx.h"
#include 

int _tmain(int argc, _TCHAR* argv[])
{
    FARPROC myFunc = NULL; /* adresse de la fonction */
    HMODULE hMod = LoadLibraryA("MyDLL.dll"); /* Charger la DLL */

    if (hMod != NULL) /* Test si le module est bien chargé */
    {
        myFunc = GetProcAddress(hMod, "HelloFromDLL"); /* Récupérer l'adresse de la fonction */

        printf("===================== Target ===================\n");
        printf("myFunc is at 0x%p\n", myFunc); /* Afficher l'adresse de la fonction */

        if (myFunc != NULL)  /* Test si l'adresse a correctement été résolue*/
            myFunc(); /* Appeler la fonction */
    }
    else /* En cas d'erreur */
        printf("LoadLibraryA failed\n");

    system("pause"); /* pause pour avoir le temps de lire */
    return 0;
}

Maintenant nous allons créer la dll, pour cela il faut réaliser un nouveau projet type Projet Win32 dans la solution.

Dans le projet vous allez trouver un fichier dllmain.cpp, au chargement de la dll (soit au moment de LoadLibrary) c’est DllMain qui sera appelé.

// dllmain.cpp : Définit le point d'entrée pour l'application DLL.
#include "stdafx.h"

BOOL APIENTRY DllMain( HMODULE hModule,
                       DWORD  ul_reason_for_call,
                       LPVOID lpReserved
					 )
{
	switch (ul_reason_for_call)
	{
	case DLL_PROCESS_ATTACH:
	case DLL_THREAD_ATTACH:
	case DLL_THREAD_DETACH:
	case DLL_PROCESS_DETACH:
		break;
	}
	return TRUE;
}

On réalise un fichier d’entête MyDLL.h qui va contenir le prototype de la fonction :

#pragma once

/* Déclaration C de la fonction exportée */
extern "C"
{
    __declspec(dllexport) void HelloFromDLL();
}

Le mot clef __declspec(dllexport) préciser que la fonction sera exportée, extern “C” permet d’exporter les symboles dans le format C.

Renommer les fichiers .cpp en .c pour qu’ils soient pris en compte par un compilateur C et non C++,

Voici son implémentation dans le fichier MyDLL.c :

// MyDLL.cpp : définit les fonctions exportées pour l'application DLL.
//

#include "stdafx.h"
#include 

/* Implémentation de la fonction */
__declspec(dllexport) void HelloFromDLL()
{
    printf("Hello world !\n");
}

Maintenant compiler et tester. Si tout se passe bien vous devriez voir un Hello world! à l’écran.

Injection

Nous allons maintenant créer un programme qui va injecter une dll dans une application quelconque . Pour faire cela on va déclencher l’appel de LoadLibrary dans le processus cible. Les applications sont virtuellement isolées sous Windows, elles possèdent leurs propres espaces d’adressages. Il faudra copier notre nom de dll dans le processus cible afin d’éviter un ACCESS VIOLATION.

Notre fonction va avoir le prototype suivant:

int InjectDLL(char* PathToProcess,char* DLLName)

PathToProcess sera le chemin vers l’exécutable dans lequel on injecteras la dll spécifiée en deuxième argument.

Dans un premier temps il faut démarrer le programme cible. Parmi les multiples procédures que propose l’API Windows on trouve :

BOOL WINAPI CreateProcess(
  _In_opt_    LPCTSTR               lpApplicationName,
  _Inout_opt_ LPTSTR                lpCommandLine,
  _In_opt_    LPSECURITY_ATTRIBUTES lpProcessAttributes,
  _In_opt_    LPSECURITY_ATTRIBUTES lpThreadAttributes,
  _In_        BOOL                  bInheritHandles,
  _In_        DWORD                 dwCreationFlags,
  _In_opt_    LPVOID                lpEnvironment,
  _In_opt_    LPCTSTR               lpCurrentDirectory,
  _In_        LPSTARTUPINFO         lpStartupInfo,
  _Out_       LPPROCESS_INFORMATION lpProcessInformation
);

La version qui utilise des chaînes ANSI se termine par un A (CreateProcessA), et la version unicode par un W. lpApplicationName doit contenir le chemin de l’application. Il faut prévoir deux structures de type STARTUPINFO et PROCESS_INFORMATION. La structure du type STARTUPINFO ne va pas être utilisé, il faut la remplir de zéros. La structure du type PROCESS_INFORMATION recevra les informations sur le processus notamment son handle, qui permet de manipuler l’objet représentant le processus. dwCreationFlags prendra la valeur CREATE_NEW_CONSOLE pour que le processus cible ait sa propre fenêtre console. On passera les valeurs par défaut pour les paramètres restants.

Avec la fonction VirtualAllocEx on alloue de l’espace dans le processus cible.

LPVOID WINAPI VirtualAllocEx(
  _In_     HANDLE hProcess,
  _In_opt_ LPVOID lpAddress,
  _In_     SIZE_T dwSize,
  _In_     DWORD  flAllocationType,
  _In_     DWORD  flProtect
);

Le handle est accessible via le membre hProcess de la structure PROCESS_INFORMATION. Ensuite la taille sera celle de notre chaîne DLLName. Ensuite on écrit le nom de la dll avec WriteProcessMemory.

Maintenant pour déclencher l’appel de la fonction LoadLibrary dans l’application cible, nous allons utiliser la fonction CreateRemoteThread,

HANDLE WINAPI CreateRemoteThread(
  _In_  HANDLE                 hProcess,
  _In_  LPSECURITY_ATTRIBUTES  lpThreadAttributes,
  _In_  SIZE_T                 dwStackSize,
  _In_  LPTHREAD_START_ROUTINE lpStartAddress,
  _In_  LPVOID                 lpParameter,
  _In_  DWORD                  dwCreationFlags,
  _Out_ LPDWORD                lpThreadId
);

Celle-ci prend en paramètre un pointeur vers la routine qui sera exécutée au démarrage du thread lpStartAddress, et un pointeur vers les paramètres de cette routine. Il suffit de récupérer l’adresse de LoadLibraryA qui se trouve dans kernel32.dll qui sera notre routine de démarrage, et de lui donner l’adresse de la mémoire dans laquelle on a mis notre DLLName en arguments.

Voici le code complet de la fonction d’injection,

int InjectDLL(char* PathToProcess, char* DLLName)
{
    //Recupération de l'addresse de LoadLibraryA
    void* pLoadLibrary = (void*)GetProcAddress(GetModuleHandle(L"kernel32"), "LoadLibraryA");
    //Création du processus 
    STARTUPINFOA startupInfo;
    PROCESS_INFORMATION processInfo;
    ZeroMemory(&startupInfo, sizeof(startupInfo));
    if (!CreateProcessA(0, PathToProcess, 0, 0, 1, CREATE_NEW_CONSOLE, 0, 0, &startupInfo, &processInfo)){
        return 0;
    }

    //Allocation dans le processus
    void* pReservedSpace = VirtualAllocEx(processInfo.hProcess, NULL, strlen(DLLName), MEM_COMMIT, PAGE_EXECUTE_READWRITE);
    if (!pReservedSpace){
        return 0;
    }
    //Ecriture dans la mémoire
    if (!WriteProcessMemory(processInfo.hProcess, pReservedSpace, DLLName, strlen(DLLName), NULL)){
        return 0;
    }
    //Creation du thread distant
    HANDLE hThread = CreateRemoteThread(
        processInfo.hProcess,
        NULL,
        0,
        (LPTHREAD_START_ROUTINE)pLoadLibrary,
        pReservedSpace,
        0,
        NULL
    );

    if (!hThread){
        return 0;
    }

    WaitForSingleObject(hThread, INFINITE); // Attendre la fin du thread
    VirtualFreeEx(processInfo.hProcess, pReservedSpace, strlen(DLLName), MEM_COMMIT); // Libérer la mémoire précédemment allouée 
    return 1;
}

On crée une dll pour l’injection,

// dllmain.cpp : Définit le point d'entrée pour l'application DLL.
#include "stdafx.h"

BOOL APIENTRY DllMain( HMODULE hModule,
                       DWORD  ul_reason_for_call,
                       LPVOID lpReserved
					 )
{
	switch (ul_reason_for_call)
	{
	case DLL_PROCESS_ATTACH:
               break;
	case DLL_THREAD_ATTACH:
               MessageBoxA(NULL,"Coucou","Coucou",0);
               printf("INJECTED\n");
	case DLL_THREAD_DETACH:
	case DLL_PROCESS_DETACH:
		break;
	}
	return TRUE;
}

Et hop le tour est joué ;) . Naturellement une dll en 32 bits ne fonctionnera que sur des applications en 32 bits.

Références