ramensoftware / windhawk

The customization marketplace for Windows programs: https://windhawk.net/
https://windhawk.net
GNU General Public License v3.0
1.06k stars 28 forks source link

Early injection helper #197

Open valinet opened 3 weeks ago

valinet commented 3 weeks ago

Hi

First of all, I have to admit, yeah, I am a really big fan of this project. Good job, and hats off - from UI, mod deployment to coding and injection, everything is state of the art so to say - textbook example. Really glad to see this open sourced and so well maintained, congratulations.

Now, I have a small question: I have read the technicality behind this. Indeed, Windows does not have any proper hook/in time notification mechanism for when a new process is created, short of writing a kernel mode driver. I mean, that's a possibility as well, idk, I am curious to hear your opinion on this but it is not my main question today.

The main question is, so to say, how to help Windhawk more effectively inject these processes that it injects late? For example, since services.exe cannot be injected (it being PPL WinTcb), usually Windhawk fails to inject in time for most services, which is not that great. Now, depending on what we want to inject, there could be workarounds. For one of my needs, I have written a proxy stub forwarder DLL that I place in the same folder, beside the target executable. Because of DLL load order, my custom proxy DLL is loaded, which forwards all exported functions of the original to the original DLL via a symlink in the same folder to the original DLL. Now, my question would be, how to "help" Windhawk properly with this DLL? Right now, what I do is simply Sleep(1000) there, which gives enough time for Windhawk to pick up the new process, inject it and still have the process in a pristine state, since most threads haven't stated yet, while the loader still loads imported DLLs in the executable.

But like, Sleep(1000) is hacky. Do you think a mechanism for somehow notifying Windhawk to trigger its internal "scan for processes/inject new processes" thing would make sense (something simple, like a named event, for example?). Another possibility I think would be to load windhawk.dll in process and call the InjectInit export, I think? Although yeah, the way it is right now, the path to windhawk.dll is not that easily obtainable, maybe add some registry path that specifies it? Otherwise, FindFirstFile & co for listing directories in Windhawk's Engine folder in reverse alphabetical order and picking the first entry? What's your recommmendation on this use case, any advice or direction for making such functionality official?

Again, thank you for this great product.

@valinet

m417z commented 3 weeks ago

Hi Valentin,

Thank you for the great feedback. I spent a lot of time and effort in Windhawk, I'm happy to see that it's appreciated!

I have to say that I'm also impressed by your projects, mainly Explorer Patcher but others too. I recommended it to some friends, many users, and mentioned it in several blog posts. Also, as you probably know, some bits of your work were ported to Windhawk mods.

Regarding your question:

writing a kernel mode driver. I mean, that's a possibility as well, idk, I am curious to hear your opinion on this but it is not my main question today.

The short answer, from my blog post: "I wanted Windhawk to be able to run even without administrator rights. And in general, I preferred to avoid installing a driver which is too intrusive to my taste and can affect the system's stability". Having to worry about the driver signature is yet another reason to avoid it. With all the downsides, I felt that it's not worth it.

since services.exe cannot be injected (it being PPL WinTcb)

Normally not, but people find workarounds from time to time, and Microsoft fixes them, sometimes quickly, sometimes not so much. Recently, I stumbled upon a new method: https://github.com/Slowerzs/PPLSystem. It's not sustainable for the long term but can be helpful depending on your goal.

Do you think a mechanism for somehow notifying Windhawk to trigger its internal "scan for processes/inject new processes" thing would make sense (something simple, like a named event, for example?)

Yes, I think it's a great idea, allowing for more customization opportunities without much effort or downsides.

The nifty thing about Windhawk is that instead of releasing a new version for testing a small fix or feature, I can create a mod :) So here's a small mod that adds this functionality, and some simple C++ code that demonstrates the usage of the new event: https://gist.github.com/m417z/5811d5eda32fc33b86c5053f2b54b16c

Note that I also added a mechanism for waiting for the Windhawk module to initialize by waiting for the CreateProcessInternalW hook to be placed. It's not very elegant, but should be fairly reliable. If I end up adding this feature to Windhawk, it'd probably be a good idea to add an event with the PID in the name that will be signaled when initialization is done.

One downside of this implementation is that the named event won't be available in sandboxed UWP apps. Not sure if the feature is even relevant for them.

Another possibility I think would be to load windhawk.dll in process and call the InjectInit export, I think?

That's possible in theory, but it wouldn't be very convenient as InjectInit expects to receive some data that has to be prepared for it.

the path to windhawk.dll is not that easily obtainable

The relative path to the up-to-date engine folder can be obtained from C:\Program Files\Windhawk\windhawk.ini. The reason it's not a fixed path is that upon an update or reinstall, some DLLs might be locked, e.g. because they're loaded in suspended processes. In this case, instead of asking for a reboot, a different folder is created.

valinet commented 1 week ago

Hi Michael,

First of all, thank you very much, such a module definitely helps address the issue.

Also, I apologize for the delayed reply, I took the time to play at lengths with this the past few days (weeks), even wrote a paper for a subject I am enrolled into about this late injection dilemma. I have basically tested a bunch of methods (AppInit_DLLs, SetWindowsHookEx, load order hijacking, using the AppVerifier infrastructure, driver) and have looked into a bunch more (API set overrides, shims, job object notifications).

For my initial use case, I was using the load order hijacking method. But that is messy, since one needs to craft custom proxy DLLs and place them besides the targeted executable, which is not always doable (for example, when wanting to hook something specific from System32, like taskmgr.exe, but not everything in there). I have since came to the conclusion that the best methods for the "notifier" part of the solution (the thing that signals the WindhawkScanForProcesses event would be:

1. A custom DLL injected using the AppVerifier infrastructure.

This could work nicely with Windhawk. First of all, indeed, it would require administrative privileges to be configured, but that is expected and okay for a lot of use cases, including mine.

Windhawk could include a section in settings where users write the names of the executables they want injected with this method. Then, all Windhawk has to do is place a universal injection DLL in System32 and SysWOW64 and configure the desired applications set by the user to load that DLL, by specifying appropriate GlobalFlag and VerifierDlls for each of the apps in HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options. For example, if we want to hook test.exe using this, Windhawk has to set:

HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\test.exe:

That's it, it's one DLL for all such apps the user specifies in the list. Injection is done by Windows and happens very early in the process' lifetime, even before kernel32.dll or kernelbase.dll are loaded (and we don't need those from such a simple DLL if we write everything needed suing only calls from ntdll.dll). All that universal injection DLL then has to do is to signal the WindhawkScanForProcesses event and then wait for the patching to be done and that's it, as you described, it's job is done (ofc, the AppVerifier infrastructure is pretty extensive - Windows can set hooks for any function for us, it has a built in hooking engine when used like this, but yeah, that's another discussion). Busy waiting on that byte from CreateProcessInternal is fine for me, it's not that often anyway and could be calmed down using a bunch of alertable sleeps of some actual time anyway.

With this method, Windhawk subsequently injects the new process by creating a remote thread in it, which is expected, since the process has actually started running.

2. A custom driver that signals the event on receiving notifications for new process creation using PsSetCreateProcessNotifyRoutineEx

The title is self explanatory. This method, kind of expected, works best. Basically, it's a simple driver that subscribes to processes creation notifications and signals the WindhawkScanForProcesses event when the system creates a new process (calls our notification routine in the driver). If the event is reopened on each call, nothing bad can happen - if Windhawk is not running, the driver simply does not signal the event and that's it. The method is error prone, with the driver being lightweight and using only officially sanctioned mechanisms. The driver then does not even wait in the notification routine, it simply lets it flow. I have seen that, in practice, in my tests, notifying Windhawk that early (but we are guaranteed that the user space "sees" the new process when the system calls the notification routine in the driver) is enough for Windhawk to be able to inject the new process using an APC on the main thread even (!), so the process is there but hasn't even started yet, which is optimal if you ask me. In practice this behaves similarly to how Windhawk treats processes which it knows about because of the CreateProcessInternalW hooks. Indeed, here there is no check, no wait for Windhawk to patch before "letting go" in the notification callback, but in practice, in my tests, it did not seem to be an issue - each and every time such processes were then injected by Windhawk using the "APC method", so the window of opportunity there is very good.

An interesting situation could happen where the system is executing the driver notification routine, we opened the event but haven't yet set it and closed it, while on the user land side suppose Windhawk restarts just then. Then, suppose the kernel is still there, slow, hasn't yet closed the event, and Windhawk starts, it will try to create the event and yeah, I think it succeeds, and actually will reuse the old event (since the kernel hasn't yet closed it), which is actually fine, so yeah, don't think it is a problem, just an interesting case to think about. As I said, the mechanism is so simple that I do not see much going wrong with it - we definitely do not want drivers causing havoc, but here it definitely should not be the case.

Ofc, as we know, for drivers we need admin access to setup, plus, the elephant in the room, having it signed. While I personally run it just fine using ssde, it is not doable for everyone. Do you think such a driver has any chance of getting signed by Microsoft? I mean, any exe could come and set up such an event and then be notified about new process creation. Idk what is so bad regarding that (the fact that there is no such user space API is pretty infuriating tbh), but maybe it is against some rules...? Do you happen to own an EV certificate for code signing? I was thinking maybe getting one for myself, but yeah, haven't made up my mind, if you have any experience, I'd definitely appreciate some tips. Yeah, the code signing requirement may definitely bury this idea, yet still, interesting to look at nevertheless.

Examples for the 2 techniques described above are here: https://gist.github.com/valinet/0b61552b493079de1e3b4762378d352e

Also here a repo with an entire test setup I used for my paper: https://github.com/valinet/R2Work

Again, thanks for Windhawk, such a great tool, a joy to have on my side each and every day. Looking forward to hearing your input on this.

Valentin

m417z commented 1 day ago

A custom DLL injected using the AppVerifier infrastructure

I wanted to play with this method a while ago, but @namazso, the SecureUxTheme author, shared some negative experience with it, such as performance issues.

Here are some quotes:

Settings app slow [...] this is caused by app verifier enabling heap debugger

https://github.com/namazso/SecureUxTheme/issues/87

Slow log out/shutdown [...] the main performance penalty [...] from the application verifier's initialization [...] On my severily resource limited VM lockscreen without SecureUxTheme took 0.716 seconds to appear with background, while 1.200 seconds with SecureUxTheme

https://github.com/namazso/SecureUxTheme/issues/5

That made the method much less appealing.

I also wanted to play with shims, but haven't gotten the chance to do it. It seems like a powerful tool, but I'm not sure how usable is it for this scenario, how stable is it between Windows versions, etc.

A custom driver that signals the event on receiving notifications for new process creation using PsSetCreateProcessNotifyRoutineEx

a simple driver that subscribes to processes creation notifications

Interesting, it's impressive how small it is. When I thought about a driver, I had an injdrv-style driver in mind, which is much more complex.

in practice, in my tests, notifying Windhawk that early [...] is enough for Windhawk to be able to inject the new process using an APC on the main thread

That's great, but it's slightly concerning that it's not guaranteed, as the behavior might differ under a load, or with different hardware or Windows version. Still, I must say that it looks very appealing.

Also, perhaps running the event listener thread in high priority can make it even more likely to get Windhawk notified on time.

but we are guaranteed that the user space "sees" the new process when the system calls the notification routine in the driver

Are you saying it based on tests, or based on your understanding of the flow? Relying on tests here can be dangerous, too, since the behavior might differ if Windhawk is notified very early. If, when in PsSetCreateProcessNotifyRoutineEx, the process and its first thread are already accessible via NtGetNextProcess/NtGetNextThread, it should be fine.

While I personally run it just fine using ssde, it is not doable for everyone

Interesting, but yeah, probably not a method that can be used by a legit app users install.

Do you think such a driver has any chance of getting signed by Microsoft? I mean, any exe could come and set up such an event and then be notified about new process creation.

I can't say for sure, but I see no reason why not. I can't see how it can be misused. If an exploit can use it, it can probably also just busy loop to catch a process in time.

the fact that there is no such user space API is pretty infuriating tbh

Yeah, totally.

Do you happen to own an EV certificate for code signing?

No, only code singing.

Actually, my certificate is about to expire soon, and since there's a new policy that forces using a physical Hardware Security Module, I'm looking into Azure Trusted Signing which simplifies the certificate signing process. Unfortunately, it doesn't issue EV certificates.

I also thought, for such a small driver, perhaps we can ask some open source project to help and sign it for us, for example System Informer.

I'd be happy to integrate such a driver into Windhawk. The main reason Windhawk injects its dll into all processes is the early injection, but it inevitably causes incompatibilities with some programs. You can see some of them in the pinned issues here. Having the driver will allow Windhawk to only inject its dll into processes which are targeted by a mod, which will greatly reduce the likelihood of an incompatibility for most users.

namazso commented 1 day ago

Re: HSM keys for signing

You can actually use Azure Key Vault and https://github.com/namazso/AzuKI or AzureSignTool for code signing without a HSM

namazso commented 1 day ago

Also, regarding drivers, I have a “universal” driver signed that I could offer for usage, however it can’t do any callbacks.