https://bugs.winehq.org/show_bug.cgi?id=56968
Bug ID: 56968 Summary: Easyhook remote hooking does not work, breaking some game modding frameworks Product: Wine Version: unspecified Hardware: x86-64 OS: Linux Status: UNCONFIRMED Severity: normal Priority: P2 Component: kernel32 Assignee: wine-bugs@winehq.org Reporter: katharine.chui@gmail.com Distribution: ---
There exist game modding frameworks that uses EasyHook, for example, https://andrasteframework.github.io/content/1.0.0/index.html
EasyHook remote hooking uses ReadProcessMemory to fetch module handle and function addresses before using CreateRemoteThread to perform remote hooking with the fetched function addresses
On wine, this breaks at https://github.com/EasyHook/EasyHook/blob/16f641c8e2197b01095f548c94dcbe696a...
When trying to fetch export directory from remote process' kernel32.dll's PE header, ReadProcessMemory would succeed, eliminating the fallback codepath outright, but the ExportDirectory buffer would then get filled with 0s. With an export directory data structure filled with 0s, EasyHook would not be able to do much with CreateRemoteThread as functions fetched at https://github.com/EasyHook/EasyHook/blob/16f641c8e2197b01095f548c94dcbe696a... are all unavailable.
Patching the routine with a loop to loop until the function addresses can be fetched, it seems that it's not (just) a timing issue either because the loop just seems to go on forever.
Interestingly, through patching EasyHook itself and force the fallback code path at https://github.com/EasyHook/EasyHook/blob/16f641c8e2197b01095f548c94dcbe696a... which grabs export directory from PE NT headers, it can actually fetch an export directory, then eventually fetch the addresses of LoadLibraryW, FreeLibrary, GetProcAddress, ExitThread and GetLastError, but not VirtualFree and VirtualProtect
With the EasyHook patches and dotnet48 installed, it is currently enough to keep EasyHook going as it is now able to continue it's code injection and init routine with LoadLibraryW.
Would it be possible for remote processes to fetch export directory from remote PE headers? Would it be possible to fetch address to VirtualFree and VirtualProtect? Thanks!
https://bugs.winehq.org/show_bug.cgi?id=56968
Fabian Maurer dark.shadow4@web.de changed:
What |Removed |Added ---------------------------------------------------------------------------- CC| |dark.shadow4@web.de
--- Comment #1 from Fabian Maurer dark.shadow4@web.de --- Created attachment 76797 --> https://bugs.winehq.org/attachment.cgi?id=76797 Sample to reproduce the issue
Attaching a sample program made from EasyHook code
https://bugs.winehq.org/show_bug.cgi?id=56968
Fabian Maurer dark.shadow4@web.de changed:
What |Removed |Added ---------------------------------------------------------------------------- Status|UNCONFIRMED |NEW Keywords| |download, testcase Ever confirmed|0 |1
--- Comment #2 from Fabian Maurer dark.shadow4@web.de --- Confirming
https://bugs.winehq.org/show_bug.cgi?id=56968
--- Comment #3 from Fabian Maurer dark.shadow4@web.de --- Created attachment 76798 --> https://bugs.winehq.org/attachment.cgi?id=76798 Hack to work around the issue
Attaching a dirty hack that should make it work.
No idea why it would work that way, but apparently it does. Not sure about 64bit though...
https://bugs.winehq.org/show_bug.cgi?id=56968
--- Comment #4 from Dmitry Timoshkov dmitry@baikal.ru --- (In reply to Fabian Maurer from comment #3)
Created attachment 76798 [details] Hack to work around the issue
Attaching a dirty hack that should make it work.
+ void *ptr = &sec[i].VirtualAddress; Looks like a typo, '&' should not be needed.
Does your hack work if it's only done at the end of perform_relocations()? Probably when relocating a PE image the loader is also supposed to fix up VirtualAddress field in every section in the sections table.
Also NtProtectVirtualMemory() probably should be called only for the section table, not for every random section in the PE binary.
https://bugs.winehq.org/show_bug.cgi?id=56968
--- Comment #5 from Fabian Maurer dark.shadow4@web.de ---
void *ptr = &sec[i].VirtualAddress;
Looks like a typo, '&' should not be needed.
No, we need the address of where the VirtualAddress is stored, not where it points.
Does your hack work if it's only done at the end of perform_relocations()? Probably when relocating a PE image the loader is also supposed to fix up VirtualAddress field in every section in the sections table.
I suppose so, since we have "get_rva( module, sec[i].VirtualAddress );" before, otherwise that would need to be changed. But I only really tested the case without relocations.
Also NtProtectVirtualMemory() probably should be called only for the section table, not for every random section in the PE binary.
Probably, it's just a dirty hack. Not sure how it should exactly work, especially since on 64bit you can't put a pointer in that VirtualAddress DWORD.
https://bugs.winehq.org/show_bug.cgi?id=56968
--- Comment #6 from Dmitry Timoshkov dmitry@baikal.ru --- (In reply to Fabian Maurer from comment #5)
void *ptr = &sec[i].VirtualAddress;
Looks like a typo, '&' should not be needed.
No, we need the address of where the VirtualAddress is stored, not where it points.
Why do we need it and where? If you mean the NtProtectVirtualMemory() call, it's already done by passing '&ptr'.
https://bugs.winehq.org/show_bug.cgi?id=56968
--- Comment #7 from Dmitry Timoshkov dmitry@baikal.ru --- (In reply to Fabian Maurer from comment #5)
Does your hack work if it's only done at the end of perform_relocations()? Probably when relocating a PE image the loader is also supposed to fix up VirtualAddress field in every section in the sections table.
I suppose so, since we have "get_rva( module, sec[i].VirtualAddress );" before, otherwise that would need to be changed. But I only really tested the case without relocations.
In the case without relocations sec.VirtualAddress already contains correct address.
https://bugs.winehq.org/show_bug.cgi?id=56968
--- Comment #8 from Fabian Maurer dark.shadow4@web.de ---
Why do we need it and where? If you mean the NtProtectVirtualMemory() call, it's already done by passing '&ptr'.
Because I modify the "VirtualAddress" value here:
sec[i].VirtualAddress = (UINT_PTR)get_rva( module, sec[i].VirtualAddress );
but that is readonly, so I need to make it read-write.
So we now have
void *ptr = &sec[i].VirtualAddress;
which is the pointer to the address I want to modify. And since NtProtectVirtualMemory needs a pointer to a pointer I pass "&ptr".
In the case without relocations sec.VirtualAddress already contains correct address.
Sure, there is no relocations, but that programs expect the sec.VirtualAddress to not be an offset but an absolute value.
https://bugs.winehq.org/show_bug.cgi?id=56968
--- Comment #9 from Alexandre Julliard julliard@winehq.org --- (In reply to Fabian Maurer from comment #8)
Sure, there is no relocations, but that programs expect the sec.VirtualAddress to not be an offset but an absolute value.
sec.VirtualAddress is an RVA, not a pointer. That program is broken.
https://bugs.winehq.org/show_bug.cgi?id=56968
--- Comment #10 from Fabian Maurer dark.shadow4@web.de ---
sec.VirtualAddress is an RVA, not a pointer. That program is broken.
I thought maybe there is weird behavior in windows, but I retested against windows and the program only seems to get away with it because the windows dlls don't have a ".edata" section.
Not sure if we want to mimic that windows behavior to make that broken program happy.
https://bugs.winehq.org/show_bug.cgi?id=56968
Dario dario86@tutamail.com changed:
What |Removed |Added ---------------------------------------------------------------------------- CC| |dario86@tutamail.com
--- Comment #11 from Dario dario86@tutamail.com --- I think this might be related to issue https://bugs.winehq.org/show_bug.cgi?id=57395 although the attached patch does not work for that issue.
https://bugs.winehq.org/show_bug.cgi?id=56968
--- Comment #12 from Fabian Maurer dark.shadow4@web.de --- (In reply to Dario from comment #11)
I think this might be related to issue https://bugs.winehq.org/show_bug.cgi?id=57395 although the attached patch does not work for that issue.
Why do you think they are related?
https://bugs.winehq.org/show_bug.cgi?id=56968
--- Comment #13 from Dario dario86@tutamail.com --- «Resident Evil - Seamless HD Project» and «Resident Evil 2 - Seamless HD Project» do work while «Resident Evil 3 - Seamless HD Project» does not. Injecting DLLs of all three games only use a few methods from the standard library and Libwebp. The one that does not work though, additionally uses functions from KERNEL32.dll in its injecting DLL, namely «FlushInstructionCache», «FreeLibrary», «GetCurrentProcess», «LoadLibraryA» and «VirtualProtect».
All of the three look similar in their implementation, but in the one that does not work the methods for loading high definition textures are never executed.