将可执行文件加载到当前进程的内存中,然后执行它
Loading an executable into current process's memory, then executing it
首先,这仅用于教育目的。我绝不是专家。
我的第一门编程语言是 python。在 python 中,你有 exec() 和 eval() 函数,它们允许你 运行 任意字符串作为代码。现在我正在学习C++和汇编。我注意到,当我第一次开始使用 C++ 时,没有与上述功能等效的功能,这是因为 C++ 是一种编译语言。这让我想知道是否有一种方法可以让可执行文件编写 C++ 代码、调用编译器并将生成的字节码复制到内存中以动态修改程序的功能。当然,这是不切实际的,从本质上讲,有更好的方法可以实现我想要的。最后,我开始学习汇编,这让我对字节码到底是什么以及它是如何工作有了一些了解。这促使我回到这个概念;将其视为挑战和机遇。
大致思路如下:
程序A有可执行文件程序B作为资源(例如)。
程序A想在它自己的地址space执行程序B(在修改它或任何它想做的事情之后)。
程序 A 分配 space(当然有适当的权限)。
程序A拷贝程序B的字节码到分配的space.
程序 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;
}
更新:此代码大部分时间都有效。它似乎在复杂的程序上失败了,截至目前,我不确定为什么。对于那些想知道的人:出现 & 0x80000000 的原因是因为该位表示您要将两个低位字节视为序数。所以我使用 MAKEINTRESOURCE 相应地转换地址。
我曾经做过类似的事情。首先,您应该用 C(++) 编写非常简单的函数。例如:
int add(a, b) {
return a + b;
}
然后将其编译为目标文件(不要link)。然后从目标文件中复制可执行代码并尝试将其加载到内存中。
如果您使用的是可执行文件,则必须解析其中的信息。此外,您还必须 linking.
如果你想加载任意代码(相当模糊的表达但让我们使用它)那么你可以制作一个DLL。然后,在 运行 时,您可以让 LoadLibary 加载它,并让 GetProcAddress 获取指向 DLL 导出的函数的函数指针。
我认为这是您在 C/C++ 中可以做的最接近您所描述的事情。
你的代码是一个好的开始,但你遗漏了一些东西。
正如您提到的,首先是解析导入。你说的好像是对的,但我从来没有像你这样手动做过,所以我不知道细节。程序可以在不解析导入的情况下运行,但前提是您不使用任何导入函数。此处您的代码失败,因为它试图访问尚未解析的导入;函数指针包含 0x4242
而不是解析地址。
第二件事是搬家。为简单起见,PE executable 是位置独立的(可以在任何基地址工作),即使代码不是。为实现这一点,该文件包含一个重定位 table,用于调整所有依赖于图像位置的数据。如果您可以在首选地址(pINH->OptionalHeader.ImageBase
)加载,则此点是可选的,但这意味着如果您使用重定位table,则可以在任何地方加载图像,并且可以省略第一个参数VirtualAlloc
(并删除相关检查)。
如果您还没有找到有关导入解析和重定位的更多信息,可以在 this article 中找到。您可以找到许多其他资源。
此外,正如 marom 的回答中提到的,您的程序基本上就是 LoadLibrary
所做的,因此在更实际的情况下,您可以改用此功能。
首先,这仅用于教育目的。我绝不是专家。
我的第一门编程语言是 python。在 python 中,你有 exec() 和 eval() 函数,它们允许你 运行 任意字符串作为代码。现在我正在学习C++和汇编。我注意到,当我第一次开始使用 C++ 时,没有与上述功能等效的功能,这是因为 C++ 是一种编译语言。这让我想知道是否有一种方法可以让可执行文件编写 C++ 代码、调用编译器并将生成的字节码复制到内存中以动态修改程序的功能。当然,这是不切实际的,从本质上讲,有更好的方法可以实现我想要的。最后,我开始学习汇编,这让我对字节码到底是什么以及它是如何工作有了一些了解。这促使我回到这个概念;将其视为挑战和机遇。
大致思路如下:
程序A有可执行文件程序B作为资源(例如)。
程序A想在它自己的地址space执行程序B(在修改它或任何它想做的事情之后)。
程序 A 分配 space(当然有适当的权限)。
程序A拷贝程序B的字节码到分配的space.
程序 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;
}
更新:此代码大部分时间都有效。它似乎在复杂的程序上失败了,截至目前,我不确定为什么。对于那些想知道的人:出现 & 0x80000000 的原因是因为该位表示您要将两个低位字节视为序数。所以我使用 MAKEINTRESOURCE 相应地转换地址。
我曾经做过类似的事情。首先,您应该用 C(++) 编写非常简单的函数。例如:
int add(a, b) {
return a + b;
}
然后将其编译为目标文件(不要link)。然后从目标文件中复制可执行代码并尝试将其加载到内存中。
如果您使用的是可执行文件,则必须解析其中的信息。此外,您还必须 linking.
如果你想加载任意代码(相当模糊的表达但让我们使用它)那么你可以制作一个DLL。然后,在 运行 时,您可以让 LoadLibary 加载它,并让 GetProcAddress 获取指向 DLL 导出的函数的函数指针。
我认为这是您在 C/C++ 中可以做的最接近您所描述的事情。
你的代码是一个好的开始,但你遗漏了一些东西。
正如您提到的,首先是解析导入。你说的好像是对的,但我从来没有像你这样手动做过,所以我不知道细节。程序可以在不解析导入的情况下运行,但前提是您不使用任何导入函数。此处您的代码失败,因为它试图访问尚未解析的导入;函数指针包含 0x4242
而不是解析地址。
第二件事是搬家。为简单起见,PE executable 是位置独立的(可以在任何基地址工作),即使代码不是。为实现这一点,该文件包含一个重定位 table,用于调整所有依赖于图像位置的数据。如果您可以在首选地址(pINH->OptionalHeader.ImageBase
)加载,则此点是可选的,但这意味着如果您使用重定位table,则可以在任何地方加载图像,并且可以省略第一个参数VirtualAlloc
(并删除相关检查)。
如果您还没有找到有关导入解析和重定位的更多信息,可以在 this article 中找到。您可以找到许多其他资源。
此外,正如 marom 的回答中提到的,您的程序基本上就是 LoadLibrary
所做的,因此在更实际的情况下,您可以改用此功能。