首先,这仅用于教育目的。我绝不是专家。
我的第一个编程语言是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来相应地转换地址。
答案 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;
}
然后将其编译为目标文件(不要链接)。然后从目标文件中复制可执行代码并尝试将其加载到内存中。
如果您使用的是可执行文件,则必须解析其中的信息。此外,您还必须进行链接。