Created
October 8, 2024 11:23
-
-
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
foo 0x1000 | |
bar 0x1010 | |
baz 0x1020 | |
main 0x1030 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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()); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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