Friday, July 1, 2011

Listing "hot patchable" functions in a module

Hi folks!

Today, I will tell you about a little program that I've coded in order to list hot patchable functions in a module.

First and foremost, you must wonder what I mean by "hot patchable" and "module".

Well, the "hot patching", in Information Technology security, is simply a hooking method which consists in, according to the name, patching a function located in a module - a DLL in most cases.

An example of aim of the "hot patching" method is to hook a function, for debugging purposes.

But I've started a code in order to make hot patching for another purpose: API monitoring. I haven't implemented it yet, but I hope I will do it soon (maybe in a further article?).

By the way, how do we detect a hot patchable function?

Well, let's have a look at kernel32!SetHandleCount dissassembly:

7C80CD37 >/$ 8BFF           MOV EDI,EDI
7C80CD39  |. 55             PUSH EBP
7C80CD3A  |. 8BEC           MOV EBP,ESP
7C80CD3C  |. 8B45 08        MOV EAX,DWORD PTR SS:[EBP+8]
7C80CD3F  |. 5D             POP EBP
7C80CD40  \. C2 0400        RETN 4

What should warn you is the first instruction: "MOV EDI, EDI". It does not look useful, but in reality it does! It's a kind of "nop instruction", coded by 2 bytes, that you can "rewrite" by whatever 2 bytes-coded instruction. For example, you can patch these 2 bytes by a short jump (EB XX). And fortunately for us, the 5 bytes above the "MOV EDI, EDI" instruction are:

7C80CD32     90             NOP
7C80CD33     90             NOP
7C80CD34     90             NOP
7C80CD35     90             NOP
7C80CD36     90             NOP

We can also patch these 5 bytes by a far jump (or a far call, as you want). Then we redirect the program stream to another region code (for example, an injected dll?).

I shall explain this technique in another article, eg. once I've implemented it.

Meanwhile, I suggest you this code which lists hot patchable functions from a dll:

/**
 * \file list_hot_patchable_functions.c
 * \brief little program that let you to list hotpatchable functions from a module
 * \author Geoffrey ROYER
 * \date 01/07/2011
 * \version 0.1
 */
#include <stdio.h>
#include <stdlib.h>
#include <windows.h>

/**
 * \fn void list_hotpatchable_functions(const char* module_name);
 * \brief simply dumps the list of hotpatchable functions from `module_name`
 * \param module_name (const char*) : path of the module name (mainly a dll file)
 * \example list_hotpatchable_functions("C:\\WINDOWS\\System32\\kernel32.dll");
 * \example list_hotpatchable_functions("C:\\WINDOWS\\System32\\user32.dll");
 * \return void
 */
void list_hotpatchable_functions(const char* module_name);

/**
 * \fn DWORD rva_to_offset(PIMAGE_SECTION_HEADER pBaseOfSectionHeaders, DWORD dwNumberOfSections, DWORD dwRva);
 * \brief calculate the equivalent offset of dwRva, basing on pBaseOfSectionHeaders feat dwNumberOfSections
 * \param pBaseOfSectionHeaders (PIMAGE_SECTION_HEADER) : pointer to the first section header of the PE
 * \param dwNumberOfSections (DWORD) : number of sections in the PE file
 * \param dwRva (DWORD) : the relative virtual address from which one you want the offset
 * \example no need :)
 * \return DWORD : offset of the specified relative virtual address
 */
DWORD rva_to_offset(PIMAGE_SECTION_HEADER pBaseOfSectionHeaders, DWORD dwNumberOfSections, DWORD dwRva);


int main(int argc, char **argv) {
    if(argc < 2) {
        printf("Usage: %s <module>\n", argv[0]);
        exit(EXIT_FAILURE);
    }

    list_hotpatchable_functions(argv[1]);

    return EXIT_SUCCESS;
}


void list_hotpatchable_functions(const char* module_name) {

    /* All utils variables */
    HANDLE                  hMapOfExe                   = INVALID_HANDLE_VALUE;
    HANDLE                  hExeHandle                  = INVALID_HANDLE_VALUE;
    LPVOID                  lpExeView                   = NULL;
    PIMAGE_DOS_HEADER       pImageDosHeader             = NULL;
    PIMAGE_NT_HEADERS       pImageNtHeaders             = NULL;
    PIMAGE_SECTION_HEADER   pImageSectionHeader         = NULL;
    PIMAGE_EXPORT_DIRECTORY pImageExportDirectory       = NULL;
    DWORD                   dwEatOffset                 = 0;
    DWORD                   dwEatVirtualAddress         = 0;
    LPDWORD                 tableNames                  = NULL;
    LPDWORD                 tableAddresses              = NULL;
    DWORD                   currentName                 = 0;
    DWORD                   currentRva                  = 0;
    DWORD                   offsetOfFunction            = 0;

    int i;
    int number_of_patchable_functions = 0;


    /* Opening the file */
    hExeHandle = CreateFile(
        module_name,
        GENERIC_READ,
        FILE_SHARE_READ,
        NULL,
        OPEN_EXISTING,
        0,
        0
    );


    if(hExeHandle != INVALID_HANDLE_VALUE) {


        hMapOfExe = CreateFileMapping(
            hExeHandle,
            NULL,
            PAGE_READONLY,
            0,
            0,
            NULL
        );

        if(hMapOfExe != INVALID_HANDLE_VALUE) {

            /* Mapping the file */
            lpExeView = MapViewOfFile(
                hMapOfExe,
                FILE_MAP_READ,
                0,
                0,
                0
            );

            if(lpExeView != NULL) {

                /* Mapping with different pointers */
                pImageDosHeader         = (PIMAGE_DOS_HEADER) lpExeView;
                pImageNtHeaders         = (PIMAGE_NT_HEADERS) ((LPBYTE) pImageDosHeader + pImageDosHeader->e_lfanew);
                pImageSectionHeader     = (PIMAGE_SECTION_HEADER) ((LPBYTE) pImageNtHeaders + sizeof(IMAGE_NT_HEADERS));
                dwEatVirtualAddress     = pImageNtHeaders->OptionalHeader.DataDirectory[0].VirtualAddress;

                /* Retrieving the section of the export table address */
                dwEatOffset = rva_to_offset(
                    pImageSectionHeader,
                    pImageNtHeaders->FileHeader.NumberOfSections,
                    dwEatVirtualAddress
                );


                pImageExportDirectory = (PIMAGE_EXPORT_DIRECTORY) ((LPBYTE) pImageDosHeader + dwEatOffset);

                DWORD dwOffsetTableNames        = rva_to_offset(
                    pImageSectionHeader,
                    pImageNtHeaders->FileHeader.NumberOfSections,
                    pImageExportDirectory->AddressOfNames
                );

                DWORD dwOffsetTableAddresses    = rva_to_offset(
                    pImageSectionHeader,
                    pImageNtHeaders->FileHeader.NumberOfSections,
                    pImageExportDirectory->AddressOfFunctions
                );


                tableNames      = (LPDWORD)( (LPBYTE)lpExeView + dwOffsetTableNames);
                tableAddresses  = (LPDWORD)( (LPBYTE)lpExeView + dwOffsetTableAddresses);


                printf("=== LIST OF PATCHABLE FUNCTIONS OF %s MODULE ===\n", module_name);


                for(i = 0; i < pImageExportDirectory->NumberOfNames; ++i) {

                    currentName = rva_to_offset(
                        pImageSectionHeader,
                        pImageNtHeaders->FileHeader.NumberOfSections,
                        *tableNames
                    );

                    currentRva = rva_to_offset(
                        pImageSectionHeader,
                        pImageNtHeaders->FileHeader.NumberOfSections,
                        *tableAddresses
                    );

                    offsetOfFunction = rva_to_offset(
                        pImageSectionHeader,
                        pImageNtHeaders->FileHeader.NumberOfSections,
                        currentRva
                    );

                    /* Hot patchable? */
                    if(*((PDWORD)((LPBYTE)lpExeView + currentRva - 5)) == 0x90909090 &&
                        *((PDWORD)((LPBYTE)lpExeView + currentRva - 2)) == 0xFF8B9090) {
                        ++number_of_patchable_functions;
                        printf(
                            "%s (0x%08X)\n",
                            (char*)(lpExeView + currentName),
                            (unsigned int)(pImageNtHeaders->OptionalHeader.ImageBase + *tableAddresses)
                        );
                    }


                    ++tableNames;
                    ++tableAddresses;

                }

                printf("-------------------------\nTotal: %d function(s)", number_of_patchable_functions);

                /* Freeing resource */
                UnmapViewOfFile(lpExeView);

            } else {
                printf("Error MapViewOfFile().\n");
            }

            CloseHandle(hMapOfExe);

        } else {
            printf("Error CreateFileMapping().\n");
        }

        CloseHandle(hExeHandle);

    } else {
        printf("Error CreateFile().\n");
    }

}

DWORD rva_to_offset(PIMAGE_SECTION_HEADER pBaseOfSectionHeaders, DWORD dwNumberOfSections, DWORD dwRva) {
    int i = 0;
    while(i < dwNumberOfSections) {
        if(pBaseOfSectionHeaders->VirtualAddress >= dwRva) {
            --i;
            --pBaseOfSectionHeaders;
            break;
        }
        ++pBaseOfSectionHeaders;
        ++i;
    }

    return pBaseOfSectionHeaders->PointerToRawData + (dwRva - pBaseOfSectionHeaders->VirtualAddress);
}


The code browses the export table address of a given module and read the 5 + 2 important bytes. If these bytes are composed of nops with a "mov edi, edi" instruction, then the function is considered as hot patchable.

If you have any suggest or any idea to improve the code (or the explanations, either), feel free to leave a comment.

For the moment, I don't have any stable depository, so I can't let you download any file. But I'm working on it...

Thank you for having read this blog entry!

Ge0

No comments:

Post a Comment