library: resolve import tables in dll entry

This commit is contained in:
Adam
2024-08-02 18:59:14 +01:00
parent f472c0aff6
commit fa038c7740
7 changed files with 114 additions and 48 deletions

View File

@@ -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 $<TARGET_FILE:${BOLT_PLUGIN_LIB_NAME}> "${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

30
src/library/dll/common.c Normal file
View File

@@ -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;
}

17
src/library/dll/common.h Normal file
View File

@@ -0,0 +1,17 @@
// for functions used by more than one DLL-related target
#include <Windows.h>
#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

View File

@@ -1,25 +1,9 @@
#include "stub_inject.h"
#include "common.h"
#include <iostream>
#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.

View File

@@ -1,6 +1,14 @@
#include <Windows.h>
#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;
}
}

View File

@@ -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

View File

@@ -1,4 +1,5 @@
#include "stub_inject.h"
#include "common.h"
#include <iostream>
/// 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