From 39f4b61ac4e312b3dfd8164e00419071b8b9908f Mon Sep 17 00:00:00 2001 From: Leo Tenenbaum Date: Sat, 14 Aug 2021 19:10:48 -0400 Subject: windows build --- .gitignore | 5 + Makefile | 2 +- README.md | 61 ++++++++++-- main.c | 319 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++---- make.bat | 6 ++ 5 files changed, 362 insertions(+), 31 deletions(-) create mode 100644 make.bat diff --git a/.gitignore b/.gitignore index 6de5a42..f7798eb 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,8 @@ TAGS *.lib *.exp *.dll +*.pdb +*.obj +_dlsub_temp* +.vs +*.swp diff --git a/Makefile b/Makefile index 324430b..4c914ab 100644 --- a/Makefile +++ b/Makefile @@ -1,2 +1,2 @@ dlsub: main.c - $(CC) -O0 -g -o dlsub main.c -std=c89 -Wpedantic -pedantic -Wall -Wextra -Wshadow -Wconversion -Wimplicit-fallthrough + $(CC) -O2 -g -o dlsub main.c -std=c89 -Wpedantic -pedantic -Wall -Wextra -Wshadow -Wconversion -Wimplicit-fallthrough diff --git a/README.md b/README.md index 7ff7ea3..b8c0c06 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # dlsub -A tool for replacing a subset of functions in dynamic libraries. +A tool (x86-64 only) for replacing a subset of functions in dynamic libraries. Let's say you're meddling around with a program that uses [SDL](https://libsdl.org). One thing you might want to do is replace an SDL @@ -29,15 +29,26 @@ To install these on Ubuntu/Debian: sudo apt install nasm tcc ``` +On Windows, you can get yourself a copy of Microsoft Visual Studio, search for +a file called `vcvarsall.bat`, add it to your PATH, and the run `vcvarsall x64` +to set up the C compiler. You can install NASM from their website, and to make +things more convenient, you can add nasm.exe to your PATH (or just copy +the file to the same directory as dlsub.exe). + On Unix-like systems, the default is to use TCC (for faster preprocessing and less likelihood of weird syntax messing dlsub up). You can, however, override this by setting the C_PREPROCESSOR environment variable. +## Compiling dlsub + +You can use the `Makefile` and `make.bat` provided, or you can just compile +`main.c` with any C compiler. + ## Figuring out which library file is being used On Windows, it may just be a DLL file in the same directory as the exe. -Otherwise, you can install [depends.exe](https://www.dependencywalker.com/); -good luck. +Otherwise, you can install [depends.exe](https://www.dependencywalker.com/) +to figure it out. On Unix-like systems, if you want to know what specific library files an executable is using, run: @@ -60,9 +71,14 @@ You can specify multiple header files if the library has more than one. Here is an example invocation for replacing SDL: ```bash -dlsub --no-warn -l /usr/lib/x86_64-linux-gnu/libSDL2-2.0.so -I /usr/include/SDL2 -i SDL.h -i SDL_syswm.h -i SDL_vulkan.h -C -DSDL_DISABLE_IMMINTRIN_H -o sdl +dlsub --no-warn -l -I -i SDL.h -i SDL_syswm.h -i SDL_vulkan.h -C <- on Unix, / on Windows>DSDL_DISABLE_IMMINTRIN_H -o sdl ``` +Substitute `` and `` +with `/lib/x86_64-linux-gnu/libSDL2-2.0.so` and `/usr/include/SDL2` on Linux, and +something like `C:\\SDL2-2.0.14\\lib\\x64\\SDL2.dll` and +`C:\\SDL2-2.0.14\\include` on Windows + (the `-DSDL_DISABLE_IMMINTRIN_H` is needed for tcc, and it also speeds up processing) @@ -71,11 +87,19 @@ Now let's say you want to replace `SDL_SetWindowTitle`. First, delete the line in `sdl.asm`: ``` -global SDL_SetWindowTitle:function +GLOBAL SDL_SetWindowTitle ``` (This deletes the default replacement, i.e. to redirect to the real SDL function). + +(This is specific to SDL.h on Windows) +At the start of sdl.c, add: + +```c +#define DLL_EXPORT +``` + Now at the end of sdl.c, add: ```c @@ -87,7 +111,8 @@ DLSUB_EXPORT void SDL_SetWindowTitle(SDL_Window *window, const char *title) { The `DLSUB_EXPORT` ensures that the function is exported out to the dynamic library (on Windows, where that distinction is made). -You can now compile libSDL2-2.0.so.0 on Linux, with: +On Linux, you can now compile libSDL2-2.0.so.0 with: + ``` nasm -f elf64 sdl.asm cc -fPIC -shared sdl.o sdl.c -o libSDL2-2.0.so.0 -I/usr/include/SDL2 @@ -98,6 +123,15 @@ And run a program that uses SDL like this: LD_LIBRARY_PATH=/directory/where/your/library/file/is ./some_application ``` +And on Windows: + +``` +nasm -f win64 sdl.asm -o sdl_asm.obj +cl /nologo /Fe:SDL2 /LD sdl_asm.obj sdl.c /I C:\\SDL2-2.0.14\\include +``` + +And just copy `SDL2.dll` to the same directory as the target application. + Note that dlsub *cannot* handle dynamic libraries' objects (e.g. `extern int foo;`), so if there are any you will have to make your own substitutes for those. @@ -114,7 +148,7 @@ without assembly. ## More examples... -### Replacing `XNextEvent` from libX11 +### (Unix-y) Replacing `XNextEvent` from libX11 ```bash dlsub --no-warn -l /usr/lib/x86_64-linux-gnu/libX11.so.6 -I /usr/include/X11 -i Xlib.h -i Xutil.h -o x11 @@ -125,7 +159,7 @@ dlsub --no-warn -l /usr/lib/x86_64-linux-gnu/libX11.so.6 -I /usr/include/X11 -i Delete the line from x11.asm: ``` -global XNextEvent:function +GLOBAL XNextEvent ``` Add to the bottom of x11.c: @@ -152,7 +186,7 @@ nasm -f elf64 x11.asm cc -fPIC -shared x11.c x11.o -o libX11.so.6 -I/usr/include/X11 ``` -### Replacing `exp` from libm +### (Unix-y) Replacing `exp` from libm Here's a silly example. This could cause some... interesting behavior. @@ -165,7 +199,7 @@ dlsub --no-warn -l /lib/x86_64-linux-gnu/libm.so.6 -I /usr/include -i math.h -o Delete from math.asm: ``` -global exp:function +GLOBAL exp ``` Add to math.c: @@ -179,3 +213,10 @@ double exp(double x) { nasm -f elf64 math.asm cc -fPIC -shared math.c math.o -o libm.so.6 ``` + +### Report a bug + +Bugs can be sent to `pommicket at pommicket.com`. Please only report bugs that +could/do actually occur in real usage of dlsub; in theory, it might not +correctly parse a function returning a function pointer that returns a function +pointer but that doesn't happen in real libraries. diff --git a/main.c b/main.c index 235c935..46de394 100644 --- a/main.c +++ b/main.c @@ -1,3 +1,11 @@ +/* +Anyone is free to modify/distribute/use/sell/etc +this software for any purpose by any means. +Any copyright protections are relinquished by the author(s). +This software is provided "as is", without any warranty. +The author(s) shall not be held liable in connection with this software. +*/ + #define VERSION "0.0" #if __GNUC__ @@ -92,14 +100,73 @@ #endif #elif _WIN32 - #define C_PREPROCESSOR_DEFAULT "cl" + #define C_PREPROCESSOR_DEFAULT "cl.exe /nologo /P" #include #include #include #include #include #include - #error "@TODO" + #include + + #if _WIN64 + #define fseek _fseeki64 + #define ftell _ftelli64 + typedef __int64 fseek_t; + #else + typedef long fseek_t; + #endif + + typedef struct { + unsigned signature; + unsigned short machine; + unsigned short n_sections; + unsigned time_date_stamp; + unsigned ptr_symbol_table; + unsigned n_symbols; + unsigned short size_of_optional_header; + unsigned short characteristics; + } PEHeader; + + typedef struct { + char name[8]; + unsigned virtual_size; + unsigned virtual_address; + unsigned size_of_raw_data; + unsigned ptr_to_raw_data; + unsigned ptr_to_relocations; + unsigned ptr_to_line_numbers; + unsigned short number_of_relocations; + unsigned short number_of_linenumbers; + unsigned characteristics; + } PESectionHeader; + + typedef struct { + unsigned flags; + unsigned time_date_stamp; + unsigned short major_version; + unsigned short minor_version; + unsigned name_rva; + unsigned ordinal_base; + unsigned address_table_entries; + unsigned n_name_pointers; + unsigned export_address_table_rva; + unsigned name_pointer_rva; + unsigned ordinal_table_rva; + } PEExportDirectoryTable; + + static char PEHeader__static_assertion[2 * (sizeof(PEHeader) == 24) - 1]; + static char PESectionHeader__static_assertion[2 * (sizeof(PESectionHeader) == 40) - 1]; + static char PEExportDirectoryTable__static_assertion[2 * (sizeof(PEExportDirectoryTable) == 40) - 1]; + + static int file_is_readable(const char *filename) { + FILE *f = fopen(filename, "rb"); + if (f) { + fclose(f); + return 1; + } + return 0; + } #else #error "Unsupported operating system." #endif @@ -170,7 +237,7 @@ static void symbol_hash_table_grow(SymbolHashTable *table) { for (i = 0; i < table->n_entries; ++i) { SymbolHashEntry *entry = table->entries[i]; - unsigned long p; + size_t p; if (!entry) continue; p = str_hash(entry->symbol) % new_n_entries; while (new_entries[p]) { @@ -186,7 +253,7 @@ static void symbol_hash_table_grow(SymbolHashTable *table) { } static void symbol_hash_table_insert(SymbolHashTable *table, const char *sym_name) { - unsigned long p; + size_t p; SymbolHashEntry *entry; if (table->n_present_entries * 2 >= table->n_entries) { symbol_hash_table_grow(table); @@ -211,7 +278,7 @@ static void symbol_hash_table_insert(SymbolHashTable *table, const char *sym_nam } static SymbolHashEntry *symbol_hash_table_get(SymbolHashTable *table, const char *name) { - unsigned long p = str_hash(name) % table->n_entries; + size_t p = str_hash(name) % table->n_entries; SymbolHashEntry *entry; while ((entry = table->entries[p])) { if (strcmp(entry->symbol, name) == 0) @@ -437,12 +504,70 @@ int main(int argc, char **argv) { } } #else - #error "@TODO" + { + PEHeader pe_header = {0}; + + { /* check if this is an actual DLL, find offset to PE header */ + unsigned short signature1 = 0; + unsigned pe_header_offset = 0; + + fread(&signature1, sizeof signature1, 1, fp); + if (signature1 != 0x5a4d) { + fprintf(stderr, "%s is not a DLL file.\n", libname); + exit(2); + } + + fseek(fp, 0x3c, SEEK_SET); + fread(&pe_header_offset, sizeof pe_header_offset, 1, fp); + fseek(fp, (fseek_t)pe_header_offset, SEEK_SET); + } + + fread(&pe_header, sizeof pe_header, 1, fp); + + if (pe_header.signature != 0x00004550 || (pe_header.characteristics & 0x2000) == 0) { + fprintf(stderr, "%s is not a DLL file.\n", libname); + exit(2); + } + + fseek(fp, (fseek_t)pe_header.size_of_optional_header, SEEK_CUR); + + { + unsigned section; + PESectionHeader section_header = {0}; + for (section = 1; section <= pe_header.n_sections; ++section) { + fread(§ion_header, sizeof section_header, 1, fp); + if (strncmp(section_header.name, ".edata", 8) == 0) { + /* offset for all "RVA" pointers */ + fseek_t rva_offset = (fseek_t)section_header.ptr_to_raw_data - (fseek_t)section_header.virtual_address; + + PEExportDirectoryTable edt = {0}; + unsigned s; + unsigned name_ptr = 0; + char name[256]; + + fseek(fp, (fseek_t)section_header.ptr_to_raw_data, SEEK_SET); + fread(&edt, sizeof edt, 1, fp); + + for (s = 0; s < edt.n_name_pointers; ++s) { + fseek(fp, rva_offset + (fseek_t)edt.name_pointer_rva + s * 4, SEEK_SET); + fread(&name_ptr, sizeof name_ptr, 1, fp); + fseek(fp, rva_offset + (fseek_t)name_ptr, SEEK_SET); + fread(name, 1, sizeof name, fp); + symbol_hash_table_insert(&all_symbols, name); + /* printf("%s\n",name); */ + } + break; + } + } + } + + } #endif fclose(fp); } + /* preprocess headers */ #if __unix__ { int preprocessed_headers_fd = fileno(tmpfile()); @@ -534,9 +659,7 @@ int main(int argc, char **argv) { } if (fd == -1) { - char prefix[128]; - sprintf(prefix, "Couldn't open %.100s", header); - perror(prefix); + fprintf(stderr, "Couldn't find %.100s\n", header); kill(SIGKILL, compiler_process); exit(2); } @@ -594,9 +717,114 @@ int main(int argc, char **argv) { } #else - #error "@TODO" -#endif + { + const char *in_headers_name = "_dlsub_temp"; + const char *out_headers_name = "_dlsub_temp.i"; + FILE *in_headers; + + in_headers = fopen(in_headers_name, "wb"); + if (!in_headers) { + perror("Couldn't create temporary file"); + exit(2); + } + + for (i = 1; i < argc-1; ++i) { + if (strcmp(argv[i], "-i") == 0) { + const char *header_name = argv[i+1]; + FILE *header_fp = fopen(header_name, "rb"); + char buf[4096]; + size_t bytes_read; + + if (!header_fp) { + /* check include directories */ + char path[520]; + int j; + for (j = 1; j < argc-1; ++j) { + if (strcmp(argv[j], "-I") == 0) { + const char *dir = argv[j+1]; + sprintf(path, "%.256s\\%.256s", dir, header_name); + header_fp = fopen(path, "rb"); + if (header_fp) break; + } + if (argv[j][0] == '-' && argv[j][1] != '-') ++j; + } + + if (!header_fp) { + fprintf(stderr, "Couldn't find %.100s.\n", header_name); + exit(2); + } + } + + while ((bytes_read = fread(buf, 1, sizeof buf, header_fp))) { + fwrite(buf, 1, bytes_read, in_headers); + } + + } + if (argv[i][0] == '-' && argv[i][1] != '-') ++i; + } + + fclose(in_headers); + + { + static char command[L_tmpnam + 50000] = {0}; + size_t p = 0; + sprintf(&command[p], "%.300s %.1000s ", preprocessor_program, in_headers_name); + p += strlen(&command[p]); + for (i = 1; i < argc-1; ++i) { + if (strcmp(argv[i], "-I") == 0) { + const char *inc = argv[i+1]; + if (p + strlen(inc) + 10 > sizeof command) { + fprintf(stderr, "Too many arguments to C preprocessor.\n"); + exit(-1); + } + sprintf(&command[p], "/I %s ", inc); + p += strlen(&command[p]); + } else if (strcmp(argv[i], "-C") == 0) { + const char *arg = argv[i+1]; + if (p + strlen(arg) + 10 > sizeof command) { + fprintf(stderr, "Too many arguments to C preprocessor.\n"); + exit(-1); + } + sprintf(&command[p], "%s ", arg); + p += strlen(&command[p]); + } + if (argv[i][0] == '-' && argv[i][1] != '-') ++i; + } + system(command); + if (!file_is_readable(out_headers_name)) { + fprintf(stderr, "C header preprocessing failed.\n"); + exit(2); + } + } + + { + HANDLE output_file = CreateFileA(out_headers_name, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + HANDLE mapping; + + if (output_file == INVALID_HANDLE_VALUE) { + fprintf(stderr, "Couldn't open %s (Windows error code %u).\n", out_headers_name, GetLastError()); + exit(2); + } + + mapping = CreateFileMappingA(output_file, NULL, PAGE_READONLY, 0, 0, NULL); + if (mapping == INVALID_HANDLE_VALUE) { + fprintf(stderr, "Couldn't create mapping for %s (Windows error code %u).\n", out_headers_name, GetLastError()); + exit(2); + } + + /* lets hope nobody's preprocessed headers are >4GB in size ... */ + preprocessed_headers_len = GetFileSize(output_file, NULL); + preprocessed_headers = MapViewOfFile(mapping, FILE_MAP_READ, 0, 0, preprocessed_headers_len); + if (!preprocessed_headers) { + fprintf(stderr, "Couldn't map file %s into memory (Windows error code %u).\n", out_headers_name, GetLastError()); + exit(2); + } + } + + + } +#endif /* figure out functions! */ @@ -638,7 +866,24 @@ int main(int argc, char **argv) { #else fprintf(c_output, "#if _WIN32\n"); #endif - fprintf(c_output, "#define DLSUB_REAL_DL_NAME \"%s\"\n", input_filename); + fprintf(c_output, "#define DLSUB_REAL_DL_NAME \""); + { + const char *p; + for (p = input_filename; *p; ++p) { + switch (*p) { + case '\\': + fputs("\\\\", c_output); + break; + case '"': + fputs("\\\"", c_output); + break; + default: + putc(*p, c_output); + break; + } + } + } + fprintf(c_output, "\"\n"); fprintf(c_output, "#else\n" "#error \"Please define DLSUB_REAL_DLNAME here.\"\n" @@ -652,17 +897,23 @@ int main(int argc, char **argv) { "#define DLSUB_GET_DLHANDLE(filename) dlopen(filename, RTLD_LAZY)\n" "#define DLSUB_GET_SYM(handle, name) ((void(*)(void))dlsym(handle, name))\n" "#define DLSUB_EXPORT\n" + "#define DLSUB_CALL\n" "static void __attribute__((constructor)) dlsub_constructor(void) {\n" "\tdlsub_init();\n" + "\t}\n" "}\n" ); fprintf(c_output, "#elif _WIN32\n" - "extern void *__stdcall LoadLibraryA(const char *);\n" - "extern int (*__stdcall GetProcAddress(void *, const char *))(void);\n" + "typedef struct HINSTANCE__ *HMODULE;\n" + "__declspec(dllimport) HMODULE __stdcall LoadLibraryA(const char *);\n" + "__declspec(dllimport) __int64 (*__stdcall GetProcAddress(HMODULE, const char *))();\n" "#define DLSUB_GET_DLHANDLE LoadLibraryA\n" "#define DLSUB_GET_SYM GetProcAddress\n" - "#define DLSUB_EXPORT __declspec((dllexport))\n" + "#define DLSUB_CALL\n" + "#define DLSUB_EXPORT __declspec(dllexport)\n" + ); + fprintf(c_output, "unsigned __stdcall DllMain(void *instDLL, unsigned reason, void *_reserved) {\n" "\t(void)instDLL; (void)_reserved;\n" "\tswitch (reason) {\n" @@ -671,6 +922,8 @@ int main(int argc, char **argv) { "\t\tbreak;\n" "\tcase 0: /* DLL unloaded */\n" "\t\tbreak;\n" + "\t}\n" + "\treturn 1;\n" "}\n" ); fprintf(c_output, @@ -789,6 +1042,18 @@ int main(int argc, char **argv) { memmove(attr, p, (size_t)(statement + strlen(statement) + 1 - p)); } } + { + /* remove MSVC calling conventions */ + char *cc = strstr(statement, "__cdecl"); + if (cc) + memmove(cc, cc + 7, (size_t)(statement + strlen(statement) + 1 - (cc+7))); + cc = strstr(statement, "__stdcall"); + if (cc) + memmove(cc, cc + 9, (size_t)(statement + strlen(statement) + 1 - (cc+9))); + cc = strstr(statement, "__fastcall"); + if (cc) + memmove(cc, cc + 10, (size_t)(statement + strlen(statement) + 1 - (cc+10))); + } } if ( @@ -807,7 +1072,7 @@ int main(int argc, char **argv) { /* not a function declaration */ } else { /* possibly a function declaration */ - char *func_name = statement, *func_name_end; + char *func_name = statement, *func_name_end = 0; if (strncmp(func_name, "const ", 6) == 0) func_name += 6; while (is_ident(*func_name)) ++func_name; if (*func_name == ' ') ++func_name; @@ -839,7 +1104,7 @@ int main(int argc, char **argv) { } else { entry->declared = 1; fprintf(c_output, - "typedef %.*s (*PTR_%.*s)%.*s;\n" + "typedef %.*s (*DLSUB_CALL PTR_%.*s)%.*s;\n" "PTR_%.*s REAL_%.*s;\n", (int)(func_name - statement), statement, @@ -866,7 +1131,21 @@ int main(int argc, char **argv) { const SymbolHashEntry *entry; const char *symbol; - fprintf(nasm_output, "default rel\n"); + fputs("default rel\n" + "%ifidn __OUTPUT_FORMAT__, elf64\n" + "\t%define FUNCPTR(p) [p wrt ..gotpc]\n" + "\t%macro GLOBAL 1\n" + "\t\tglobal %1 %+ :function\n" + "\t%endmacro\n" + "%else\n" + "\t%define FUNCPTR(p) p\n" + "\t%macro GLOBAL 1\n" + "\t\tglobal %1\n" + "\t\texport %1\n" + "\t%endmacro\n" + "%endif\n", + nasm_output); + for (s = 0; s < all_symbols.n_entries; ++s) { entry = all_symbols.entries[s]; @@ -890,7 +1169,7 @@ int main(int argc, char **argv) { symbol = entry->symbol; fprintf(c_output, "\tREAL_%s = (%s%s)DLSUB_GET_SYM(handle, \"%s\");\n", symbol, entry->declared ? "PTR_" : "void (*)(void)", entry->declared ? symbol : "", symbol); - fprintf(nasm_output, "global %s:function\n", symbol); + fprintf(nasm_output, "GLOBAL %s\n", symbol); } @@ -898,7 +1177,7 @@ int main(int argc, char **argv) { entry = all_symbols.entries[s]; if (!entry) continue; symbol = entry->symbol; - fprintf(nasm_output, "%s: mov r11, [REAL_%s wrt ..gotpc]\njmp [r11]\n", symbol, symbol); + fprintf(nasm_output, "%s: mov r11, FUNCPTR(REAL_%s)\njmp [r11]\n", symbol, symbol); } } diff --git a/make.bat b/make.bat new file mode 100644 index 0000000..081bfa2 --- /dev/null +++ b/make.bat @@ -0,0 +1,6 @@ +@echo off +if "%VCVARS%" == "" ( + set "VCVARS=1" + call vcvarsall x64 +) +cl /O2 /Zi /DEBUG /nologo /wd4996 /wd4706 /W4 main.c /Fe:dlsub.exe -- cgit v1.2.3