Skip to content

Instantly share code, notes, and snippets.

@PascalBeyer
Created October 8, 2024 11:23
Show Gist options
  • Save PascalBeyer/e9fda393d2a5475581063ba670e91a55 to your computer and use it in GitHub Desktop.
Save PascalBeyer/e9fda393d2a5475581063ba670e91a55 to your computer and use it in GitHub Desktop.
Converting a .exe into a .dll with a list of function names and virtual addresses.
// Example: exe_to_dll.exe test.exe functions.txt
//
// with functions.txt:
// foo 0x1030
// bar 0x1040
// baz 0x1050
// main 0x1060
//
// Gives test.dll which has no entry point and exports foo, bar, baz and main.
// Build me with: cl exe_to_dll.c
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
struct file{
uint8_t *data;
size_t size;
};
struct file load_file(char *file_name){
struct file ret = {0};
FILE *handle = fopen(file_name, "rb");
if(!handle){
printf("could not fopen '%s'\n", file_name);
return ret;
}
fseek(handle, 0, SEEK_END);
size_t size = ftell(handle);
fseek(handle, 0, SEEK_SET);
uint8_t *memory = malloc(size + 1);
memory[size] = 0;
fread(memory, 1, size, handle);
fclose(handle);
ret.data = memory;
ret.size = size;
return ret;
}
struct function_rva{
struct function_rva *next;
char *function_name;
uint32_t relative_virtual_address;
};
int compare_function_rva(const void *_a, const void *_b){
const struct function_rva *a = _a;
const struct function_rva *b = _b;
return strcmp(a->function_name, b->function_name);
}
int main(int argc, char *argv[]){
if(argc != 3){
printf("Usage: %s <.exe> <function-file>\n", argv[0]);
return 0;
}
//
// Load the files.
//
struct file exe = load_file(argv[1]);
struct file function_file = load_file(argv[2]);
uint32_t offset = *(uint32_t *)(exe.data + 0x3c);
//
// Set the DLL bit.
//
PIMAGE_FILE_HEADER ImageFileHeader = (void *)(exe.data + offset + /*PE\0\0*/4);
ImageFileHeader->Characteristics |= IMAGE_FILE_DLL;
//
// Remove the entry point, as otherwise on it would be called on load.
// If I am not mistaken, you are not allowed to call some functions in a DllMain.
//
PIMAGE_OPTIONAL_HEADER64 OptionalHeader = (void *)(ImageFileHeader + 1);
OptionalHeader->AddressOfEntryPoint = 0; // Nuke the entry point.
OptionalHeader->DllCharacteristics &= ~IMAGE_DLLCHARACTERISTICS_TERMINAL_SERVER_AWARE; // This bit is not valid, not sure if it matters though.
OptionalHeader->Subsystem = IMAGE_SUBSYSTEM_WINDOWS_GUI; // Not sure if this matters.
PIMAGE_SECTION_HEADER SectionTable = (void *)(OptionalHeader + 1);
if((uint8_t *)(SectionTable + ImageFileHeader->NumberOfSections + 1) - exe.data > OptionalHeader->SizeOfHeaders){
printf("There is not enough space in the header section of the image to fit another section...\n");
printf("In theory, you could search for a hole to put the .edata into, or something.\n");
printf("But this is unimplemented.\n");
return 1;
}
//
// We assume the <function-file> to look as follows:
//
// function-name relative-virtual-address
// main 0x1200
// other 0x1245
// ... ...
//
struct function_rva *function_list = 0;
uint32_t function_count = 0;
uint32_t function_name_size = 0;
char *at = (char *)function_file.data;
while(*at){
char *function_name = at;
at = strchr(at, ' ');
if(!at) break;
*at++ = 0;
function_name_size += (at - function_name);
while(*at == ' ') at++;
char *address = at;
at = strchr(at, '\n');
if(!at){
at = "";
}else{
if(at[-1] == '\r') at[-1] = 0;
*at++ = 0;
}
uint32_t relative_virtual_address = strtoul(address, NULL, 16);
struct function_rva *new = malloc(sizeof(struct function_rva));
new->function_name = function_name;
new->relative_virtual_address = relative_virtual_address;
new->next = function_list;
function_list = new;
function_count += 1;
}
// Hacky.
struct function_rva *function_array = malloc(sizeof(struct function_rva) * function_count);
{
uint32_t function_index = 0;
for(struct function_rva *function = function_list; function; function = function->next){
function_array[function_index++] = *function;
}
qsort(function_array, function_count, sizeof(*function_array), compare_function_rva);
}
//
// Write out the new export table.
//
uint32_t ExportTableVirtualAddress = OptionalHeader->SizeOfImage; // The new section will start at the end of the Image.
uint32_t ExportTableFilePointer = exe.size;
uint32_t DllNameLength = strlen(argv[1]) + 1;
uint32_t ExportAddressTableSize = sizeof(uint32_t) * function_count; // one address per function
uint32_t ExportNamePointerTableSize = sizeof(uint32_t) * function_count; // one name address per function
uint32_t ExportOrdinalTableSize = sizeof(uint16_t) * function_count; // one ordinal per function
uint32_t ExportDataRawSize = sizeof(IMAGE_EXPORT_DIRECTORY)
+ ExportAddressTableSize
+ ExportNamePointerTableSize
+ ExportOrdinalTableSize
+ function_name_size
+ DllNameLength;
uint32_t ExportDataSize = (ExportDataRawSize + (OptionalHeader->FileAlignment - 1)) & ~(OptionalHeader->FileAlignment - 1); // Assuming FileAlignement is a power of two...
uint32_t ExportDataVirtualSize = (ExportDataSize + OptionalHeader->SectionAlignment - 1) & ~(OptionalHeader->SectionAlignment - 1); // Assuming SectionAlignment is a power of two...
PIMAGE_EXPORT_DIRECTORY ExportDirectory = calloc(ExportDataSize, 1);
uint8_t *ExportDirectoryBase = (void *)ExportDirectory;
uint32_t ExportAddressTableOffset = sizeof(*ExportDirectory);
uint32_t ExportNamePointerTableOffset = ExportAddressTableOffset + ExportAddressTableSize;
uint32_t ExportOrdinalTableOffset = ExportNamePointerTableOffset + ExportNamePointerTableSize;
uint32_t DllNameOffset = ExportOrdinalTableOffset + ExportOrdinalTableSize;
uint32_t FunctionNameOffset = DllNameOffset + DllNameLength;
ExportDirectory->Name = ExportTableVirtualAddress + DllNameOffset;
ExportDirectory->Base = 1;
ExportDirectory->NumberOfFunctions = function_count;
ExportDirectory->NumberOfNames = function_count;
ExportDirectory->AddressOfFunctions = ExportTableVirtualAddress + ExportAddressTableOffset;
ExportDirectory->AddressOfNames = ExportTableVirtualAddress + ExportNamePointerTableOffset;
ExportDirectory->AddressOfNameOrdinals = ExportTableVirtualAddress + ExportOrdinalTableOffset;
uint32_t function_name_offset_at = FunctionNameOffset;
for(uint32_t index = 0; index < function_count; index++){
((uint32_t *)(ExportDirectoryBase + ExportAddressTableOffset))[index] = function_array[index].relative_virtual_address;
((uint32_t *)(ExportDirectoryBase + ExportNamePointerTableOffset))[index] = ExportTableVirtualAddress + function_name_offset_at;
((uint16_t *)(ExportDirectoryBase + ExportOrdinalTableOffset))[index] = index;
size_t len = strlen(function_array[index].function_name);
memcpy(ExportDirectoryBase + function_name_offset_at, function_array[index].function_name, len);
function_name_offset_at += len + 1;
}
char *DllName = (char *)(ExportDirectoryBase + DllNameOffset);
memcpy(DllName, argv[1], DllNameLength-/*exe\0*/4);
memcpy(DllName + DllNameLength - /*exe\0*/4, "dll", 3);
//
// Link the ExportDirectory up to the optional header.
//
OptionalHeader->DataDirectory[0].VirtualAddress = ExportTableVirtualAddress;
OptionalHeader->DataDirectory[0].Size = ExportDataRawSize;
//
// Add one more section.
// We will use this section to hold the export data.
//
PIMAGE_SECTION_HEADER NewSection = SectionTable + ImageFileHeader->NumberOfSections++;
memcpy(NewSection->Name, ".edata\0", 8);
NewSection->Misc.VirtualSize = ExportDataRawSize;
NewSection->VirtualAddress = ExportTableVirtualAddress;
NewSection->SizeOfRawData = ExportDataSize;
NewSection->PointerToRawData = ExportTableFilePointer;
NewSection->PointerToRelocations = 0;
NewSection->PointerToLinenumbers = 0;
NewSection->NumberOfRelocations = 0;
NewSection->NumberOfLinenumbers = 0;
NewSection->Characteristics = 0x40000040; // Intialized Data, Read only.
OptionalHeader->SizeOfInitializedData += ExportDataSize;
OptionalHeader->SizeOfImage += ExportDataVirtualSize;
FILE *out = fopen(DllName, "wb");
if(!out){
printf("Failed to open output file '%s'\n", DllName);
return 1;
}
fwrite(exe.data, 1, exe.size, out);
fwrite(ExportDirectoryBase, 1, ExportDataSize, out);
fclose(out);
return 0;
}
foo 0x1000
bar 0x1010
baz 0x1020
main 0x1030
// Example loader. Build me with: cl loader.c
#include <windows.h>
#include <stdio.h>
int main(){
HMODULE module = LoadLibraryA("test.dll");
int (*foo)(void) = GetProcAddress(module, "foo");
int (*bar)(void) = GetProcAddress(module, "bar");
int (*baz)(void) = GetProcAddress(module, "baz");
printf("foo %d\n", foo());
printf("bar %d\n", bar());
printf("baz %d\n", baz());
}
// Simple example.
// Build me with: cl test.c /Z7 /link /incremental:no /nodefaultlib /entry:main
int foo(){
return 1337;
}
int bar(){
return 69;
}
int baz(){
return 420;
}
int main(){
return foo() + bar() + baz();
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment