Abstract. Solaris (as well as practically every other Unix dialect) provides a mechanism for pre-loading and executing of code of user choice upon process start-up, before the execution control is passed to program entry-point. This gives you an option to intercept calls to other shared libraries (most notably libc.so:-) and modify their behavior or simply study the way application interacts with the system. To make the story short I find this functionality essential enough to devote an effort to implementing similar one for Windows NT.
Basically there're two [viable] options for pre-loading code into Win32 application:
HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Windows\AppInit_DLLs (see Q197571: Working with the AppInit_DLLs Registry Value for details);
inject DLL after the program is started (by creating a thread in the target process context and tricking it to LoadLibrary, see Microsoft Systems Journal, "Load Your 32-bit DLL into Another Process's Address Space Using INJLIB," by Jeffrey Richter, May 1994: Vol. 9 No. 5).
I find neither method suitable for "every-day" usage. Indeed! AppInit_DLLs affects every single program (well, most of them, see below), not to mention that it's only read upon OS boot (i.e. you have to reboot each time you modify the value), which doesn't exactly facilitates the development... Injecting DLL after the program is started on the other hand might be just too late to be interesting, not to mention that it requires external wrapper program...
But what if we implement a compact, light-weight, dumb and robust DLL which we register as the only "AppInit_DLL" module and let this module pre-load other DLLs based on per-process environmental factors? I mean it could evaluate an environment variable (I picked %DLL_PRELOAD%:-), some per-executable registry key in HKCU (I picked Software\Microsoft\Windows NT\CurrentVersion\DLL_PRELOAD\*) and finally corresponding key in the HKLM hive and load listed DLLs... Of course you have to remember that AppInit_DLLs key is interpreted by USER32.DLL's initialization procedure. Meaning that the target application has to be linked with USER32.DLL (you can verify this by running DEPENDS.EXE or DUMPBIN.EXE /DEPENDENTS). This means that we can play the trick primarily on GUI applications, but for my purposes it was more than enough. Alternative could be to modify the executable and inject a reference to USER32.DLL... Note that I'm not referring to modification of the machine code, but only the import table where the libraries the executable is linked with are listed.
And so the journey began. First challenge. The DllMain reference page claims:
Warning The entry-point function (of our DLL_PRELOAD-ing module) should perform only simple initialization tasks. It must not call the LoadLibrary or LoadLibraryEx function (or a function that calls these functions), because this may create dependency loops in the DLL load order. This can result in a DLL being used before the system has executed its initialization code.
The first reason (dependency loops) is hardly real (DLLs are permitted to be linked with other DLLs and the run-time linker has to be able to deal with it), but the second part (running before internal data structures being initialized) is more than real and has to be respected. Unfortunately we don't know much about or have control over the order in wich DLLs are initialized (consult this MSJ article for additional information). In other words I can't just do whatever I was so excited about to do directly in DllMain and has to postpone it somehow...
As you might remember the idea is to pre-load libraries before execution control is passed to program entry point. Entry point! Unfortunately adjusting the pointer to it in PE header in memory doesn't work (at least not under W2K). Apparently the system makes a copy of the value before I get the chance to poke with it:-( So I had no other choice but to modify the code at entry point. I.e. replace the machine instructions at the program's entry point with a call to procedure of own design which upon return restores the original instructions and returns to the original entry point (yes, one more time). Or in other words:
DLL_PRELOAD.c | |
|
As Win64 compilers don't support inline assembler, you have to separately assemble and link in this assembler module into IA-64 binary and this one - into AMD64 one.
Security consideration! If you choose to deploy this module to enforce some per-application policy, do assign absolute path to the AppInit_DLLs value, e.g. C:\WINNT\DLL_PRELOAD.DLL, so that it can not be bypassed by another module with the same name residing elsewhere on the %PATH% or even in current working directory. I have to recommend C:\WINNT and not say C:\WINNT\system32 because of 32-char limitation on AppInit_DLLs value length. Windows XP and later offer alternative solution to this problem. If HKLM\System\CurrentControlSet\Control\Session Manager\SafeDllSearchMode is set to 1, then current working directory is searched after the system one. Yet another option is to register DLL_PRELOAD as a "Known DLL," in which case the module installed in the system directory shall be loaded. Special note about Win64. Keep in mind that you want native and legacy applications to load different modules, %SystemRoot%\system32\DLL_PRELOAD.DLL and %SystemRoot%\SysWOW64\DLL_PRELOAD.DLL respectively, so that special care has to be taken to make sure search pathes are separated.
To facilitate policy-oriented deployment, keys are actually evaluated in reverse order (opposite to one depicted in the beginning), in paticular HKLM is searched first. In addition if you terminate the key value in HKLM with -, e.g. C:\App\Policy.dll;-, then processing stops and no other modules will be preloaded. Well, another [untrusted] module loaded by application at later point might have an opportunity to bypass the policy, unless you explicitely address this problem in some manner, e.g. by making sure names passed to LoadLibrary are absolute and trusted pathes.
What's next? Well, just use your imagination:-) For example... Have you ever ran into a program that insists on opening a file read-write in place you don't want to relax ACL and it does it even if it has no intention to write to it? Intercept CreateFile and mask off the write bits! Ran into application with pre-compiled path to non-existing location? Rebias all references to a point in a file system (say C:\TMP to be redirected to %TEMP% for the target application) by overriding same API call. There is a catch though! The problem is that import tables, so called .idata segments, are private to DLLs and if we want to intercept all calls to certain API routine, then we have to examine and fix-up all these tables. The latter implies that all the modules has to be loaded and processed by run-time linker before we can proceed with our "surveillance" activities. Any other options? It should be noted that intercepting so called native API calls at KERNEL32 "level" is way more efficient. At least it's a single point to patch, namely reference to NtCreateFile in KERNEL32's .idata segment. Well, you would have to purchase an appropriate book to code such modules, but you do get excused from listing the loaded modules and spying on the LoadLibrary itself to cover up for the cases when the application in question (or a DLL on its behalf) dynamically loads extra DLLs during the course of execution. Anyway, here is a template module which simply logs the filenames being opened/created.
LogNtCreateFile.c | |
|
It's possible to bypass the filter by making an explicit native API call. If you strive to catch even such cases, see next module for suitable technic.
Here is another cool snippet demonstrating another sofisticated interception trick. This module so to say sells one-way VM tickets:-) It first modifies machine code at NtProtectVirtualMemory entry point, then sets up a filter rejecting all attempts to manipulate page access permissions on KERNEL32.DLL, NTDLL.DLL and itself and finally throws away the key. The latter means that when the module is done, not even the module itself will be able to tamper with filter, as it will require modification of page access permissions, while such attempts will be scuruplously rejected. Digest this:
VirtualProtect.c | |
|
Well, there actually is a simple way around it: just implement your own gate to NtProtectVirutalMemory's kernel-space counterpart. So that this module has to complemented with one making sure modules loaded with LoadLibrary are all trusted. Alternative is to design a kernel module which makes sure the system calls are originating from within NTDLL.DLL. It should be noted that this code can't be used together with LogNtCreateFile.c sample, latter causes former to [naturally!] crash at program exit. Well, last two snippets are basically templates assigned to free your imagination, not some kind of production code anyway:-)
As you might have noticed last module is not ported to Win64/IA-64 [yet]. There actually is a way to intercept [native] calls without modifying the machine code and coding an assembler shim. As already pointed out (in DLL_PRELOAD.c commentary) a pointer to a Win64 function is not an address of its first instruction, but a pointer to a structure comprising the pointer to instruction and accompanying global pointer value... Yes, I mean that it's perfectly possible to intercept a call by modifying this structure instead of the machine code. Unfortunately, internal calls, those made from within same .DLL, slips through, but on the pros side you can intercept external KERNEL32.DLL calls as effectively as native ones (recall the reason how did native API come into picture in first place).
A closing note. Some of you might wonder can't I
just wrap KERNEL32.DLL or NTDLL.DLL to intercept the calls? You know,
create a fake DLL which would redirect all the procedures to the
original library, but the
Happy Hacking...