将可执行文件加载到当前进程的内存中,然后执行它

时间:2015-12-04 11:03:40

标签: c++ windows x86 inline-assembly portable-executable

首先,这仅用于教育目的。我绝不是专家。

我的第一个编程语言是python。在python中,您有exec()和eval()函数,它们允许您将任意字符串作为代码运行。现在我正在学习C ++和汇编。我注意到,当我第一次开始使用C ++时,没有相应的上述函数,这是因为C ++是一种编译语言。这让我想知道是否有办法让可执行的编写C ++代码,调用编译器,并将生成的字节码复制到内存中以动态修改程序的功能。当然这是不切实际的,并且有更好的方法来实现我想要的东西。最后,我开始学习汇编,这有助于我了解字节码究竟是什么以及它是如何工作的。这促使我回到这个概念;将其视为挑战和机遇。

以下是一般概念:

程序A将可执行文件程序B作为资源(例如)。

程序A希望在自己的地址空间中执行程序B(在修改它之后或者它想要做的任何事情)。

程序A分配空间(当然具有适当的权限)。

将副本的程序B的字节码编程到分配的空间中。

计划A解决了计划B的进口。

程序A将执行转移到程序B(可以将执行转移回程序A等)。


这是我到目前为止的基本代码:

#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;
}

更新:此代码大部分时间都可以使用。它似乎在复杂的程序上失败了,截至目前,我不知道为什么。对于那些想知道的人:有一个&amp; 0x80000000是因为该位表示您将两个低位字节视为序数。所以我使用MAKEINTRESOURCE来相应地转换地址。

3 个答案:

答案 0 :(得分:1)

如果你想加载任意代码(相当模糊的表达式,但让我们使用它),那么你可以创建一个DLL。然后,在运行时,您可以使用LoadLibary加载它,并使用GetProcAddress获取指向DLL导出的函数的函数指针。

我认为这是你在C / C ++中与你所描述的最接近的事情。

答案 1 :(得分:1)

您的代码是一个良好的开端,但您缺少一些东西。

首先,正如您所提到的,解决进口问题。你说的看起来是正确的,但我从来没有像你那样手动完成,所以我不知道细节。程序可以在不解析导入的情况下工作,但前提是您不使用任何导入的函数。在这里,您的代码失败,因为它尝试访问尚未解析的导入;函数指针包含0x4242而不是已解析的地址。

第二件事是搬迁。为简单起见,PE可执行文件与位置无关(可以在任何基址工作),即使代码不是。为了使其工作,该文件包含一个重定位表,用于调整依赖于图像位置的所有数据。如果您可以在首选地址(pINH->OptionalHeader.ImageBase)加载,则此点是可选的,但这意味着如果您使用重定位表,则可以将图像加载到任何位置,并且可以省略{{1}的第一个参数(并删除相关的检查)。

如果您还没有找到,可以在this article中找到有关导入解析和重定位的更多信息。您可以找到许多其他资源。

另外,正如marom的回答中提到的,你的程序基本上是VirtualAlloc所做的,所以在更实际的环境中,你会使用这个函数。

答案 2 :(得分:0)

我曾做过类似的事情。首先,你应该用C(++)编写非常简单的函数。例如:

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

然后将其编译为目标文件(不要链接)。然后从目标文件中复制可执行代码并尝试将其加载到内存中。

如果您使用的是可执行文件,则必须解析其中的信息。此外,您还必须进行链接。