|Developer Documentation : .NET hooking|
- Hooking .NET from win32
- .NET Stack Walking
- .NET Exceptions
Hooking .NET from win32
To hook .NET function, we use the well known ICorProfilerCallback interface.
There's a lot of people which using the ICorProfilerInfo::SetILFunctionBody call, and ICorProfilerInfo2::SetEnterLeaveFunctionHooks2 to monitor.
1) ICorProfilerInfo2::SetEnterLeaveFunctionHooks2 is interesting, but it works only for binaries using .NET 2.0 or higher framework, and it is really slow as
each function of the framework must be rejitted to be logged.
2) SetILFunctionBody could be nice, but there's trouble hooking tiny functions, and as we already get an asm hooker, why not using it ?
So I've decided to use the ICorProfilerCallback::JITCompilationFinished callback : it's provides compiled IL code address and size; so there is no trouble using the WinAPIOverride hooking algorithm; and until you want to monitor framework function calls, you don't need to slow down your application.
The great advantage is that this way can be used on any framework.
So using this way we should be able to log binaries internal call with no changes on algorithms (and we still get functionalities of API monitoring like breaking, conditionnal logging...)
An interesting point with .NET assemblies are their auto descripting way : you don't need to manualy write monitoring file, this can be done automatically.
As I already say this way is really interesting, but... due to .NET stack walking, your hooked functions can fails (see next section)
.NET Stack Walking
To optimize speed, .NET frequently use /Oy like option, that means ebp is no use to parse stack.
So to do stack walking, framework should use another way than ebp content.
Notice : the following is not official documentation, this is what I think after tests
So framework must know functions using ebp or not, and for those not using ebp, where the return address is located (thanks to the stack size used by the function).
To do it, frameworks just
1) Get first return address
2) Find to which function return address belongs
3) Once the calling function has been found, retrieve calling function return address according to ebp or function stack size
4) goto 2) and continue until full stack has been parsed
This has 2 consequences :
- if hooks use stack, .NET stack walking will never succeed. (but as ebp is often use as standard register in .NET, hooks using stack must be forgotten)
- if no-stack hooking, as we hook return address, by replacing original return address by our after call function, .NET stack walking should fail (as our after call function is not known by .NET). The trick is to replace our ret to the original one the .NET needs it.
.NET do stack walking for 2 main purpose :
a) On exceptions : no troubles as we hook exceptions before .NET and ICorProfilerCallback::ExceptionCatcherEnter tells us when exception process (and so stack walking) ends.
b) On system security checking : in some circumstances, even these functions are not directly called, they can be called by subfunctions. We quite never know when it can occurs, so in this case you will get an exception if function or on of its callers is hooked.
To avoid this the only way we have is to use the slow ICorProfilerInfo::SetEnterLeaveFunctionHooks way (but we still have to used our hook as first version of SetEnterLeaveFunctionHooks doesn't provide any way to log parameters).
In our case we only use the "FunctionLeave" : instead of replacing ret address before calling the hooked function, we restore it only just before it returns.
So if a stack walking is done inside hooked function, .NET framework will see original return address, and the stack walking will be successful.
.NET use the default exception handler, that means if you do the following
call hooked .NET function;
your catch code will be reached before the .NET framework see the exception and try to catch it.
The consequences are the following : even if .Net function should catch the error, as our exception filter is checked sooner, we break the exception flow.
So on .NET exception, we have to check the ICorProfilerCallback::ExceptionSearchFunctionLeave and ICorProfilerCallback::ExceptionSearchCatcherFound
If ExceptionSearchFunctionLeave has been called for only one hooked function, that means the hooked functions has catched the error.