Alex
Alex

Reputation: 316

Loading an executable into current process's memory, then executing it

First off, this is only for educational purposes. I am, by no means, an expert.

My first programming language was python. In python you have the exec() and eval() functions which allow you to run arbitrary strings as code. Now I am learning C++ and assembly. I noticed that, when I first started working with C++, there was no equivalent to the aforementioned functions and this is because C++ is a compiled language. This made me wonder if there was a way to have an executable write C++ code, invoke the compiler, and copy the resulting bytecode into memory to dynamically modify the program's functionality. Of course this was impractical and there were better ways to accomplish, in essence, what I wanted. Finally, I started learning assembly which helped give me some insight as to what bytecode really is and how it works. This prompted me to come back to this concept; seeing it as both a challenge and an opportunity.

Here is the general idea:

Program A has executable file Program B as a resource (for example).

Program A wants to execute Program B in its own address space (after modifying it or whatever it wants to do).

Program A allocates the space (with the appropriate permissions of course).

Program A copy's Program B's bytecode into the allocated space.

Program A resolves Program B's imports.

Program A transfers execution to Program B (which can transfer the execution back to Program A etc).


This is the basic code that I have so far:

#include <iostream>
#include <windows.h>
#include <winternl.h>

DWORD Rva2Offset(DWORD rva, PIMAGE_SECTION_HEADER psh, PIMAGE_NT_HEADERS pnt)
{
    size_t i = 0;
    PIMAGE_SECTION_HEADER pSeh;
    if (rva == 0)
    {
        return (rva);
    }
    pSeh = psh;
    for (i = 0; i < pnt->FileHeader.NumberOfSections; i++)
    {
        if (rva >= pSeh->VirtualAddress && rva < pSeh->VirtualAddress +
            pSeh->Misc.VirtualSize)
        {
            break;
        }
        pSeh++;
    }
    return (rva - pSeh->VirtualAddress + pSeh->PointerToRawData);
}


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

    PIMAGE_DOS_HEADER pIDH;
    PIMAGE_NT_HEADERS pINH;
    PIMAGE_SECTION_HEADER pISH;

    PVOID image, mem, base;
    DWORD i, read, nSizeOfFile;
    HANDLE hFile;

    if (argc != 2)
    {
        printf("\nNot Enough Arguments\n");
        return 1;
    }

    printf("\nOpening the executable.\n");

    hFile = CreateFile(argv[1], GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);

    if (hFile == INVALID_HANDLE_VALUE)
    {
        printf("\nError: Unable to open the executable. CreateFile failed with error %d\n", GetLastError());
        return 1;
    }

    nSizeOfFile = GetFileSize(hFile, NULL);

    image = VirtualAlloc(NULL, nSizeOfFile, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); // Allocate memory for the executable file

    if (!ReadFile(hFile, image, nSizeOfFile, &read, NULL)) // Read the executable file from disk
    {
        printf("\nError: Unable to read the replacement executable. ReadFile failed with error %d\n", GetLastError());
        return 1;
    }

    CloseHandle(hFile); // Close the file handle

    pIDH = (PIMAGE_DOS_HEADER)image;

    if (pIDH->e_magic != IMAGE_DOS_SIGNATURE) // Check for valid executable
    {
        printf("\nError: Invalid executable format.\n");
        return 1;
    }

    pINH = (PIMAGE_NT_HEADERS)((LPBYTE)image + pIDH->e_lfanew); // Get the address of the IMAGE_NT_HEADERS

    printf("\nAllocating memory in child process.\n");

    mem = VirtualAlloc((PVOID)pINH->OptionalHeader.ImageBase, pINH->OptionalHeader.SizeOfImage, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE); // Allocate memory for the executable image

    if (!mem)
    {
        mem = VirtualAlloc(NULL, pINH->OptionalHeader.SizeOfImage, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE); // Allow it to pick its own address
    }

    if ((DWORD)mem != pINH->OptionalHeader.ImageBase)
    {
            printf("\nProper base could not be reserved.\n");

            return 1;
    }

    printf("\nMemory allocated. Address: %#X\n", mem);

    printf("\nResolving Imports\n");


    if (pINH->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].Size != 0)
    {
        PIMAGE_SECTION_HEADER pSech = IMAGE_FIRST_SECTION(pINH);

        PIMAGE_IMPORT_DESCRIPTOR pImportDescriptor = (PIMAGE_IMPORT_DESCRIPTOR)((DWORD_PTR)image + Rva2Offset(pINH->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress, pSech, pINH));
        LPSTR libname;
        size_t i = 0;
        // Walk until you reached an empty IMAGE_IMPORT_DESCRIPTOR
        while (pImportDescriptor->Name != NULL)
        {
            printf("Library Name   :");
            //Get the name of each DLL
            libname = (PCHAR)((DWORD_PTR)image + Rva2Offset(pImportDescriptor->Name, pSech, pINH));
            printf("%s\n", libname);

            HMODULE libhandle = GetModuleHandle(libname);
            if(!libhandle)
                libhandle = LoadLibrary(libname);

            PIMAGE_THUNK_DATA nameRef = (PIMAGE_THUNK_DATA)((DWORD_PTR)image + Rva2Offset(pImportDescriptor->Characteristics, pSech, pINH));
            PIMAGE_THUNK_DATA symbolRef = (PIMAGE_THUNK_DATA)((DWORD_PTR)image + Rva2Offset(pImportDescriptor->FirstThunk, pSech, pINH));
            for (; nameRef->u1.AddressOfData; nameRef++, symbolRef++)
            {
                if (nameRef->u1.AddressOfData & 0x80000000)
                {
                    symbolRef->u1.AddressOfData = (DWORD)GetProcAddress(libhandle, MAKEINTRESOURCE(nameRef->u1.AddressOfData));
                }
                else
                {
                    PIMAGE_IMPORT_BY_NAME thunkData = (PIMAGE_IMPORT_BY_NAME)((DWORD_PTR)image + Rva2Offset(nameRef->u1.AddressOfData, pSech, pINH));
                    symbolRef->u1.AddressOfData = (DWORD)GetProcAddress(libhandle, (LPCSTR)&thunkData->Name);
                }
            }
            pImportDescriptor++; //advance to next IMAGE_IMPORT_DESCRIPTOR
            i++;

        }
    }

    printf("\nWriting executable image into child process.\n");

    memcpy(mem, image, pINH->OptionalHeader.SizeOfHeaders); // Write the header of the executable

    for (i = 0; i<pINH->FileHeader.NumberOfSections; i++)
    {
        pISH = (PIMAGE_SECTION_HEADER)((LPBYTE)image + pIDH->e_lfanew + sizeof(IMAGE_NT_HEADERS) + (i*sizeof(IMAGE_SECTION_HEADER)));
        memcpy((PVOID)((LPBYTE)mem + pISH->VirtualAddress), (PVOID)((LPBYTE)image + pISH->PointerToRawData), pISH->SizeOfRawData); //Write the remaining sections
    }

    if (pINH->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].Size && (pINH->OptionalHeader.ImageBase != (DWORD)mem))
    {
        printf("\nBase relocation.\n");

        DWORD i, num_items;
        DWORD_PTR diff;
        IMAGE_BASE_RELOCATION* r;
        IMAGE_BASE_RELOCATION* r_end;
        WORD* reloc_item;

        diff = (DWORD)mem - pINH->OptionalHeader.ImageBase; //Difference between memory allocated and the executable's required base.
        r = (IMAGE_BASE_RELOCATION*)((DWORD)mem + pINH->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress); //The address of the first I_B_R struct 
        r_end = (IMAGE_BASE_RELOCATION*)((DWORD_PTR)r + pINH->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].Size - sizeof(IMAGE_BASE_RELOCATION)); //The addr of the last

        for (; r<r_end; r = (IMAGE_BASE_RELOCATION*)((DWORD_PTR)r + r->SizeOfBlock))
        {
            reloc_item = (WORD*)(r + 1);
            num_items = (r->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) / sizeof(WORD);

            for (i = 0; i<num_items; ++i, ++reloc_item)
            {
                switch (*reloc_item >> 12)
                {
                case IMAGE_REL_BASED_ABSOLUTE:
                    break;
                case IMAGE_REL_BASED_HIGHLOW:
                    *(DWORD_PTR*)((DWORD)mem + r->VirtualAddress + (*reloc_item & 0xFFF)) += diff;
                    break;
                default:
                    return 1;
                }
            }
        }
    }

    DWORD entrypoint = (DWORD)((LPBYTE)mem + pINH->OptionalHeader.AddressOfEntryPoint); 

    printf("\nNew entry point: %#X\n", entrypoint);

    VirtualFree(image, 0, MEM_RELEASE); // Free the allocated memory

    __asm jmp entrypoint

    return 0;
}

UPDATE: This code works most of the time. It seems to fail on complex programs and, as of right now, I am not sure why. For those who are wondering: The reason there is an & 0x80000000 is because that bit signifies that you are to treat the two low order bytes as an ordinal. So I use MAKEINTRESOURCE to convert the address accordingly.

Upvotes: 2

Views: 5758

Answers (3)

ElderBug
ElderBug

Reputation: 6145

You code is a good start, but you are missing a few things.

First is, as you mentioned, resolving imports. What you say looks right, but I've never done this manually like you so I don't know the details. It would be possible for a program to work without resolving imports, but only if you don't use any imported function. Here your code fails because it tries to access an import that hasn't been resolved ; the function pointer contains 0x4242 instead of the resolved address.

The second thing is relocation. To make it simple, PE executable are position independent (can work at any base address), even if the code isn't. To make this work, the file contains a relocation table that is used to adjust all the data that are dependent on the image location. This point is optional if you can load at the preferred address (pINH->OptionalHeader.ImageBase), but it means that if you use the relocation table, you can load your image anywhere, and you can omit the first parameter of VirtualAlloc (and remove the related checks).

You can find more info on import resolving and relocation in this article, if you didn't find it already. There is plenty of other resource you can find.

Also, as mentioned in marom's answer, your program is basically what LoadLibrary do, so in a more practical context, you would use this function instead.

Upvotes: 3

marom
marom

Reputation: 5230

If you want to load an arbitrary code (rather vague expression but let's use it) then you can make a DLL. Then, at run time you can get LoadLibary to load it and GetProcAddress to get a function pointer to the functions exported by the DLL.

I think this is the closest thing you can do in C/C++ to what you describe.

Upvotes: 2

Martin Habovštiak
Martin Habovštiak

Reputation: 1

I once did something similar. At first, you should write very simple function in C(++). For example:

int add(a, b) {
     return a + b;
}

Then compile it as object file (do not link). Then copy the executable code from object file and try to load that into memory.

If you are using executable, you have to parse information in it. Also, you will have to do linking.

Upvotes: 0

Related Questions