diff --git a/src/library/CMakeLists.txt b/src/library/CMakeLists.txt index 7f466b5..9a23db3 100644 --- a/src/library/CMakeLists.txt +++ b/src/library/CMakeLists.txt @@ -16,14 +16,14 @@ if (WIN32) file(GENERATE OUTPUT stub.def CONTENT "LIBRARY STUB\nEXPORTS\n${BOLT_STUB_ENTRYNAME} @${BOLT_STUB_ENTRYORDINAL}\n") file(GENERATE OUTPUT plugin.def CONTENT "LIBRARY BOLT-PLUGIN\nEXPORTS\n${BOLT_STUB_ENTRYNAME} @${BOLT_STUB_ENTRYORDINAL}\n") - add_library(${BOLT_PLUGIN_LIB_NAME} SHARED dll/main.c plugin/plugin.c gl.c + add_library(${BOLT_PLUGIN_LIB_NAME} SHARED dll/main.c dll/common.c plugin/plugin.c gl.c rwlock/rwlock_win32.c ipc_win32.c plugin/plugin_win32.c ../../modules/hashmap/hashmap.c ../miniz/miniz.c ../../modules/spng/spng/spng.c "${CMAKE_CURRENT_BINARY_DIR}/plugin.def") target_compile_definitions(${BOLT_PLUGIN_LIB_NAME} PUBLIC BOLT_STUB_ENTRYNAME=${BOLT_STUB_ENTRYNAME}) target_link_libraries(${BOLT_PLUGIN_LIB_NAME} PUBLIC "${BOLT_LUAJIT_DIR}/lua51.lib") target_include_directories(${BOLT_PLUGIN_LIB_NAME} PUBLIC "${BOLT_LUAJIT_DIR}" "${BOLT_ZLIB_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}/../miniz") - add_executable(dll_inject_generator dll/dll_inject_generator.cxx) + add_executable(dll_inject_generator dll/dll_inject_generator.cxx dll/common.c) target_compile_definitions(dll_inject_generator PUBLIC BOLT_STUB_ENTRYORDINAL=${BOLT_STUB_ENTRYORDINAL}) set_target_properties(dll_inject_generator PROPERTIES CXX_STANDARD 20 CXX_EXTENSIONS OFF) add_custom_command( @@ -32,9 +32,9 @@ if (WIN32) COMMAND dll_inject_generator $ "${BOLT_LUAJIT_DIR}/lua51.dll" "${CMAKE_CURRENT_SOURCE_DIR}/dll/dll_inject.h" ">dll_inject_cmake_gen.c" ) - add_library(stub SHARED dll/stub.c dll_inject_cmake_gen.c "${CMAKE_CURRENT_BINARY_DIR}/stub.def") + add_library(stub SHARED dll/stub.c dll/common.c dll_inject_cmake_gen.c "${CMAKE_CURRENT_BINARY_DIR}/stub.def") target_compile_definitions(stub PUBLIC BOLT_STUB_ENTRYNAME=${BOLT_STUB_ENTRYNAME}) - add_executable(stub_inject_generator dll/stub_inject_generator.cxx) + add_executable(stub_inject_generator dll/stub_inject_generator.cxx dll/common.c) target_compile_definitions(stub_inject_generator PUBLIC BOLT_STUB_ENTRYORDINAL=${BOLT_STUB_ENTRYORDINAL}) add_custom_command( OUTPUT stub_inject_cmake_gen.cxx diff --git a/src/library/dll/common.c b/src/library/dll/common.c new file mode 100644 index 0000000..112af75 --- /dev/null +++ b/src/library/dll/common.c @@ -0,0 +1,30 @@ +#include "common.h" + +BOOL bolt_cmp(const char* a1, const char* a2, BOOL case_sensitive) { + while (1) { + if (*a1 != *a2) { + if (case_sensitive || (*a2 < 'a' || *a2 > 'z')) return 0; + if (*a1 != *a2 - ('a' - 'A')) return 0; + } + if (!*a1) return 1; + a1 += 1; + a2 += 1; + } +} + +DWORD perms_for_characteristics(DWORD characteristics) { + const BOOL readp = (characteristics & IMAGE_SCN_MEM_READ); + const BOOL writep = (characteristics & IMAGE_SCN_MEM_WRITE); + const BOOL execp = (characteristics & IMAGE_SCN_MEM_EXECUTE); + return + readp ? + writep ? + execp ? PAGE_EXECUTE_READWRITE : PAGE_READWRITE + : + execp ? PAGE_EXECUTE_READ : PAGE_READONLY + : + writep ? + execp ? PAGE_EXECUTE_WRITECOPY : PAGE_WRITECOPY + : + execp ? PAGE_EXECUTE : PAGE_NOACCESS; +} diff --git a/src/library/dll/common.h b/src/library/dll/common.h new file mode 100644 index 0000000..d3e6733 --- /dev/null +++ b/src/library/dll/common.h @@ -0,0 +1,17 @@ +// for functions used by more than one DLL-related target +#include + +#if defined(__cplusplus) +extern "C" { +#endif + +/// take an nt section's "characteristics" and return the equivalent value for VirtualProtect +DWORD perms_for_characteristics(DWORD characteristics); + +/// compare two strings. +/// true if match, false if not. for case-insensitive mode a2 must be all lowercase. +BOOL bolt_cmp(const char*, const char*, BOOL case_sensitive); + +#if defined(__cplusplus) +} +#endif diff --git a/src/library/dll/dll_inject_generator.cxx b/src/library/dll/dll_inject_generator.cxx index 2f216bc..97abed4 100644 --- a/src/library/dll/dll_inject_generator.cxx +++ b/src/library/dll/dll_inject_generator.cxx @@ -1,25 +1,9 @@ #include "stub_inject.h" +#include "common.h" #include #define MAX(A, B) (((A) > (B)) ? (A) : (B)) -DWORD perms_for_characteristics(DWORD characteristics) { - const bool readp = (characteristics & IMAGE_SCN_MEM_READ); - const bool writep = (characteristics & IMAGE_SCN_MEM_WRITE); - const bool execp = (characteristics & IMAGE_SCN_MEM_EXECUTE); - return - readp ? - writep ? - execp ? PAGE_EXECUTE_READWRITE : PAGE_READWRITE - : - execp ? PAGE_EXECUTE_READ : PAGE_READONLY - : - writep ? - execp ? PAGE_EXECUTE_WRITECOPY : PAGE_WRITECOPY - : - execp ? PAGE_EXECUTE : PAGE_NOACCESS; -} - /// This program is run by the build system on Windows. It manually maps the plugin DLL, then outputs some C code which /// writes that DLL into a target process, invokes the payload on a remote thread, and blocks until that thread returns. /// The function generated by this code will be called by the stub DLL during a CreateProcessW hook. diff --git a/src/library/dll/main.c b/src/library/dll/main.c index 1391da3..2e87d75 100644 --- a/src/library/dll/main.c +++ b/src/library/dll/main.c @@ -1,6 +1,14 @@ #include +#include "common.h" #include "dll_inject.h" +typedef HMODULE(__stdcall* LOADLIBRARYW)(LPCWSTR); +typedef HMODULE(__stdcall* GETMODULEHANDLEW)(LPCWSTR); +typedef FARPROC(__stdcall* GETPROCADDRESS)(HMODULE, LPCSTR); +typedef BOOL(__stdcall* VIRTUALPROTECT)(LPVOID, SIZE_T, DWORD, PDWORD); + +static void resolve_imports(HMODULE, PIMAGE_IMPORT_DESCRIPTOR, HMODULE luajit, GETMODULEHANDLEW, GETPROCADDRESS, LOADLIBRARYW, VIRTUALPROTECT); + // this function is invoked via CreateRemoteThread by the stub dll (see dll_inject). // the plugin dll is injected into the suspended game process during a CreateProcessW hook, then it // calls this function, awaits its return, and finally resumes the process. @@ -9,5 +17,56 @@ // we can not only resolve our own IAT but also place hooks in the game's IAT. // note that it is not safe to use any libc function from this function. doing so will probably crash. DWORD __stdcall BOLT_STUB_ENTRYNAME(struct PluginInjectParams* data) { + LOADLIBRARYW pLoadLibraryW = (LOADLIBRARYW)data->pGetProcAddress(data->kernel32, "LoadLibraryW"); + VIRTUALPROTECT pVirtualProtect = (VIRTUALPROTECT)data->pGetProcAddress(data->kernel32, "VirtualProtect"); + + // before doing anything else, we need to sort out our import tables, starting with lua's. + // LoadLibrary would normally do this for us, but this DLL gets loaded by a manual map injector, not LoadLibrary. + resolve_imports(data->luajit, data->luajit_import_directory, data->luajit, data->pGetModuleHandleW, data->pGetProcAddress, pLoadLibraryW, pVirtualProtect); + resolve_imports(data->plugin, data->plugin_import_directory, data->luajit, data->pGetModuleHandleW, data->pGetProcAddress, pLoadLibraryW, pVirtualProtect); + return 0; } + +static void resolve_imports(HMODULE image_base, PIMAGE_IMPORT_DESCRIPTOR import_, HMODULE luajit, GETMODULEHANDLEW pGetModuleHandleW, GETPROCADDRESS pGetProcAddress, LOADLIBRARYW pLoadLibraryW, VIRTUALPROTECT pVirtualProtect) { + // 4096 appears to be the maximum allowed name of a DLL or exported function, though they don't expose a constant for it + WCHAR wname_buffer[4096]; + + PIMAGE_IMPORT_DESCRIPTOR import = import_; + while (import->Characteristics) { + PIMAGE_THUNK_DATA orig_first_thunk = (PIMAGE_THUNK_DATA)((LPBYTE)image_base + import->OriginalFirstThunk); + PIMAGE_THUNK_DATA first_thunk = (PIMAGE_THUNK_DATA)((LPBYTE)image_base + import->FirstThunk); + LPCSTR dll_name = (LPCSTR)((LPBYTE)image_base + import->Name); + HMODULE hmodule; + if (bolt_cmp(dll_name, "lua51.dll", 0)) { + hmodule = luajit; + } else { + size_t i = 0; + while (1) { + // this is not a great way of converting LPCSTR to LPCWSTR, but it's safe in this case + // because DLL names are ASCII. generally you should use MultiByteToWideChar(). + if (!(wname_buffer[i] = (WCHAR)dll_name[i])) break; + i += 1; + } + hmodule = pGetModuleHandleW(wname_buffer); + if (!hmodule) hmodule = pLoadLibraryW(wname_buffer); + } + + while (orig_first_thunk->u1.AddressOfData) { + DWORD oldp; + pVirtualProtect((LPVOID)(&first_thunk->u1.Function), sizeof(first_thunk->u1.Function), PAGE_READWRITE, &oldp); + if (orig_first_thunk->u1.Ordinal & IMAGE_ORDINAL_FLAG) { + // import by ordinal + first_thunk->u1.Function = (ULONGLONG)pGetProcAddress(hmodule, (LPCSTR)(orig_first_thunk->u1.Ordinal & 0xFFFF)); + } else { + // import by name + PIMAGE_IMPORT_BY_NAME pibn = (PIMAGE_IMPORT_BY_NAME)((LPBYTE)image_base + orig_first_thunk->u1.AddressOfData); + first_thunk->u1.Function = (ULONGLONG)pGetProcAddress(hmodule, (LPCSTR)pibn->Name); + } + pVirtualProtect((LPVOID)(&first_thunk->u1.Function), sizeof(first_thunk->u1.Function), oldp, &oldp); + orig_first_thunk += 1; + first_thunk += 1; + } + import += 1; + } +} diff --git a/src/library/dll/stub.c b/src/library/dll/stub.c index 727c0a5..7644af6 100644 --- a/src/library/dll/stub.c +++ b/src/library/dll/stub.c @@ -1,4 +1,5 @@ #include "stub_inject.h" +#include "common.h" #include "dll_inject.h" typedef BOOL(__stdcall* CREATEPROCESSW)(LPCWSTR, LPWSTR, LPSECURITY_ATTRIBUTES, LPSECURITY_ATTRIBUTES, BOOL, DWORD, LPVOID, LPCWSTR, LPSTARTUPINFOW, LPPROCESS_INFORMATION); @@ -12,19 +13,6 @@ BOOL hook_CreateProcessW(LPCWSTR, LPWSTR, LPSECURITY_ATTRIBUTES, LPSECURITY_ATTR struct StubInjectParams params; -// true if match, false if not, for case-insensitive mode a2 must be all lowercase -BOOL bolt_cmp(const char* a1, const char* a2, BOOL case_sensitive) { - while (1) { - if (*a1 != *a2) { - if (case_sensitive || (*a2 < 'a' || *a2 > 'z')) return 0; - if (*a1 != *a2 - ('a' - 'A')) return 0; - } - if (!*a1) return 1; - a1 += 1; - a2 += 1; - } -} - // this function is invoked via CreateRemoteThread by the manual map injector (see stub_inject) // after it injects it into the process. // it is not safe to call any imported functions from inside the stub dll, since the import table is diff --git a/src/library/dll/stub_inject_generator.cxx b/src/library/dll/stub_inject_generator.cxx index ee0c60b..a367158 100644 --- a/src/library/dll/stub_inject_generator.cxx +++ b/src/library/dll/stub_inject_generator.cxx @@ -1,4 +1,5 @@ #include "stub_inject.h" +#include "common.h" #include /// This program is run by the build system on Windows. It manually maps the stub DLL, then outputs some C++ code which @@ -108,20 +109,7 @@ int wmain(int argc, const wchar_t **argv) { // set rwx permissions for each section std::cout << "DWORD oldp;" << std::endl; for (size_t i = 0; i < stub_nt_headers->FileHeader.NumberOfSections; i += 1) { - const bool readp =(stub_section_header[i].Characteristics & IMAGE_SCN_MEM_READ); - const bool writep =(stub_section_header[i].Characteristics & IMAGE_SCN_MEM_WRITE); - const bool execp =(stub_section_header[i].Characteristics & IMAGE_SCN_MEM_EXECUTE); - const DWORD permission = - readp ? - writep ? - execp ? PAGE_EXECUTE_READWRITE : PAGE_READWRITE - : - execp ? PAGE_EXECUTE_READ : PAGE_READONLY - : - writep ? - execp ? PAGE_EXECUTE_WRITECOPY : PAGE_WRITECOPY - : - execp ? PAGE_EXECUTE : PAGE_NOACCESS; + const DWORD permission = perms_for_characteristics(stub_section_header[i].Characteristics); std::cout << "VirtualProtectEx(process, (LPVOID)(dll + " << stub_section_header[i].VirtualAddress << "), " << stub_section_header[i].Misc.VirtualSize << ", " << permission << ", &oldp);" << std::endl; } // invoke entrypoint