Support pop'n music 15-18
* popnhook1 for pop'n 15 - 18 has been added * popnio has been added * inject.exe has new syntax for loading hook DLLs, `real.dll=hook.dll`. This will trigger an early IAT hook where it will load the EXE suspended without resolving imports, replace the reference to real.dll in the import table with hook.dll, and then resolve everything before launching. This allows for ezusb.dll to be hooked properly. * launcher.exe also has a new early IAT hook feature now. Use `-I real.dll=hook.dll`. The idea is the same as described above for inject.exe. * Updated ezusb constant namings based on what is visible in ezusb.dll's debug statements. The launcher.exe implementation of early IAT hooking means that someone can implement popnhook2.dll for 19 and above. I have tried pop'n music Sunny Park using a modified version of popnhook1 and it seems to work to some degree: the I/O check and security check returns OK which means the ezusb hooking used in popnhook1 is also working for the later games using `launcher.exe -I ezusb.dll=ezusb2-popn-shim.dll ...`. The process is rather invasive (manually resolving all imports means more chances to fail) so it has been implemented in such a way that the launcher will work the same as it has before as long as `-I` isn't specified. One questionable thing I am not confident about is the `texture_usage_fix` hack flag I added in the conf. As the comment says, pop'n music 16 will work in Windows XP without the flag being set, but the game will immediately crash on later OSes without the flag being set in my experience. No other games had this issue in my experience. Enabling it in other games doesn't seem to have any negative effects.
This commit is contained in:
@@ -8,8 +8,8 @@ libs_inject := \
|
||||
util \
|
||||
|
||||
src_inject := \
|
||||
main.c \
|
||||
debugger.c \
|
||||
logger.c \
|
||||
main.c \
|
||||
options.c \
|
||||
version.c \
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
#include "util/signal.h"
|
||||
#include "util/str.h"
|
||||
|
||||
#define MM_ALLOCATION_GRANULARITY 0x10000
|
||||
|
||||
struct debugger_thread_params {
|
||||
const char *app_name;
|
||||
char *cmd_line;
|
||||
@@ -27,6 +29,14 @@ static HANDLE debugger_ready_event;
|
||||
|
||||
static PROCESS_INFORMATION pi;
|
||||
|
||||
static PVOID load_nt_header_from_process(HANDLE hProcess,
|
||||
HMODULE hModule,
|
||||
PIMAGE_NT_HEADERS32 pNtHeader);
|
||||
|
||||
static HMODULE enumerate_modules_in_process(HANDLE hProcess,
|
||||
HMODULE hModuleLast,
|
||||
PIMAGE_NT_HEADERS32 pNtHeader);
|
||||
|
||||
// Source:
|
||||
// https://docs.microsoft.com/en-us/windows/win32/memory/obtaining-a-file-name-from-a-file-handle
|
||||
static bool
|
||||
@@ -548,6 +558,156 @@ alloc_fail:
|
||||
return false;
|
||||
}
|
||||
|
||||
HRESULT debugger_pe_patch_remote(HANDLE hProcess, void *dest, const void *src, size_t nbytes)
|
||||
{
|
||||
DWORD old_protect;
|
||||
BOOL ok;
|
||||
|
||||
log_assert(dest != NULL);
|
||||
log_assert(src != NULL);
|
||||
|
||||
ok = VirtualProtectEx(
|
||||
hProcess,
|
||||
dest,
|
||||
nbytes,
|
||||
PAGE_EXECUTE_READWRITE,
|
||||
&old_protect);
|
||||
|
||||
if (!ok) {
|
||||
return HRESULT_FROM_WIN32(GetLastError());
|
||||
}
|
||||
|
||||
ok = WriteProcessMemory(
|
||||
hProcess, dest, src, nbytes, NULL);
|
||||
|
||||
if (!ok) {
|
||||
return HRESULT_FROM_WIN32(GetLastError());
|
||||
}
|
||||
|
||||
ok = VirtualProtectEx(
|
||||
hProcess,
|
||||
dest,
|
||||
nbytes,
|
||||
old_protect,
|
||||
&old_protect);
|
||||
|
||||
if (!ok) {
|
||||
return HRESULT_FROM_WIN32(GetLastError());
|
||||
}
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
bool debugger_replace_dll_iat(const char *expected_dll, const char *replacement_path_dll)
|
||||
{
|
||||
log_assert(expected_dll);
|
||||
log_assert(replacement_path_dll);
|
||||
|
||||
HMODULE hModule = NULL;
|
||||
HMODULE hLast = NULL;
|
||||
void *remote_addr;
|
||||
|
||||
// Find EXE base in process memory
|
||||
IMAGE_NT_HEADERS inh;
|
||||
for (;;) {
|
||||
memset(&inh, 0, sizeof(IMAGE_NT_HEADERS));
|
||||
|
||||
if ((hLast = enumerate_modules_in_process(pi.hProcess, hLast, &inh)) == NULL) {
|
||||
break;
|
||||
}
|
||||
|
||||
if ((inh.FileHeader.Characteristics & IMAGE_FILE_DLL) == 0) {
|
||||
hModule = hLast;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (hModule == NULL) {
|
||||
log_warning("Couldn't find target EXE for hooking");
|
||||
goto inject_fail;
|
||||
}
|
||||
|
||||
// Search through import table if it exists and replace the target DLL with our DLL filename
|
||||
PBYTE pbModule = (PBYTE)hModule;
|
||||
PIMAGE_SECTION_HEADER pRemoteSectionHeaders
|
||||
= (PIMAGE_SECTION_HEADER)((PBYTE)pbModule
|
||||
+ sizeof(inh.Signature)
|
||||
+ sizeof(inh.FileHeader)
|
||||
+ inh.FileHeader.SizeOfOptionalHeader);
|
||||
size_t total_size = inh.OptionalHeader.SizeOfHeaders;
|
||||
|
||||
IMAGE_SECTION_HEADER header;
|
||||
for (DWORD n = 0; n < inh.FileHeader.NumberOfSections; ++n) {
|
||||
if (!ReadProcessMemory(pi.hProcess, pRemoteSectionHeaders + n, &header, sizeof(header), NULL)) {
|
||||
log_warning("Couldn't read section header: %lu", GetLastError());
|
||||
goto inject_fail;
|
||||
}
|
||||
|
||||
size_t new_total_size = header.VirtualAddress + header.Misc.VirtualSize;
|
||||
if (new_total_size > total_size)
|
||||
total_size = new_total_size;
|
||||
}
|
||||
|
||||
remote_addr = VirtualAllocEx(
|
||||
pi.hProcess,
|
||||
pbModule + total_size + MM_ALLOCATION_GRANULARITY,
|
||||
strlen(replacement_path_dll) + 1,
|
||||
MEM_RESERVE | MEM_COMMIT,
|
||||
PAGE_READWRITE);
|
||||
|
||||
log_assert(remote_addr != NULL);
|
||||
|
||||
debugger_pe_patch_remote(
|
||||
pi.hProcess, remote_addr, replacement_path_dll, strlen(replacement_path_dll));
|
||||
|
||||
if (inh.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress != 0) {
|
||||
PIMAGE_IMPORT_DESCRIPTOR pImageImport = (PIMAGE_IMPORT_DESCRIPTOR)(pbModule
|
||||
+ inh.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress);
|
||||
|
||||
DWORD size = 0;
|
||||
while (inh.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].Size == 0
|
||||
|| size < inh.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].Size) {
|
||||
IMAGE_IMPORT_DESCRIPTOR ImageImport;
|
||||
if (!ReadProcessMemory(pi.hProcess, pImageImport, &ImageImport, sizeof(ImageImport), NULL)) {
|
||||
log_warning("Couldn't read import: %lu", GetLastError());
|
||||
goto inject_fail;
|
||||
}
|
||||
|
||||
if (ImageImport.Name == 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
char name[MAX_PATH] = {0};
|
||||
if (ReadProcessMemory(pi.hProcess, pbModule + ImageImport.Name, name, sizeof(name), NULL)) {
|
||||
// log_misc("\tImport DLL: %ld %s", ImageImport.Name, name);
|
||||
|
||||
if (strcmp(name, expected_dll) == 0) {
|
||||
//log_misc("Replacing %s with %s", name, replacement_path_dll, (void*)pImageImport);
|
||||
|
||||
ImageImport.Name = (DWORD)((PBYTE)remote_addr - pbModule);
|
||||
|
||||
debugger_pe_patch_remote(
|
||||
pi.hProcess,
|
||||
pImageImport,
|
||||
&ImageImport,
|
||||
sizeof(ImageImport));
|
||||
}
|
||||
}
|
||||
|
||||
pImageImport++;
|
||||
size += sizeof(IMAGE_IMPORT_DESCRIPTOR);
|
||||
}
|
||||
} else {
|
||||
log_warning("Couldn't find import table, can't hook DLL\n");
|
||||
goto inject_fail;
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
inject_fail:
|
||||
return false;
|
||||
}
|
||||
|
||||
bool debugger_resume_process()
|
||||
{
|
||||
log_info("Resuming remote process...");
|
||||
@@ -593,3 +753,90 @@ void debugger_finit(bool failure)
|
||||
CloseHandle(pi.hThread);
|
||||
CloseHandle(pi.hProcess);
|
||||
}
|
||||
|
||||
// Helper functions based on Microsoft Detours
|
||||
static PVOID load_nt_header_from_process(HANDLE hProcess,
|
||||
HMODULE hModule,
|
||||
PIMAGE_NT_HEADERS32 pNtHeader)
|
||||
{
|
||||
PBYTE pbModule = (PBYTE)hModule;
|
||||
|
||||
memset(pNtHeader, 0, sizeof(*pNtHeader));
|
||||
|
||||
if (pbModule == NULL) {
|
||||
SetLastError(ERROR_INVALID_PARAMETER);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
MEMORY_BASIC_INFORMATION mbi;
|
||||
memset(&mbi, 0, sizeof(mbi));
|
||||
|
||||
if (VirtualQueryEx(hProcess, hModule, &mbi, sizeof(mbi)) == 0) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
IMAGE_DOS_HEADER idh;
|
||||
if (!ReadProcessMemory(hProcess, pbModule, &idh, sizeof(idh), NULL)) {
|
||||
log_warning("Could not read DOS header: %lu", GetLastError());
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (idh.e_magic != IMAGE_DOS_SIGNATURE ||
|
||||
(DWORD)idh.e_lfanew > mbi.RegionSize ||
|
||||
(DWORD)idh.e_lfanew < sizeof(idh)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (!ReadProcessMemory(hProcess, pbModule + idh.e_lfanew,
|
||||
pNtHeader, sizeof(*pNtHeader), NULL)) {
|
||||
log_warning("Could not read NT header: %lu", GetLastError());
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (pNtHeader->Signature != IMAGE_NT_SIGNATURE) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return pbModule + idh.e_lfanew;
|
||||
}
|
||||
|
||||
static HMODULE enumerate_modules_in_process(HANDLE hProcess,
|
||||
HMODULE hModuleLast,
|
||||
PIMAGE_NT_HEADERS32 pNtHeader)
|
||||
{
|
||||
PBYTE pbLast = (PBYTE)hModuleLast + MM_ALLOCATION_GRANULARITY;
|
||||
|
||||
memset(pNtHeader, 0, sizeof(*pNtHeader));
|
||||
|
||||
MEMORY_BASIC_INFORMATION mbi;
|
||||
memset(&mbi, 0, sizeof(mbi));
|
||||
|
||||
// Find the next memory region that contains a mapped PE image.
|
||||
for (;; pbLast = (PBYTE)mbi.BaseAddress + mbi.RegionSize) {
|
||||
if (VirtualQueryEx(hProcess, (PVOID)pbLast, &mbi, sizeof(mbi)) == 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Usermode address space has such an unaligned region size always at the
|
||||
// end and only at the end.
|
||||
if ((mbi.RegionSize & 0xfff) == 0xfff) {
|
||||
break;
|
||||
}
|
||||
if (((PBYTE)mbi.BaseAddress + mbi.RegionSize) < pbLast) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Skip uncommitted regions and guard pages.
|
||||
if ((mbi.State != MEM_COMMIT) ||
|
||||
((mbi.Protect & 0xff) == PAGE_NOACCESS) ||
|
||||
(mbi.Protect & PAGE_GUARD)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (load_nt_header_from_process(hProcess, (HMODULE)pbLast, pNtHeader)) {
|
||||
return (HMODULE)pbLast;
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
+24
-14
@@ -5,11 +5,11 @@
|
||||
|
||||
/**
|
||||
* Initialize inject's logger backend.
|
||||
*
|
||||
*
|
||||
* This takes care of hooking and merging the different log
|
||||
* streams, e.g. inject's local logging and inject's debugger
|
||||
* receiving remote logging events.
|
||||
*
|
||||
*
|
||||
* @param log_file_path Path to the file to log to or NULL to
|
||||
* disable.
|
||||
*/
|
||||
@@ -17,21 +17,21 @@
|
||||
|
||||
/**
|
||||
* Initialize the debugger.
|
||||
*
|
||||
*
|
||||
* This creates the remote process of the provided application.
|
||||
*
|
||||
*
|
||||
* The remote process is created suspended. This allows you to
|
||||
* to some more process setup tasks like injecting hook DLLs
|
||||
* before you call debugger_resume_process to actually start
|
||||
* execution of it.
|
||||
*
|
||||
*
|
||||
* The actual debugging runs in a dedicated thread which spawns
|
||||
* the process, waits for and dispatches debug events.
|
||||
*
|
||||
*
|
||||
* However, if you want to attach a remote debugger, you have to
|
||||
* set the parameter local_debugger to false. Then, the debugger
|
||||
* will only create the remote process and monitor it.
|
||||
*
|
||||
*
|
||||
* @param local_debugger True to attach inject's local debugger,
|
||||
* false to allow attaching a remote
|
||||
* debugger with enhanced features.
|
||||
@@ -44,18 +44,28 @@ bool debugger_init(bool local_debugger, const char *app_name, char *cmd_line);
|
||||
|
||||
/**
|
||||
* Inject a DLL into the remote process.
|
||||
*
|
||||
*
|
||||
* @param path_dll Path to the dll to inject.
|
||||
* @return true if sucessful, false on error.
|
||||
*/
|
||||
bool debugger_inject_dll(const char *path_dll);
|
||||
|
||||
/**
|
||||
* Inject a DLL into the remote process by replacing its reference in
|
||||
* the import table.
|
||||
*
|
||||
* @param expected_dll Name of dll to override.
|
||||
* @param replacement_path_dll Name of dll to inject.
|
||||
* @return true if sucessful, false on error.
|
||||
*/
|
||||
bool debugger_replace_dll_iat(const char *expected_dll, const char *replacement_path_dll);
|
||||
|
||||
/**
|
||||
* Wait/block for a remote debugger to attach to the remote process.
|
||||
*
|
||||
*
|
||||
* You only need to call this if you specified local_debugger = false
|
||||
* on debugger_init.
|
||||
*
|
||||
*
|
||||
* @return True if successfull and a remote debugger attached, false
|
||||
* on error.
|
||||
*/
|
||||
@@ -63,21 +73,21 @@ bool debugger_wait_for_remote_debugger();
|
||||
|
||||
/**
|
||||
* Resume the remote process.
|
||||
*
|
||||
*
|
||||
* Make sure to call this once you are done with setting it up.
|
||||
*
|
||||
*
|
||||
* @return true on success, false on error.
|
||||
*/
|
||||
bool debugger_resume_process();
|
||||
|
||||
/**
|
||||
* Wait for the remote process to exit.
|
||||
*/
|
||||
*/
|
||||
void debugger_wait_process_exit();
|
||||
|
||||
/**
|
||||
* Cleanup the debugger.
|
||||
*
|
||||
*
|
||||
* @param failure Set this to true if you have to cleanup due to
|
||||
* a failure of another debugger function call.
|
||||
* Otherwise, set this to false.
|
||||
|
||||
+38
-2
@@ -80,8 +80,15 @@ verify_hook_dlls_exist(int argc, char **argv, uint32_t hook_dll_count)
|
||||
DWORD dll_path_length;
|
||||
|
||||
for (uint32_t i = 0; i < hook_dll_count; i++) {
|
||||
dll_path_length =
|
||||
SearchPath(NULL, argv[i + 1], NULL, MAX_PATH, dll_path, NULL);
|
||||
char *iat_hook = strstr(argv[i + 1], "=");
|
||||
|
||||
if (iat_hook) {
|
||||
dll_path_length =
|
||||
SearchPath(NULL, iat_hook + 1, NULL, MAX_PATH, dll_path, NULL);
|
||||
} else {
|
||||
dll_path_length =
|
||||
SearchPath(NULL, argv[i + 1], NULL, MAX_PATH, dll_path, NULL);
|
||||
}
|
||||
|
||||
if (dll_path_length == 0) {
|
||||
log_warning(
|
||||
@@ -94,6 +101,26 @@ verify_hook_dlls_exist(int argc, char **argv, uint32_t hook_dll_count)
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool inject_iat_hook_dlls(uint32_t hooks, char **argv)
|
||||
{
|
||||
log_assert(argv);
|
||||
|
||||
log_info("Injecting IAT hook DLLs...");
|
||||
|
||||
for (int i = 0; i < hooks; i++) {
|
||||
char *iat_hook = strstr(argv[i + 1], "=");
|
||||
|
||||
if (!iat_hook)
|
||||
continue;
|
||||
|
||||
*iat_hook = '\0';
|
||||
debugger_replace_dll_iat(argv[i + 1], iat_hook+1);
|
||||
*iat_hook = '=';
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool inject_hook_dlls(uint32_t hooks, char **argv)
|
||||
{
|
||||
log_assert(argv);
|
||||
@@ -101,6 +128,11 @@ static bool inject_hook_dlls(uint32_t hooks, char **argv)
|
||||
log_info("Injecting hook DLLs...");
|
||||
|
||||
for (int i = 0; i < hooks; i++) {
|
||||
char *iat_hook = strstr(argv[i + 1], "=");
|
||||
|
||||
if (iat_hook)
|
||||
continue;
|
||||
|
||||
if (!debugger_inject_dll(argv[i + 1])) {
|
||||
return false;
|
||||
}
|
||||
@@ -155,6 +187,10 @@ int main(int argc, char **argv)
|
||||
goto debugger_init_fail;
|
||||
}
|
||||
|
||||
if (!inject_iat_hook_dlls(hooks, argv)) {
|
||||
goto inject_hook_dlls_fail;
|
||||
}
|
||||
|
||||
if (!inject_hook_dlls(hooks, argv)) {
|
||||
goto inject_hook_dlls_fail;
|
||||
}
|
||||
|
||||
@@ -54,6 +54,8 @@ void options_print_usage(void)
|
||||
"You can specify one or multiple hook.dll files, e.g. inject.exe "
|
||||
"hook1.dll hook2.dll app.exe"
|
||||
"\n"
|
||||
"An IAT replacement early injection can be specified using the syntax orig.dll=hook.dll"
|
||||
"\n"
|
||||
" The following options can be specified after the exe path:\n"
|
||||
"\n"
|
||||
" -D Enable debugging output\n"
|
||||
|
||||
Reference in New Issue
Block a user