Reputation: 316
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
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
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
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