views:

1393

answers:

5

We would like to hook calls to LoadLibrary in order to download assemblies that are not found. We have a handler for ResolveAssembly that handles the managed assemblies, but we also need to handle unmanaged assemblies.

We have attempted to hook LoadLibrary calls by re-writing the imports table via techniques specified in "Programming Applications for Microsoft Windows", but when we call WriteProcessMemory() we get a permission denied error (998). (Yes, we're running with elevated privs)

Has anyone succeeded in re-writing the imports table while the CLR is loaded? Can anyone point me in the right direction?

Update: We resolved the permission denied issue, but now when we iterate the Imports Table of a mixed assembly (managed + unmanaged), the only entry we find is mscoree.dll. Does anyone know how to find the native imports? (we're working in C++/CLI).

+5  A: 

Should work, but try using detours (or the free N-CodeHook) instead.
Detours is almost the de-facto way of instrumenting Win32 binaries.

Shay Erlichmen
... or you could use an open source lib like N-CodeHook (http://newgre.net/ncodehook) which also has 64bit support (you have to pay for X64 when using detours)
jn_
@jn: self promoting :) I will give ncodehook a try next time I need to deal with code injection although I must admit that detours has been working for me quite well. Have you thought about mirroring the API? so that existing detours apps can easily migrate?
Shay Erlichmen
Got me ;-) Actually no, I haven't thought of that. Will put it on my todo list. thx
jn_
A: 

Why don't you do the heavy-lifting for unmanaged stuff in (unmanaged) C++/CLI, exposing to .NET just the results (say a string containing a DLL name).

Dan
A: 

The best way would be to hook LoadLibrary/LoadLibraryEx, do the download if needed, and pass the downloaded file down the chain. However, I'd be worried about blocking the GUI during that download.

Joel Lucsy
+1  A: 

We resolved the specified issue via a call to VirtualProtect() prior to calling WriteProcessMemory() and then call it again afterwards to restore the protection levels. This temporarily removes the read-only protection for the memory where the IAT resides. This works well for us and resolves the issue for when LoadLibrary() is called.

Now if I can just figure out why LoadLibrary() is not called when an unmanaged assembly links against a lib (not a static lib)...

By the way, Detour and N-Code Hook both look like nice products and are most likely the way I should go, but I would like to avoid adding a 3rd party assembly if possible.

Todd Kobus
That's easy. You didn't hook all four variants (LoadLibraryA, LoadLibraryW, LoadLibraryExA, LoadLibraryExW).
Joshua
+4  A: 

I have successfully hooked from Managed code. However, I did it by injecting an unmanaged DLL into the remote process and have it rewrite the import table in DllMain. You may want to consider this method.

Here is my hooking function:

//structure of a function to hook
struct HookedFunction {
public:
    LPTSTR moduleName;
    LPTSTR functionName;
    LPVOID newfunc;
    LPVOID* oldfunc;
};

BOOL Hook(HMODULE Module, struct HookedFunction Function) {
    //parse dos header
    IMAGE_DOS_HEADER* dos_header = (IMAGE_DOS_HEADER*)Module;
    if (dos_header->e_magic != IMAGE_DOS_SIGNATURE) return 0; //not a dos program

    //parse nt header
    IMAGE_NT_HEADERS* nt_header = (IMAGE_NT_HEADERS*)(dos_header->e_lfanew + (SIZE_T)Module);
    if (nt_header->Signature != IMAGE_NT_SIGNATURE) return 0; //not a windows program

    //optional header (pretty much not optional)
    IMAGE_OPTIONAL_HEADER optional_header = nt_header->OptionalHeader;
    if (optional_header.Magic != IMAGE_NT_OPTIONAL_HDR32_MAGIC) return 0; //no optional header

    IMAGE_IMPORT_DESCRIPTOR* idt_address = (IMAGE_IMPORT_DESCRIPTOR*)(optional_header.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress + (SIZE_T)Module);
    if (!optional_header.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].Size) return 0; //no import table

    //enumerate the import dlls
    BOOL hooked = false;
    for(IMAGE_IMPORT_DESCRIPTOR* i = idt_address; i->Name != NULL; i++)
        //check the import filename
        if (!_stricmp(Function.moduleName, (char*)(i->Name + (SIZE_T)Module)))
            //enumerate imported functions for this dll
            for (int j = 0; *(j + (LPVOID*)(i->FirstThunk + (SIZE_T)Module)) != NULL; j++)
                //check if the function matches the function we are looking for
                if (!_stricmp(Function.functionName, (char*)(*(j + (SIZE_T*)(i->OriginalFirstThunk + (SIZE_T)Module)) + (SIZE_T)Module + 2) )) {
                    //replace the function
                    LPVOID* memloc = j + (LPVOID*)(i->FirstThunk + (SIZE_T)Module);
                    if (*memloc != Function.newfunc) { //not already hooked
                        DWORD oldrights;
                        DWORD newrights = PAGE_READWRITE;
                        VirtualProtect(memloc, sizeof(LPVOID), newrights, &oldrights);
                        if (Function.oldfunc && !*Function.oldfunc)
                            *Function.oldfunc = *memloc;
                        *memloc = Function.newfunc;
                        VirtualProtect(memloc, sizeof(LPVOID), oldrights, &newrights);
                    }
                    hooked = true;
                }

    return hooked;
}
Nick Whaley