Post subject: Revisiting the hooking procedures
Warepire
He/Him
Editor
Joined: 3/2/2010
Posts: 2174
Location: A little to the left of nowhere (Sweden)
... Or: How Hourglass can use the Windows debugger API to do better. This write-up is going to get technical, there's no way around it, and no way for me write a tl;dr. You have been warned. After a lot of problems with how Hourglass hooked into the WinAPI that I encountered when I tried to implement memory management, and several discussions on IRC etc, it was decided that the current hooking method was never going to work good enough. It was simply, too slow, several DLLs had completed their DllMain()'s before hooking even began, and this cause more and more headaches. Heaps were set up etc already before it could be managed. The previous method of IAT patching was just not going to cut it anymore, so, a new method had to be found. After bouncing ideas with Nach and the #midipix IRC channel, among others, I was inspired to craft the solution presented here. I am truly sorry but I forgot who else inspired me for this solution, so yell at me about it so I know! Credits are deserved where credits are due. I was given some other potential solutions as well, which I need to point out, including Kernel level APCs (Async Procedure Calls). I found the chosen solution to be sufficient for my problem as it stands today. The solution I went with is to behave more like a real debugger does, and depend more on debug events than before. Almost all the information needed are given in the debug events, and given much earlier than any injected DLL can even begin to execute. This is where I put my focus, how can I inject a DLL as early as the process space setup (which is before code executes)? There's this handy debug event for this that triggers when the process has just been created, and it also freezes the process at just the right moment by itself (being a debug event and all). There's a similar event for each DLL the process pulls in. The missing information? Well, that's where in my own DLL the code is that I want the hooks to execute, the linker can help me with that part. I just had to tell it to generate map-files, which I then can parse, and know exactly what parts in my DLL I need to patch or refer to. Here I started looking at "How do I get my DLL into the remote process space when I can't load it using an API call?" as there is no way to load a DLL into a process that hasn't executed a single instruction of it's code. Not with the APIs at least... Enter the failed attempts: > Load the DLL as a file and just write it in the process space. --- This does not work, the DLL does not have the same structure in memory as it has as a file. Caused a lot of headaches with memory access violations and pointers not pointing at anything sane in memory. > Load the DLL into my own process and copy it to the process space. --- This does not work, the DLL does not handle this if the address in the remote memory is different from the local memory. Also came with a lot of issues regarding the copying itself, which usually never actually took place leaving me with uninitialized memory instead of a DLL to execute. What I needed was my own way of loading a DLL as the API call LoadLibrary loads it (except for the execution of DllMain()). This sent me on a very interesting adventure among PE and COFF specifications. These specifications define the layout of executable files for Windows. In order to load the DLL correctly, I had to learn how to parse these things. And that I did. I apologize in advance for the not very exciting picture (click to zoom): Here we see Hourglass running my little demo program that does nothing except create a window. In the background is where the exciting stuff is going on as that is the call stacks and debug messages exported by Hourglass when it's captured by Visual Studio. Pay attention at the top of the log where it says "MyCreateWindowExW: Hello from the hook!", this is not part of the demo program, but the hook I attached to the API call that creates the window, CreateWindowExW. I also need to specifically thank ais523 and Masterjun who gave me invaluable help when I broke the assembly patching of API calls: THANK YOU! Code is here for those who want to admire it, or be completely horrified by it: https://github.com/Warepire/Hourglass-Resurrection/tree/new_hooking_poc
Joined: 7/2/2007
Posts: 3960
This is some pretty deep magic. Good luck!
Pyrel - an open-source rewrite of the Angband roguelike game in Python.
Post subject: Hooking Magic
Masterjun
He/Him
Site Developer, Skilled player (1970)
Joined: 10/12/2010
Posts: 1179
Location: Germany
\o/
Warepire wrote:
This sent me on a very interesting adventure among PE and COFF specifications.
I can't help but imagine some good old exciting adventure, maybe climbing a mountain? ...with a climbing... ...hook? (I'm sorry)
Warning: Might glitch to credits I will finish this ACE soon as possible (or will I?)
Emulator Coder
Joined: 3/9/2004
Posts: 4588
Location: In his lab studying psychology to find new ways to torture TASers and forumers
I'm kind of surprised by some of the things you tried that wouldn't work. Due to relative addressing and virtual memory management, nothing other than properly parsing and offsetting memory references in the correct application's address space is going to work. I'm glad you got it figured out in the end! So, what's next on the agenda?
Warning: Opinions expressed by Nach or others in this post do not necessarily reflect the views, opinions, or position of Nach himself on the matter(s) being discussed therein.
Dwedit
He/Him
Joined: 3/24/2006
Posts: 692
Location: Chicago
So besides sticking the loadable sections from the DLL into process memory, and applying the fixes from the RELOC section, and calling the entry point, what else is there to do to manually load a DLL?
Editor, Expert player (2312)
Joined: 5/15/2007
Posts: 3855
Location: Germany
Good job! That sounds like some deep stuff indeed. But I can't really understand it, since I know next to nothing about what you had to deal with. I'm more curious what this progress is worth. Forgive me if my questions are going to be misplaced, but I'm curious. How close to done is the Hourglass-Resurrection project and will it be able to deal with higher level games? E.g. Might and Magic 8, Rollercoaster Tycoon, Anno 1602, Age of Empires Or will it stay at "simple game" level?
WST
She/Her
Active player (442)
Joined: 10/6/2011
Posts: 1690
Location: RU · ID · AM
Dwedit wrote:
So besides sticking the loadable sections from the DLL into process memory, and applying the fixes from the RELOC section, and calling the entry point, what else is there to do to manually load a DLL?
Your avatar is very cute.
S3&A [Amy amy%] improvement (with Evil_3D & kaan55) — currently in SPZ2 my TAS channel · If I ever come into your dream, I’ll be riding an eggship :)
Site Admin, Skilled player (1235)
Joined: 4/17/2010
Posts: 11264
Location: RU
Warning: When making decisions, I try to collect as much data as possible before actually deciding. I try to abstract away and see the principles behind real world events and people's opinions. I try to generalize them and turn into something clear and reusable. I hate depending on unpredictable and having to make lottery guesses. Any problem can be solved by systems thinking and acting.
Warepire
He/Him
Editor
Joined: 3/2/2010
Posts: 2174
Location: A little to the left of nowhere (Sweden)
Nach wrote:
I'm kind of surprised by some of the things you tried that wouldn't work. Due to relative addressing and virtual memory management, nothing other than properly parsing and offsetting memory references in the correct application's address space is going to work. I'm glad you got it figured out in the end! So, what's next on the agenda?
The reason the failed methods failed aren't so surprising to me now that I understand more of how Windows handles the DLL when it's being loaded. The next step is to design and implement an IPC so that the DLL can communicate with Hourglass, regardless of what is loaded. I have a pretty good idea already what to do here. Then it's time to migrate and re-structure the logic so that this method can be used officially.
Dwedit wrote:
So besides sticking the loadable sections from the DLL into process memory, and applying the fixes from the RELOC section, and calling the entry point, what else is there to do to manually load a DLL?
Registering the DLL with Windows so Windows knows it exists etc. This is most likely not really doable without running the actual LoadLibrary call. But for the purpose of Hourglass neither calling the entry point or registering the DLL with Windows is necessary.
MUGG wrote:
Good job! That sounds like some deep stuff indeed. But I can't really understand it, since I know next to nothing about what you had to deal with. I'm more curious what this progress is worth. Forgive me if my questions are going to be misplaced, but I'm curious. How close to done is the Hourglass-Resurrection project and will it be able to deal with higher level games? E.g. Might and Magic 8, Rollercoaster Tycoon, Anno 1602, Age of Empires Or will it stay at "simple game" level?
The project is about as close to done as it was when it started, due to the insane amount of work remaining. Half-Life is already kinda bootable, so is Red Faction, but the games are not playable as it is today. Goal is for everything to work, but as it stands today, that goal is FAR from realized,
WST wrote:
Dwedit wrote:
So besides sticking the loadable sections from the DLL into process memory, and applying the fixes from the RELOC section, and calling the entry point, what else is there to do to manually load a DLL?
Your avatar is very cute.
Thank you WST for adding value to this thread.
Dwedit
He/Him
Joined: 3/24/2006
Posts: 692
Location: Chicago
What about the alternate loading modes of LoadLibrary, such as DONT_RESOLVE_DLL_REFERENCES for LoadLibraryEx?
Active player (469)
Joined: 2/1/2014
Posts: 928
\o/ exciting!
Warepire
He/Him
Editor
Joined: 3/2/2010
Posts: 2174
Location: A little to the left of nowhere (Sweden)
Dwedit wrote:
What about the alternate loading modes of LoadLibrary, such as DONT_RESOLVE_DLL_REFERENCES for LoadLibraryEx?
The main issue is that neither API call can load the DLL in another process before that process executes code. My method does that.
Dwedit
He/Him
Joined: 3/24/2006
Posts: 692
Location: Chicago
What about an infinite loop at the entry point and CreateRemoteThread?
Warepire
He/Him
Editor
Joined: 3/2/2010
Posts: 2174
Location: A little to the left of nowhere (Sweden)
Dwedit wrote:
What about an infinite loop at the entry point and CreateRemoteThread?
ntdll.dll and kernel32.dll must have loaded and executed their DllMain's before LoadLibrary can be executed. Also CreateRemoteThread that instantiates any form of DLL loading is detected by most anti-virus programs as malware. Double no-go. You might be interested in joining #hourglass, it will be easier to answer your questions that way.