Cheok Yan Cheng
Cheok Yan Cheng

Reputation: 42642

API Hooking which takes effect across entire process - both EXE and DLLs

I have an application consists of a single EXE and multiple DLLs. After reading Windows via C/C++, I try to perform hook on Sleep function in one of the DLL, and expecting the hook will work across both EXE and all DLLs. Note that, CAPIHook code is getting from Windows via C/C++'s sample code

In DLL Project

void WINAPI MySleep( DWORD dwMilliseconds );
CAPIHook g_Sleep("Kernel32.dll", "Sleep", (PROC)MySleep);
typedef void (WINAPI *Sleep_Type)( DWORD dwMilliseconds );

// Hook function.
void WINAPI MySleep( DWORD dwMilliseconds )
{
    printf ("-------> In MySleep\n");
    ((Sleep_Type)(PROC)g_Sleep)(dwMilliseconds);

}

// This is an example of an exported function.
DLL_API int dll_function_which_is_going_to_call_sleep(void)
{
    printf ("DLL function being called\n");
    printf ("Call Sleep in DLL function\n");
    Sleep(100);

    return 42;
}

In EXE Project

void CexeDlg::OnBnClickedButton1()
{
    // TODO: Add your control notification handler code here
    printf ("Button being clicked\n");

    printf ("Call Sleep in EXE function\n");
    Sleep(100);

    dll_function_which_is_going_to_call_sleep();

    printf ("Call Sleep in EXE function\n");
    Sleep(100);

    dll_function_which_is_going_to_call_sleep();
}

This is the output I am getting

Button being clicked
Call Sleep in EXE function
-------> In MySleep
DLL function being called
Call Sleep in DLL function
Call Sleep in EXE function
-------> In MySleep
DLL function being called
Call Sleep in DLL function

What make me feel strange is that, I am expecting CAPIHook will take effect across entire single process. Since EXE and DLLs belong to a same process, both should be able to reach MySleep. However, my observation is that, only call from EXE will reach MySleep, but not DLL.

I locate sample code right here CAPIHook-doesnt-have-effect-in-entire-process.zip, it contains dll and exe projects.

I also once drop in replace CHookAPI with code in apihijack. Same problem still happen. The hooking effect will not spread across entire process.

Is there anything I had missed out? Please do not suggest me to use EasyHook, Detours, ..., as I just want to know why the above code won't work, and how I can fix it.

Upvotes: 4

Views: 3280

Answers (1)

YeenFei
YeenFei

Reputation: 3208

This is because the original CAPIHook does not replace local IAT (in your case, the DLL project which contains binaries for CAPIHook).

The reason behind this was to protect itself from infinite recursion which lead to stackoverflow (which the users will also post question in SO :D).

To ensure that any subsequent modules loaded will be importing the "correct" function,
CAPIHook search and re-direct LoadLibrary and GetProcAddress upon construction.

However, these function are used by CAPIHook itself too, so changing local IAT to proxy function (CAPIHook::LoadLibrary or CAPIHook::GetProcAddress) will cause infinite recursion as the proxies unintentionally called itself while trying to call underlying OS API !


One way to solve this is by modifying CAPIHook to check whether it is alright to replace local IAT.

1.) New attribute m_bIncludeLocalIAT added to CAPIHook and ctor/dtor modified accordingly.

class CAPIHook
{
...
CAPIHook(PSTR pszCalleeModName, PSTR pszFuncName, 
         PROC pfnHook, BOOL bIncludeLocalIAT = TRUE);
...
BOOL m_bIncludeLocalIAT;
...
};


CAPIHook::CAPIHook( PSTR pszCalleeModName, PSTR pszFuncName, 
                    PROC pfnHook, BOOL bIncludeLocalIAT) {
    ...
    m_bIncludeLocalIAT = bIncludeLocalIAT;
    ...
    ReplaceIATEntryInAllMods(m_pszCalleeModName, m_pfnOrig, m_pfnHook, m_bIncludeLocalIAT);
}

CAPIHook::~CAPIHook() {
    ReplaceIATEntryInAllMods(m_pszCalleeModName, m_pfnHook, m_pfnOrig, m_bIncludeLocalIAT);
    ...
}

2.) New parameter added to the static function CAPIHook::ReplaceIATEntryInAllMods.

static void WINAPI ReplaceIATEntryInAllMods(PCSTR pszCalleeModName, 
      PROC pfnOrig, PROC pfnHook, BOOL bReplaceLocalIAT){

   HMODULE hmodThisMod = ExcludeAPIHookMod 
      ? ModuleFromAddress(ReplaceIATEntryInAllMods) : NULL;

   // Get the list of modules in this process
   CToolhelp th(TH32CS_SNAPMODULE, GetCurrentProcessId());

   MODULEENTRY32 me = { sizeof(me) };
   for (BOOL bOk = th.ModuleFirst(&me); bOk; bOk = th.ModuleNext(&me)) {

      if (bReplaceLocalIAT || (me.hModule != hmodThisMod)) {

         // Hook this function in this module
         ReplaceIATEntryInOneMod(
            pszCalleeModName, pfnCurrent, pfnNew, me.hModule);
      }
   }
}

3.) Update the static CAPIHook instances

CAPIHook CAPIHook::sm_LoadLibraryA  ("Kernel32.dll", "LoadLibraryA",   
   (PROC) CAPIHook::LoadLibraryA, FALSE);

CAPIHook CAPIHook::sm_LoadLibraryW  ("Kernel32.dll", "LoadLibraryW",   
   (PROC) CAPIHook::LoadLibraryW, FALSE);

CAPIHook CAPIHook::sm_LoadLibraryExA("Kernel32.dll", "LoadLibraryExA", 
   (PROC) CAPIHook::LoadLibraryExA, FALSE);

CAPIHook CAPIHook::sm_LoadLibraryExW("Kernel32.dll", "LoadLibraryExW", 
   (PROC) CAPIHook::LoadLibraryExW, FALSE);

CAPIHook CAPIHook::sm_GetProcAddress("Kernel32.dll", "GetProcAddress", 
   (PROC) CAPIHook::GetProcAddress, FALSE);

Upvotes: 4

Related Questions