[x86] Injection DLL dans un process windows

Voir le sujet précédent Voir le sujet suivant Aller en bas

[x86] Injection DLL dans un process windows

Message par Akabane87 le Mer 21 Jan - 13:37

Salut tout le monde   cheers

Comme je me fais un petit peu chier et que j'ai mon projet de hack d'un jeu dont je tairai le nom sous la main, j'en profite pour faire un micro tuto sur l'injection dll.

Vu que le code est très bien commenté (en anglais... donc désolé pour les anglophobes  Evil or Very Mad ), je ne vous ferai pas l’affront d'expliquer les lignes de code une à une, et posterai plutôt le code entier de chaque fichier directement.


Pour faire les choses proprement, il vous faudra créer 2 projets distincts : l'injecteur sous forme d'un simple programme (dans mon cas en console vu qu'il ne fait qu'afficher des choses et checker en permanence les process en cours pour injecter les dll à injecter), et la ou les dll à injecter si vous voulez créer votre DLL à injecter.


L'injecteur :

Cet injecteur va simplement récupérer une liste de noms de dlls à injecter depuis un fichier texte dans un process cible dont le nom est directement hardcodé dans l'injecteur (oui j'avoue j'ai eu la flemme de l'exposer dans un fichier texte lui aussi  Embarassed ).

Puis il va checker en permanence tous les process du PC en quête de l'anneau unique...  Rolling Eyes  comment ça je déraille ?! ........... en quête du process dont le nom est hardcodé bien sûr (bande de moules  Evil or Very Mad ).

Une fois ce process trouvé, il va vérifier qu'il ne contient pas les dll à injecter et si ce n'est pas le cas va injecter chacune d'elle dans le process (attention piqûrophobes  s'abstenir  Shocked ). Une fois cette sale besogne effectuée, il recommencera à scruter les process en quête d'un nouveau process pas encore injecté.

Quand on ferme le programme, il éjecte automatiquement toutes les dll injectées dans tous les process ayant été injectés.


Simple non  Laughing ? Alors c'est parti !!!

(Comme c'est du code utilisant beaucoup de fonctions de l'API windows j'ai du utiliser les types barbares comme DWORD LPSTR, ect... Veuillez me pardonner pour ces barbarismes comme nous pardonnons aussi aux codeurs qui nous ont offensé et... ).

Code:
#include <iostream>
#include <windows.h>
#include <WindowsX.h>
#include <tchar.h>
#include <malloc.h>
#include <direct.h>
#include <TlHelp32.h>
#include <string>
#include <vector>
#include <algorithm>
 
using namespace std;
 
const char* const ProcessName = "TestHook.exe";
 
std::vector<DWORD> ProcessList;
 
// callback to eject every dll injected in every process before exiting the programm
void OnExit();
 
// get a process id of any process named like ProcessName and not already managed by our code (returns 0 if none)
DWORD FindProcessByName(const std::string& processName)
{
    HANDLE hProcessSnap;
    PROCESSENTRY32 pe32;
 
    // Take a snapshot of all processes in the system.
    hProcessSnap = CreateToolhelp32Snapshot( TH32CS_SNAPPROCESS, 0 );
    if( hProcessSnap == INVALID_HANDLE_VALUE )
    {
        return( FALSE );
    }
 
    // Set the size of the structure before using it.
    pe32.dwSize = sizeof( PROCESSENTRY32 );
 
    // Retrieve information about the first process,
    // and exit if unsuccessful
    if( !Process32First( hProcessSnap, &pe32 ) )
    {
        CloseHandle( hProcessSnap );          // clean the snapshot object
        return( FALSE );
    }
 
    // Now run the snapshot of processes
    do
    {
        // if the process has the target name and is not already injected, add it to the injection list
        if ( processName.compare(pe32.szExeFile) == 0 &&
            std::find(ProcessList.begin(), ProcessList.end(), pe32.th32ProcessID) == ProcessList.end() )
        {
            CloseHandle(hProcessSnap);
            ProcessList.push_back(pe32.th32ProcessID);
            return pe32.th32ProcessID;
        }
    } while( Process32Next( hProcessSnap, &pe32 ) );
 
    CloseHandle( hProcessSnap );
    return( 0 );
}
 
// inject a dll named by fnDll inside the process defined by processID
BOOL InjectLibrary(DWORD processID, const char *fnDll)
{
    HANDLE hProcess = OpenProcess(
        PROCESS_QUERY_INFORMATION | PROCESS_CREATE_THREAD | PROCESS_VM_OPERATION  | PROCESS_VM_WRITE,
        FALSE, processID);
    if (hProcess == INVALID_HANDLE_VALUE)
    {
        return false;
    }
 
    BOOL success = FALSE;
    HANDLE hThread = NULL;
    char *fnRemote = NULL;
    FARPROC procLoadLibraryA = NULL;
 
    size_t lenFilename = strlen(fnDll) + 1;
 
    /* Allocate space in the remote process */
    fnRemote = (char *) VirtualAllocEx(hProcess, NULL, lenFilename, MEM_COMMIT, PAGE_READWRITE);
 
    if(fnRemote)
    {
        /* Write the filename to the remote process. */
        if(WriteProcessMemory(hProcess, fnRemote, fnDll, lenFilename, NULL))
        {
            /* Get the address of the LoadLibraryA function */
            procLoadLibraryA = GetProcAddress(GetModuleHandle("Kernel32"), "LoadLibraryA");
            hThread = CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE) procLoadLibraryA, fnRemote, 0, NULL);
            if(hThread != INVALID_HANDLE_VALUE)
            {
                WaitForSingleObject(hThread, INFINITE);
                success = TRUE;
            }
            else
            {
                std::cout << "CreateRemoteThread:" << GetLastError() <<std::endl;
            }
        }
        else
        {
            std::cout << "WriteProcessMemory:" << GetLastError() <<std::endl;
        }
        VirtualFreeEx(hProcess, fnRemote, 0, MEM_RELEASE);
    }
 
    CloseHandle(hProcess);
 
    return success;
}
 
// eject a dll named by fnDll outside the process defined by processID
BOOL EjectLibrary(DWORD processID, const char *fnDll)
{
    HANDLE hProcess = OpenProcess(
        PROCESS_QUERY_INFORMATION | PROCESS_CREATE_THREAD | PROCESS_VM_OPERATION  | PROCESS_VM_WRITE,
        FALSE, processID);
    if (hProcess == INVALID_HANDLE_VALUE)
    {
        std::cout << "OpenProcess:" <<GetLastError() <<std::endl;
        return false;
    }
 
    BOOL success = FALSE;
    HANDLE hSnapshot = NULL;
    HANDLE hThread = NULL;
    FARPROC procFreeLibrary = NULL;
 
    hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, processID);
 
    if(hSnapshot != INVALID_HANDLE_VALUE)
    {
        MODULEENTRY32 me = { sizeof(me) };
        BOOL isFound = FALSE;
        BOOL isMoreMods = Module32First(hSnapshot, &me);
        for(; isMoreMods; isMoreMods = Module32Next(hSnapshot, &me))
        {
            isFound = (_strcmpi(me.szModule, fnDll) == 0 || _strcmpi(me.szExePath, fnDll) == 0);
            if(isFound)
                break;
        }
 
        if(isFound)
        {
            /* Get the address of the LoadLibraryA function */
            procFreeLibrary = GetProcAddress(GetModuleHandle("Kernel32"), "FreeLibrary");
            hThread = CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE) procFreeLibrary, me.modBaseAddr, 0, NULL);
            if(hThread != INVALID_HANDLE_VALUE)
            {
                WaitForSingleObject(hThread, INFINITE);
                success = TRUE;
            }
            else
            {
                std::cout << "CreateRemoteThread:" <<GetLastError() <<std::endl;
            }
        }
        else
        {
            std::cout << "dll not found !" <<std::endl;
        }
 
        CloseHandle(hSnapshot);
    }
    else
    {
        std::cout << "CreateToolhelp32Snapshot:" <<GetLastError() <<std::endl;
    }
 
    return success;
}
 
// check if the process defined by processID owns the dll named by fnDLL
BOOL HasLibrary(DWORD processID, const char *fnDll)
{
    HANDLE hProcess = OpenProcess(
        PROCESS_QUERY_INFORMATION | PROCESS_CREATE_THREAD | PROCESS_VM_OPERATION  | PROCESS_VM_WRITE,
        FALSE, processID);
    if (hProcess == INVALID_HANDLE_VALUE)
    {
        return false;
    }
 
    BOOL success = FALSE;
    HANDLE hSnapshot = NULL;
    HANDLE hThread = NULL;
    FARPROC procFreeLibrary = NULL;
 
    hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, processID);
 
    BOOL isFound = FALSE;
 
    if(hSnapshot != INVALID_HANDLE_VALUE)
    {
        MODULEENTRY32 me = { sizeof(me) };
        BOOL isMoreMods = Module32First(hSnapshot, &me);
        for(; isMoreMods; isMoreMods = Module32Next(hSnapshot, &me))
        {
            isFound = (_strcmpi(me.szModule, fnDll) == 0 || _strcmpi(me.szExePath, fnDll) == 0);
        }
 
        CloseHandle(hSnapshot);
    }
 
    CloseHandle(hProcess);
 
    return isFound;
}
 
// a simple and ugly file reader get the dlls' name to inject inside our processes
std::vector< std::string > dllNames;
bool ReadDllListFile(const char* const path)
{
    dllNames.clear();
 
    FILE* file = fopen(path, "r");
    if (!file)
        return false;
 
    char line[2048];
 
    while (fgets(line, 2048, file) != NULL)
    {
        std::string name = line;
        if(name != "")
        {
            size_t pos = name.find(".dll");
            if(pos != std::string::npos)
                dllNames.push_back(name.substr(0, pos+4));// fuck this "\n" : have to strip it...
        }
    }
 
    fclose(file);
 
    return !dllNames.empty();
}
 
int _tmain(int argc, _TCHAR* argv[])
{
    int ret = atexit(OnExit);// registed our callback at exit call
 
    // first of all retrieve our dlls' names
    if(!ReadDllListFile("dlls.txt"))
    {
        printf("No dll to inject found in \"dlls.txt\" ! Exiting...\n");
 
        Sleep(3000);
        return 0;
    }
 
    // then loop foreveeeeeeeeeeeer...
    while(1)
    {
        DWORD processID = 0;
        std::cout << "Waiting for new game start..." << std::endl;
        while(processID == 0)// no new process to inject ? Loop until we get a new one
        {
            processID = FindProcessByName(ProcessName); //Get PID of Process
            if(processID)// we found a process not already owned : check it hasn't already the dlls we want to inject
            {
                BOOL hasLibs = true;
                for(UINT i = 0; i < dllNames.size(); ++i)
                {
                    char srcDll[512]; //dll in the same directory as the injection.exe
                    _getcwd(srcDll, 512);
                    strcat_s(srcDll, "\\");
                    strcat_s(srcDll, dllNames<span style="font-style: italic">.c_str());
 
                    if(!HasLibrary(processID, srcDll))// missing dll
                    {
                        hasLibs = FALSE;
                        break;
                    }
                }
                if(hasLibs)// already have all the dlls : ignore it
                    processID = 0;
            }
 
            Sleep(100);// no need to burn the computer for a simple process check : 0.1s delay between 2 check is largely enough...
        }
 
        // we found a process to inject...
        std::cout << "Game found : PID = " << processID << std::endl;
 
        // inject each dll if needed
        for(UINT i = 0; i < dllNames.size(); ++i)
        {
            char srcDll[512]; //dll in the same directory as the injection.exe
            _getcwd(srcDll, 512);
            strcat_s(srcDll, "\\");
            strcat_s(srcDll, dllNames[i].c_str());
 
            if(!HasLibrary(processID, srcDll))
            {
                if (InjectLibrary(processID, srcDll))// try inject !!!
                {
                    std::cout << "Injection successful : " << srcDll << std::endl;
                }
                else
                {
                    std::cout << "Injection failed : " << srcDll << std::endl;
                }
 
            }
            else
            {
                std::cout << "Dll " << srcDll << " already injected !" << std::endl;
            }
        }
        std::cout << std::endl;
 
        Sleep(100);// extra delay cause we deserve it xD
    }
 
    return 0;
}
 
 
// Our exit callback
void OnExit()
{
    for(UINT j = 0; j < ProcessList.size(); ++j)// for each process,
    {
        DWORD processID = ProcessList[j];
 
        for(UINT i = 0; i < dllNames.size(); ++i)// for each dll injected,
        {
            char srcDll[512]; //dll in the same directory as the injection.exe
            _getcwd(srcDll, 512);
            strcat_s(srcDll, "\\");
            strcat_s(srcDll, dllNames[i].c_str());
 
            if (EjectLibrary(processID, srcDll))// try to eject it !!!
            {
                //std::cout << "Ejection successful : " << srcDll << std::endl;
                printf("Ejection successful : %s\n", srcDll);// for some unknown reason lines are not displayed at exit with std::cout... so let's use printf instead.
            }
            else
            {
                //std::cout << "Ejection failed : " << srcDll << std::endl;
                printf("Ejection failed : %s\n", srcDll);
            }
        }
    }
 
    //std::cout << "All dlls ejected from running targets. Now Exiting ..." << std::endl;
    printf("All dlls ejected from running targets. Now Exiting ...\n");
 
    Sleep(3000);
}
avatar
Akabane87
Admin

Messages : 45
Date d'inscription : 30/07/2010

Voir le profil de l'utilisateur http://dreamraiser.forumactif.com

Revenir en haut Aller en bas

Re: [x86] Injection DLL dans un process windows

Message par Akabane87 le Mer 21 Jan - 13:39

La DLL :

Pour cette partie c'est beaucoup plus simple car le strict minimum se résume à créer un point d'entrée et gérer les events DLL_PROCESSS_ATTACH et DLL_PROCESS_DETACH. Pour ensuite faire ce que bon vous semblera. Par exemple démarrer un nouveau thread depuis le process lui même afin de faire des choses en asynchrone, ou alors aller modifier le code du process lui même dans la ram afin de lui faire faire autre chose (un hook en somme...).


Voici donc le code minimal de départ pour faire tout ça :
Code:
#define _CRT_SECURE_NO_WARNINGS
#include <Windows.h>
#include <cstdio>
#include <time.h>


DWORD WINAPI HookThread();
DWORD WINAPI UnhookThread();
void add_log(char* format, ...);

HANDLE tmpHandle = NULL;

BOOL WINAPI DllMain(HINSTANCE hinstDll,DWORD Reason,LPVOID Reserved)
{
   switch(Reason)
   {
   case DLL_PROCESS_ATTACH:
      add_log("==========LOG START==========");
      add_log("DLL Attached");
      add_log("Creating Thread...");
      tmpHandle = CreateThread(0, 0, (LPTHREAD_START_ROUTINE)&HookThread, 0, 0, 0);// let's use a thread as an example for this one...
      if (!tmpHandle)
      {
         add_log("ThreadCreation Failed!");
      }
      else
      {
         add_log("ThreadCreation Success!");
      }
      break;
   case DLL_PROCESS_DETACH:
      UnhookThread();
      add_log("DLL Detached");
      add_log("==========LOG END==========\n\n\n");
      break;
   }
   return 1;
}

DWORD WINAPI HookThread(void)
{
   // Do some hook stuff

   return S_OK; // exit current thread
}

DWORD WINAPI UnhookThread(void)
{
   // Do some unhook stuff

   return S_OK; // exit current thread
}

void add_log(char* format, ...)
{
   HANDLE filehandle;
   DWORD dwReadBytes;
   char buffer[2048];
   char writebuffer[2048];
   va_list args;
   va_start(args, format);
   vsprintf (buffer, format, args);
   filehandle = CreateFile(L"Log.txt", GENERIC_WRITE, 0, 0, OPEN_ALWAYS, 0, 0);
   SetFilePointer(filehandle, 0, 0, FILE_END);
   char date[18];
   _strdate(date);
   date[8] = ' ';
   _strtime(date+9);
   sprintf_s(writebuffer, 2048, "Log Added (%s): %s\r\n", date, buffer);
   WriteFile(filehandle, writebuffer, strlen(writebuffer), &dwReadBytes, 0);
   CloseHandle(filehandle);
}


Bon maintenant que nous avons les bases pour injecter une dll dans une programme cible, nous allons attaquer la partie compliquée : le hook Twisted Evil . Accrochez vous les amis Razz !
avatar
Akabane87
Admin

Messages : 45
Date d'inscription : 30/07/2010

Voir le profil de l'utilisateur http://dreamraiser.forumactif.com

Revenir en haut Aller en bas

Re: [x86] Injection DLL dans un process windows

Message par Akabane87 le Mer 21 Jan - 13:42

Les (des ?) techniques de hook :

Commençons par la théorie et je dirais même le bon sens (paysan). Supposons que nous voulons hooker (c'est à dire remplacer l'appel à une fonction X par une fonction Y à nous). 2 choix semblent s'offrir à nous : soit nous voulons hooker juste cet appel à la fonction pour appeler notre fonction et dans ce cas il suffirait juste de remplacer l'instruction asm call functionX par call functionY. Soit nous voulons faire en sorte que tous les appels à functionX arrivent à la place dans functionY. Dans ce cas, à moins de remplacer à la main tous les appels à functionX, on préfèrera généralement modifier functionX elle même pour la faire aller directement dans functionY, qui elle même retournera alors une fois terminée à l'instruction suivant l'appel à la fonctionX originale. C'est en somme ce qu'on appelle habituellement "hook".

Voici un bout de code asm représentant avant et après le hook (attention cet exemple est plutôt orienté x86) :

Avant :
Code:
// some code...
// example : push 2 args
PUSH arg1
PUSH arg0
CALL functionX
// example : test return value
TEST EAX, EAX
// some code...

//functionX definition
// some code
RET
// end functionX

Après :
Code:
// some code...
// example : push 2 args
PUSH arg1
PUSH arg0
CALL functionX
// example : test return value
TEST EAX, EAX
// some code...

//functionX definition
JMP functionY
// some code ignored now since functionY will do a RET itself at the end
RET
// end functionX

On remarque alors 2 choses :
- On va devoir écraser le code du début de functionX avec une instruction JMP functionY
- le code original de functionX après les instruction écrasées sera donc inutilisé et surtout inutilisable vu qu'il ne sera plus accessible (à cause du RET de functionY qui fera directement retourner à l'instruction TEST EAX, EAX de notre exemple).

Il existe des libs telles que Detour écrite par Microsoft permettant de hooker une fonction par une autre (ce qu'on vient de voir à l'instant). Je vous propose d'écrire ce code à la main histoire de bien comprendre ce qu'il se passe Very Happy .

Code:
#define _CRT_SECURE_NO_WARNINGS
#include <Windows.h>
#include <cstdio>
#include <time.h>

DWORD WINAPI HookThread();
DWORD WINAPI UnhookThread();
void add_log(char* format, ...);
void *SetDetour(BYTE *source, const BYTE *destination, unsigned int length);
void UnSetDetour(BYTE *source, const BYTE *destination, unsigned int length, BYTE *tunnel);
HRESULT __stdcall functionY(DWORD arg0, DWORD arg1); // our hook function

typedef HRESULT(__stdcall* FunctionX_t)(DWORD, DWORD);// define functionX_t as a function pointer on functionX type (a 2 DWORD arg function returning a value)

FunctionX_t pFunctionX;// a functionX type pointer we will use to point on the original functionX

FARPROC dwFunctionX = NULL;// the address of functionX
HANDLE tmpHandle = NULL;

BOOL WINAPI DllMain(HINSTANCE hinstDll,DWORD Reason,LPVOID Reserved)
{
   switch(Reason)
   {
   case DLL_PROCESS_ATTACH:
      add_log("==========LOG START==========");
      add_log("DLL Attached");
      add_log("Creating Thread...");
      tmpHandle = CreateThread(0, 0, (LPTHREAD_START_ROUTINE)&HookThread, 0, 0, 0);
      if (!tmpHandle)
      {
         add_log("ThreadCreation Failed!");
      }
      break;
   case DLL_PROCESS_DETACH:
      UnhookThread();
      add_log("DLL Detached");
      add_log("==========LOG END==========\n\n\n");
      break;
   }
   return 1;
}

DWORD WINAPI HookThread(void)
{
   add_log("Thread Created");

   dwFunctionX = (FARPROC) ((DWORD)0xDEADBEEF);// the address of the function (could not be static if the hooked process uses randomized base address so in this case you will have to write code before this to find the address of the targeted function)
   pFunctionX = (GetDeviceState_t) SetDetour((PBYTE) dwFunctionX, (PBYTE) functionY, 5);
   add_log("Hooked functionX - Detour: %x - New: %x !", pFunctionX, functionY);

   return S_OK;
}

DWORD WINAPI UnhookThread(void)
{
   UnSetDetour((BYTE *)dwFunctionX, (BYTE *)functionY, 5, (BYTE *)pFunctionX);
   add_log("Unhooked functionX - Detour: %x - New: %x !", functionY, pFunctionX);
   pFunctionX = NULL;

   return S_OK;
}

void add_log(char* format, ...)
{
   HANDLE filehandle;
   DWORD dwReadBytes;
   char buffer[2048];
   char writebuffer[2048];
   va_list args;
   va_start(args, format);
   vsprintf (buffer, format, args);
   filehandle = CreateFile(L"Log.txt", GENERIC_WRITE, 0, 0, OPEN_ALWAYS, 0, 0);
   SetFilePointer(filehandle, 0, 0, FILE_END);
   char date[18];
   _strdate(date);
   date[8] = ' ';
   _strtime(date+9);
   sprintf_s(writebuffer, 2048, "Log Added (%s): %s\r\n", date, buffer);
   WriteFile(filehandle, writebuffer, strlen(writebuffer), &dwReadBytes, 0);
   CloseHandle(filehandle);
}

void *SetDetour(BYTE *source, const BYTE *destination, unsigned int length)
{
   unsigned int const jmpLength(5);
   unsigned int const nopOpcode(0x90);
   unsigned int const jmpOpcode(0xE9);

   if (length < jmpLength)
      length = jmpLength; // Make sure the patch's length is long enough to hold a 32bit JMP.
   unsigned int tunnelLength = length + jmpLength;
   BYTE *tunnel  = new BYTE[tunnelLength]; // Create a body for the "tunnel" function.
   FillMemory(tunnel, tunnelLength, 0);
   DWORD oldProtection(NULL); // Old page protection.
   VirtualProtect(source, length, PAGE_EXECUTE_READWRITE, &oldProtection);
   memcpy(tunnel, source, length);
   FillMemory(source, length, nopOpcode);// erase source opcode
   source[0] = jmpOpcode;
   tunnel[length] = jmpOpcode;
   *(DWORD*)(source + 1) = (DWORD)(destination - source) - jmpLength; // JMP Offset 1
   *(DWORD*)(tunnel + 1 + length) = (DWORD)(source - tunnel) - jmpLength; // JMP Offset 2
   VirtualProtect(source, length, oldProtection, &oldProtection);
   return tunnel;
}

void UnSetDetour(BYTE *source, const BYTE *destination, unsigned int length, BYTE *tunnel)
{
   unsigned int const jmpLength(5);

   if (length < jmpLength)
      length = jmpLength; // Make sure the patch's length is long enough to hold a 32bit JMP.
   unsigned int tunnelLength = length + jmpLength;
   DWORD oldProtection(NULL); // Old page protection.
   VirtualProtect(source, length, PAGE_EXECUTE_READWRITE, &oldProtection);
   memcpy(source, tunnel, length);// copy back the original opcode
   VirtualProtect(source, length, oldProtection, &oldProtection);
   delete[] tunnel;
}
   
HRESULT __stdcall functionY(DWORD arg0, DWORD arg1)
{
   // do whatever we want (except calling functionX xD... we surely don't want to be stuck in recursivity !
   return 0;
}

En fait ce que fait SetDetour est simple : il va copier le code d'origine du début de functionX dans tunnel qui sera alors retourné par la fonction dans une zone mémoire allouée par lui (uniquement pour pouvoir le restaurer dans UnsetDetour). Il va ensuite écraser la zone mémoire correspondant à ce code d'origine et va la remplacer par un JMP functionY.

A l'inverse la fonction UnsetDetour va prendre le code qu'on a backupé et le réécrire sur la zone qu'on a précédemment écrasée pour restaurer le code d'origine (ni vu ni connu Rolling Eyes ).

Simple non Razz ?

Ce procédé peut être utilisé tant que l'on veut remplacer une fonction existante par une autre à nous (ou pas d'ailleurs : on peut aussi appeler une autre fonction du code d'origine si l'on veut). Le but est clairement de détourner l'usage premier d'une fonction pour lui faire faire autre chose. Par cette même méthode on peut également "shunter" une fonction en lui faisant faire un return "prématuré" pour empêcher son exécution pirat .


Il reste cependant un usage qui ne peut pas être gérer par cette méthode Suspect ... Le cas où l'on veut insérer du code à nous dans du code existant...

Prenons l'exemple d'un jeu que l'on voudrait améliorer ou dans lequel on voudrait créer une ébauche d'IA pour le perso principal afin d'automatiser des actions. La première chose à laquelle on pense à ce moment est "par tous les dieux, insérons un MyUpdate() à l'intérieur de la boucle de jeu principale ou dans n'importe quelle fonction de type update et rajoutons-y tout ce qui nous passera par la tête".

La solution utilisée jusqu'à présent semble un bon départ mais il manque 2 choses importantes :
- parvenir à restaurer les registres qui auront été modifiés par notre code,
- parvenir à exécuter notre code et revenir au point de départ sans louper le code situé après notre jump (et précédemment jamais exécuté à cause du RET).

La solution que j'ai jusqu'à présent utilisée (probablement utilisée par moi seul) est un peu étrange et pas très orthodoxe mais elle a l'avantage de laisser l'accès à la stack de la fonction appelante.

Au lieu d'insérer directement directement un jump vers notre fonction, on va d'abord insérer l'instruction PUSHA qui permet de pusher sur la stack tous les registres du processeur. Ensuite seulement on fait notre jump. On écrit ensuite le code que l'on veut dans notre fonction et on rajoute le petit bloc magique suivant juste avant la fon de la fonction pour empêcher l'exécution du RET et tout remettre d'aplomb :
Code:
__asm
{
   // dans le cas d'un stdcall les fonctions démarrent toutes par push ebp; mov ebp, esp; et terminent par mov esp, ebp; pop ebp; ret; Donc on reconstitue la fin de la fonction qu'on va skipper à cause de notre jmp back
   mov esp,ebp;
   pop ebp;

   popad;// on restaure les registres

   // on réécrit ici toutes les instructions enlevées du code d'origine (attention à bien avoir enlevé les opcode d'instruction ENTIERE sinon ça va tout péter...)
   // exemple : cmp [edi+108h], bl; (tient sur 6 BYTES : pile ce qu'il faut pour faire tenir le pusha et le jmp)
   
   // et finalement le jmp à l'adresse de retour ( =addresse de départ + 6 ou plus selon la taille des instructions enlevées)
   jmp dwHandleCodeRet;
}


Ça nous donne donc le code modifié suivant pour les fonctions SetDetour et UnsetDetour :
Code:
void *SetDetour(BYTE *source, const BYTE *destination, unsigned int length)
{
   unsigned int const jmpLength(5);
   unsigned int const nopOpcode(0x90);
   unsigned int const jmpOpcode(0xE9);
   unsigned int const pushaOpcode(0x60);
   unsigned int const popaOpcode(0x61);

   if (length < jmpLength+1)
      length = jmpLength+1; // Make sure the patch's length is long enough to hold a 32bit JMP + a pusha
   unsigned int tunnelLength = length + jmpLength + 1; // + popa
   BYTE *tunnel  = new BYTE[tunnelLength]; // Create a body for the "tunnel" function.
   FillMemory(tunnel, tunnelLength, 0);
   DWORD oldProtection(NULL); // Old page protection.
   VirtualProtect(source, length, PAGE_EXECUTE_READWRITE, &oldProtection);
   tunnel[0] = popaOpcode;
   memcpy(tunnel+1, source, length);
   FillMemory(source, length, nopOpcode);// erase source opcode
   source[0] = pushaOpcode;
   source[1] = jmpOpcode;
   tunnel[length+1] = jmpOpcode;
   *(DWORD*)(source + 1+1) = (DWORD)(destination - (source+jmpLength+1)); // JMP Offset 1
   *(DWORD*)(tunnel + 1 + length+1) = (DWORD)((source+jmpLength+1) - (tunnel+jmpLength+length+1)); // JMP Offset 2
   VirtualProtect(source, length, oldProtection, &oldProtection);
   return tunnel;
}

void UnSetDetour(BYTE *source, const BYTE *destination, unsigned int length, BYTE *tunnel)
{
   unsigned int const jmpLength(5);
   
   if (length < jmpLength+1)
      length = jmpLength+1; // Make sure the patch's length is long enough to hold a 32bit JMP.
   DWORD oldProtection(NULL); // Old page protection.
   VirtualProtect(source, length, PAGE_EXECUTE_READWRITE, &oldProtection);
   memcpy(source, tunnel+1, length);// copy back the original opcode
   VirtualProtect(source, length, oldProtection, &oldProtection);
   delete[] tunnel;
}[/code2]

que l'on peut utiliser alors comme ceci :
[code2=cpp]VOID hkHandleCode(VOID);
typedef VOID(*HandleCode_t)(VOID);
HandleCode_t pHandleCode;
FARPROC dwHandleCode = NULL;
FARPROC dwHandleCodeRet = NULL;

// pour le SetDetour :
dwHandleCode = (FARPROC) ((DWORD)DETOUR_MAIN_LOOP_OFFSET);
dwHandleCodeRet = (FARPROC) ((DWORD)DETOUR_MAIN_LOOP_OFFSET + 6);
pHandleCode = (HandleCode_t) SetDetour((PBYTE) dwHandleCode, (PBYTE) hkHandleCode, 6);

// pour le UnSetDetour :
UnSetDetour((BYTE *)dwHandleCode, (BYTE *)hkHandleCode, 6, (BYTE *)pHandleCode);
pHandleCode = NULL;


Bon là je vous avoue qu'on vient de rentrer dans le hack sauvage et expérimental et je ne m'appuie sur absolument rien d'autre que mon expérience et mes essais (il n'y a malheureusement que peu de tuto permettant d'apprendre quoi que ce soit sur ce sujet). Du coup je ne garantis aucunement du fait que ce soit la seule méthode pour arriver à ce résultat, ni même que cette méthode soit "saine". Faire un jmp pour atterrir dans une fonction n'est de toute façon pas ce que je pourrais qualifier de "sain", et encore moins bypasser le return de la fonction par un jump back pirat (d'ailleurs le compilo émet un bon warning avec ce code).

Il existe d'autres méthodes pour hooker une fonction tout en gardant le code d'origine utilisable, tels que le trampoline par exemple qui se base sur la même méthode mais rajoute un jmp vers la suite du code original sur lequel on a écrasé le début afin d'être capable d'exécuter le bout de code enlevé puis jump vers la suite du code original.


Voilà, c'en est fini pour ce tuto. J'espère que vous avez tous décroché avant la fin et donc que vous ne lirez pas cette conclusion de merde (bande de moules Twisted Evil !). Nan ! Plus sérieusement hésitez pas à poser des questions si vous en avez ou proposer vos propres solutions.
avatar
Akabane87
Admin

Messages : 45
Date d'inscription : 30/07/2010

Voir le profil de l'utilisateur http://dreamraiser.forumactif.com

Revenir en haut Aller en bas

Re: [x86] Injection DLL dans un process windows

Message par Contenu sponsorisé


Contenu sponsorisé


Revenir en haut Aller en bas

Voir le sujet précédent Voir le sujet suivant Revenir en haut

- Sujets similaires

 
Permission de ce forum:
Vous ne pouvez pas répondre aux sujets dans ce forum