Tuesday, July 26, 2011

Hot Patching a function: little example with Api Monitoring

Hi folks,

As mentionned in my last post, I wish I had made a blog entry concerning the hot patching. I didn't have much time for practicing it but I finally managed to prove this concept briefly.

I will provide three source codes which concern:
- a program that will inject a customized dll into a process of your choice;
- a dll that will hot patch the "GetModuleHandleA" function which is exported by the kernel32.dll module;
- a program to "hot patch".

Remembering the concept of "hot patching"

So, sometimes when you're diving into a reverse code engineering session, you might find instructions like "MOV EDI,EDI" as the first instruction of an exported routine from a module (eg. kernel32.dll, user32.dll...). This instruction looks useless but in reality it isn't. Moreover, if we look just a little above, we can see either five nop instructions or int3 ones. I don't especially think that they're runned by your processor.

These instructions aren't there by accident. They are there in order to allow us to "hot patch" the function / routine". Indeed, the "MOV edi, edi" measures 2 bytes and the sequence of nop / int3 instructions measures 5 bytes.

The concept of the hot patching consists in overwriting these seven bytes while running the process. The two bytes of "MOV edi, edi" will be overwritten by a short jump; this short jump then redirects the execution stream to the five nop / int3 instructions. And these 5 bytes are patched by a far jump / call, for example.

In our case, if we want to make API monitoring, we shall write a far call to our customized procedure that will print out "This function was called!" on the screen. This is a good start, isn't it?

But there's a little problem though. Suppose your routine, which role is to inform you of the attempt to call the hot patched function, finishes its execution, then you encounter the ret instruction. This instruction will obviously pop the saved instruction pointer of the stack.

And this saved instruction pointer will redirect our stream to... The short jump that we have written instead of "MOV edi, edi"! So we have to increase this saved EIP by two bytes. In fact that's easy:

void DLL_EXPORT foo() {
    asm("addl $2, 4(%ebp)");
    printf("GetModuleHandle was called!\n");
}

The saved EIP points to [ebp+4] because of the stack frame. Our function will be exported from a dll that we would have previously injected. And this function will have a prologue such as "PUSH EBP" and "MOV EBP, ESP"... And the "PUSH EBP" pushes a value onto the stack. It means that the stack looks like:

+--------------+ <---- ebp / esp
|  saved ebp   |
+--------------+ <---- ebp + 4
|  saved eip   |
+--------------+

The reason why I choose ebp instead of esp - and in fact it doesn't really matter in this case - is that by convention you can find the first argument of the routine at ebp+8, the second argument at ebp+12... When you've a stack frame. And at ebp+4 you've your saved EIP; you're not supposed to modify it, unless you're smashing the stack (buffer overflow).

So, by increasing the interesting value by two bytes, we will return after the short jump (the old "MOV edi, edi") and, after this instruction, there is the code of the routine.

What have we done? We have made a detour. Our "foo" function has been called before the GetModuleHandle function.

Now it's time to practice. First of all, here is the code of the dll to inject; it has both a header file and a C source file:

main.h
#ifndef __MAIN_H__
#define __MAIN_H__

#include <stdio.h>
#include <windows.h>

/*  To use this exported function of dll, include this header
 *  in your project.
 */

#ifdef BUILD_DLL
    #define DLL_EXPORT __declspec(dllexport)
#else
    #define DLL_EXPORT __declspec(dllimport)
#endif


#ifdef __cplusplus
extern "C"
{
#endif


void DLL_EXPORT foo();

#ifdef __cplusplus
}
#endif

#endif // __MAIN_H__


main.c
#include "main.h"


void DLL_EXPORT foo() {
    asm("addl $2, 4(%ebp)");
    printf("GetModuleHandle was called!\n");
}


void HotPatch(LPVOID lpOldFunction, LPVOID lpNewFunction)  {

    /* -5 because of the nop / int3 instructions */
    LPVOID lpNewOldFunctionPointer = lpOldFunction - 5;

    DWORD dwOldProtectionValue, dwNewProtectionValue;

    /* Calculating the call destination (-5 because of the call size) */
    LPVOID lpCallDestination = (LPVOID)(lpNewFunction - lpNewOldFunctionPointer - 5);

    VirtualProtect( (LPDWORD) lpNewOldFunctionPointer, 7, PAGE_EXECUTE_READWRITE, &dwOldProtectionValue);

    /* Writing the call */
    memcpy((LPVOID)lpNewOldFunctionPointer, "\xE8", sizeof(char));
    memcpy((LPVOID)(lpNewOldFunctionPointer + 1), &lpCallDestination, sizeof(LPVOID));

    /* Writing the jump */
    memcpy((LPVOID)(lpNewOldFunctionPointer + 5), "\xEB\xF9", 2 * sizeof(char));

    /* Set back the old protection */
    VirtualProtect((LPDWORD) lpNewOldFunctionPointer, 7, dwOldProtectionValue, &dwNewProtectionValue);
}

BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
{
    HINSTANCE hDll = LoadLibrary("kernel32.dll");


    LPVOID lpFunctionToHook = GetProcAddress(hDll, "GetModuleHandleA");

    switch (fdwReason)
    {
        case DLL_PROCESS_ATTACH:


            // Rulz babe
            HotPatch(lpFunctionToHook, (LPVOID)foo);

            break;

        case DLL_PROCESS_DETACH:
            // detach from process
            break;

        case DLL_THREAD_ATTACH:
            // attach to thread
            break;

        case DLL_THREAD_DETACH:
            // detach from thread
            break;
    }
    return TRUE; // succesful
}

As you can see, when the DLL is loaded, it immediately "hot patches" the GetModuleHandle function in kernel32.dll - supposing that it's hot patchable and it is in my OS (Win7 x64).

We miss a dll injector and a little program. Here are the codes:

injector.c
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <tlhelp32.h>

int InjectDllIntoProcess(DWORD dwPid, LPCTSTR szDll);
DWORD ProcessNameToPid(LPCTSTR lpszProcessName);

int main(int argc , char* argv[]) {

    if(argc < 3) {
        printf("Usage: %s <process> <dll>\n",argv[0]);
        exit(EXIT_FAILURE);
    }

    if(InjectDllIntoProcess(ProcessNameToPid(argv[1]),argv[2])) {
        printf("=== Dll successfully injected ===\n");
    }


    return EXIT_SUCCESS;
}


int InjectDllIntoProcess(DWORD dwPid, LPCTSTR szDll) {
    LPVOID                  lpReservedSpace         = NULL;
    DWORD                   dwSizeOfDllPath         = strlen(szDll) + 1;
    DWORD                   dwRet                   = 0;
    DWORD                   dwStatus                = 0;
    HANDLE                  hRemoteThread           = NULL;
    LPTHREAD_START_ROUTINE  lpThreadStartRoutine    = NULL;
    DWORD                   dwRemoteThreadId        = -1;
    printf("Opening the process... ");

    HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPid);
    if(hProcess != NULL) {

        printf("[OK]\nAllocating memory into the process...\n");
        LPVOID lpReservedSpace = VirtualAllocEx( hProcess, NULL, dwSizeOfDllPath, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
        if(lpReservedSpace != NULL) {

            printf("[OK]\nWriting into the allocated memory space...\n");
            dwStatus = WriteProcessMemory(hProcess, lpReservedSpace, szDll, dwSizeOfDllPath , 0);
            if(dwStatus) {

                printf("[OK]\nCreating a remote thread into the process...\n");

                lpThreadStartRoutine = (LPTHREAD_START_ROUTINE)GetProcAddress(LoadLibrary("kernel32"),"LoadLibraryA");
                hRemoteThread = CreateRemoteThread(hProcess, NULL, 0, lpThreadStartRoutine, lpReservedSpace, 0 , &dwRemoteThreadId );

                if(hRemoteThread != NULL) {
                    printf("[OK]\n");
                    WaitForSingleObject(hRemoteThread, INFINITE);
                    VirtualFreeEx(hProcess, lpReservedSpace, 0, MEM_DECOMMIT);

                    CloseHandle(hProcess);
                    CloseHandle(hRemoteThread);

                    dwRet = 1;
                }
            }
        }
    }

    return dwRet;
}

DWORD ProcessNameToPid(LPCTSTR lpszProcessName) {
    HANDLE      hSnapshot;
    PROCESSENTRY32  proc;
    DWORD       dwPid = -1;

    proc.dwSize = sizeof(PROCESSENTRY32);

    if((hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0)) != INVALID_HANDLE_VALUE) {

        if(Process32First(hSnapshot, &proc)) {
            if(!strcmp(lpszProcessName, proc.szExeFile)) {
                dwPid = proc.th32ProcessID;
            }

            while(dwPid == -1 && Process32Next(hSnapshot, &proc)) {
                if(!strcmp(lpszProcessName, proc.szExeFile)) {
                    dwPid = proc.th32ProcessID;
                }
            }
        }

        CloseHandle(hSnapshot);
    }

    return dwPid;
}



Finally, the target program.

hotpatchme.c
#include <stdio.h>
#include <windows.h>

int main(int argc, char **argv) {
    printf("Press enter once you've hooked.\n");
    getchar();
    GetModuleHandle(NULL);
}

Let's try the stuff.

C:\Users\Geoffrey\Mes documents\CPP>hotpatchme.exe
Press enter once you've hooked.


C:\Users\Geoffrey\Mes documents\CPP>

Relaunch it without pressing enter:

C:\Users\Geoffrey\Mes documents\CPP>hotpatchme.exe
Press enter once you've hooked.

In another prompt, launch the injector:

C:\Users\Geoffrey\Mes documents\CPP>dllinjector.exe
Usage: dllinjector.exe

C:\Users\Geoffrey\Mes documents\CPP>dllinjector.exe hotpatchme.exe hotpatch.dll
Opening the process... [OK]
Allocating memory into the process...
[OK]
Writing into the allocated memory space...
[OK]
Creating a remote thread into the process...
[OK]
=== Dll successfully injected ===

C:\Users\Geoffrey\Mes documents\CPP>

And press enter...

C:\Users\Geoffrey\Mes documents\CPP>hotpatchme.exe
Press enter once you've hooked.

GetModuleHandle was called!

C:\Users\Geoffrey\Mes documents\CPP>

It works! \o/

I hope you enjoyed the article. At the beginning I was attempting to write a more complete tool where you could have provided functions to monitor; this tool would have checked whether the dll was loaded or not - by browsing the loaded module list into the Process Environment Block - and other stuff but it takes a lot of time to make a clear and cleaned code. Nevertheless you figure out what I was talking about.

Thank you for having read this post. If you've any suggestion concerning the techniques or my poor English... Feel free to leave a comment!

Geo

Thanks go out to Gr3' for having fixed many english mistakes!

No comments:

Post a Comment