Moved from CodePlex
Originally submitted by taowen
*Environment: *
Windows Server 2003 R2 x64, Latest Service Pack
Projects:
- Host, managed console, who create target and inject guest
- Guest, managed dll, being inject to target
- Target, managed console, who just sleep three seconds and quit
Expected Behavior:
guest is loaded, and execute before target.
Actual Behavior:
guest is not loaded, and create and inject can not return until the target is finished.
Source Code attached. The log will appear in \helium-.txt
file attachments
bug.zip [x]
bug-bin.zip [x]
Comments
taowen wrote Sep 3, 2008 at 10:58 AM
binaries attached as well. Just execute host.exe, and you can see the logs similar to:
56:44: going to create target.exe and inject guest.dll to it
56:47: host experienced a error: System.ArgumentException: Process with an Id of 3212 is not running. at System.Diagnostics.Process.GetProcessById(Int32 processId, String machineName) at EasyHook.RemoteHooking.CreateAndInject(String InEXEPath, String InCommandLine, String InLibraryPath_x86, String InLibraryPath_x64, Int32& OutProcessId, Object[] InPassThruArgs) at host.Program.Main(String[] args) in C:\bug\host\Program.cs:line 21
The time difference is 3 seconds, it is because the create and inject wait the target sleep 3 seconds. and after create and inject, the target is exited, so that we can not get process by id.
wrote Sep 3, 2008 at 10:58 AM
taowen wrote Sep 3, 2008 at 11:10 AM
I just tested it again. Inject into unmanaged process, like: mspaint.exe, iexplorer.exe, no problem.
but inject to any managed process, no matter is console or GUI, it will always wait until the target finished, and guest not loaded.
so I doubt if the clr hosting api caused the issue. Or is it because the single threaded apartment?
maybe the only way would be inject unmanaged dll. but there is no example on how to use it yet. maybe I can work it out and post it back you.
taowen wrote Sep 3, 2008 at 11:14 AM
Also, if not use CreateAndInject
, but create(not suspended), then inject. it works like a charm. So, another solution would be using a managed loader process, let it wait for the hooks being installed, and load the target assembly on the fly, and use reflection to invoke its entry point (main function).
taowen wrote Sep 4, 2008 at 8:19 AM
just verified, injecting to unmanaged console is ok. So, the problem is just for injecting to any managed process.
taowen wrote Sep 8, 2008 at 10:27 AM
Just added some logs. It shows, the target process will not be executed until we create the remote thread. So to me, it seems like CreateRemoteThread triggered the target process execution. The exit code of the remote thread is 0, so the error code says: c++ routine completion routine succeed but didn't raise the event... in the log, we can not see the completion routine being executed at all. but the process explorer shows the EasyHook library is loaded into target process successfully...
ChristophHusse wrote Sep 8, 2008 at 3:58 PM
Your solution folders may be one issue. Do you have all the files "easyhook.dll" "easyhook32/64.dll" and "easyhook32/64Svc.exe" AND the host binary as well as the guest library in one folder???
So to me, it seems like CreateRemoteThread triggered the target process execution.
Puuh... I think I never tried CreateAndInject on managed processes BTW. But make sure that you are not using the stealth injection for CreateAndInject. I think also in the DOCs there is stated somewhere that this won't work. CreateStealthRemoteThread() requires an ACTIVE thread and there is none if you call CreateAndInject...
The exit code of the remote thread is 0, so the error code says: c++ routine completion routine succeed but didn't raise the event... in the log, we can not see the completion routine being executed at all.
The C++ completion routine has no logging by default?! Did you add one? After all your custom completion routine will never be executed from assembler code... But it is still weird that Null is returned. Null should never be returned...
Maybe in case of managed processes you should only use unmanaged injection AND an unmanaged library in case of CreateAndInject. The problem I see here is that the NET Framework probably interfers with the process creation so much, that some assumptions I made about it are just wrong...
Please check whether unmanaged injection AND unmanaged library will work for CreateAndInject for managed processes.
regards
chris
ChristophHusse wrote Sep 8, 2008 at 4:00 PM
Sorry, currently I have no windows installed... Could you give me the logs of your machine???
taowen wrote Sep 9, 2008 at 1:17 AM
I have all the things in one folder (all copied to a folder called "work"), including EasyHook32.dll EasyHook64.dll...
yes, I added the logs, it is using fopen fwrite to write to a txt file.
So, according to the log file, the HookCompleteInjection (entry.cpp) is not being called if I am creating and injecting to a managed process. I tried with Unmanaged hook, it is the same. As HookCompleteInjection not being called, no matter is managed or unmanaged. I thought it was the clr hosting api as well, it turned out not.
The interesting behavior is, If I do not CreateRemoteThread, the target managed process will not be executed as we created it as suspended state. But after CreateRemoteThread, the asm being executed (as I can see the EasyHook dll being loaded), and the target process (suspended primary thread) being executed as well. I don't know what happened at that time (I can not add log to asm code...). I would suspect the load library triggered the dll main caused some problem. BTW, injecting to running process is working properly, but that is not I want, I need the api being hook from the very beginning.
BTW, have you considered using SetThreadContext or NTQueueAPCThread to complement CreateRemoteThread (NtCreateThreadEx), or they two have very obvious limitation force us to use CreateRemoteThread? I would like to know which one is the best. Or, could we make them as strategy, and let the end-user choose which way to use?
BTW again, the db 3eh instruction is for what? why it is before jne in the trampoline asm code? I suspect it is for nothing, just to make the instruction two bytes long?
ChristophHusse wrote Sep 18, 2008 at 5:48 AM
Sorry for late response...
If it only doesn't work from the beginning, then it depends on the target... I can't see any problem with EasyHook because it works in other scenarios, for example when injecting a library into FireFox from the very beginning...
The 0x3e instruction is for something ;-). It will tell the processor that the jump is not taken or taken in general (I am not sure which) and this will speed up the whole thing, because the code prefetcher will prefetch the correct code section most of the time...
regards
chris
wrote Jan 31, 2009 at 8:50 PM
ChristophHusse wrote Jan 31, 2009 at 8:50 PM
I will try to fix this in the final version
wrote Feb 13, 2009 at 1:05 PM
ChristophHusse wrote Mar 7, 2009 at 1:48 AM
The problem is that NET seems to "adapt" the first running thread in a process. So if we start a suspended process, all things go as usual. But the moment when the remote thread is created, instead of executing the target invokation stub, NET seems to hijack this thread for its own purposes. Very funning thing ;-). I will now try to compensate this with another thread.
ChristophHusse wrote Mar 8, 2009 at 4:36 AM
REASON:
It wasn't that easy but after some debugging I found out that NET is hijacking
the first active thread in a process. And since CreateAndInject() is meant to
execute EasyHook in first place, NET hijacks the thread intended to run EasyHook.
So EasyHook is never executed and the host waits until the target terminates.
Then I tried to start another thread but the same problem occurred. It does not
matter how many suspended threads you create and which one you start, the first
active thread will always run the process instead of EasyHook!
WORKAROUND:
Fortunately, NET is a good child ;-). Just setup a wrapper process, install all your
hook either locally or remotely in that process. Remotely means to "inject" a dll
into the wrapper process which has the advantage that EasyHook simplifies the work
required to setup a proper IPC-Connection.
After all your hooks are installed into the wrapper, all you have to do is to load
the target process by calling "System.Reflection.Assembly.Load()", extract the
main() method and execute it via dynamic invoke. To all guys new to this invokation
stuff, just read some articles about NET reflection and dynamic invokation ;-).