5/31/2008

API Hooking on Windows Platform

DLL hooking is an useful technique when you are doing debugging, testing and hacking. In the following code, I will show how to hook a system API (GetCurrentProcessId()) called in a 3rd party DLL (MyNetwork.DLL).

The import section of a DLL is conceptually organized as:
  1 ///////////////////////////////////////////////////////////////////////////////
  2 // hooker.h
  3 ///////////////////////////////////////////////////////////////////////////////
  4 #include <windows.h>
  5
  6 class GetPidHooker
  7 {
  8 public:
  9    GetPidHooker(int nDiff);
 10    ~GetPidHooker();
 11
 12 private:
 13    static DWORD WINAPI HookedGetPid(void);
 14    void   HookIt(FARPROC pfnOld, FARPROC pfnNew);
 15
 16    typedef DWORD (*GetPIDFuncType)(void);
 17
 18 private:
 19    static int m_nDiff;
 20    static FARPROC m_pfnOrgMethod;
 21 };
 22
 23
 24 ///////////////////////////////////////////////////////////////////////////////
 25 // hooker.cpp
 26 ///////////////////////////////////////////////////////////////////////////////
 27 #include <windows.h>
 28 #include <dbghelp.h>
 29 #include <stdio.h>
 30 #include "hooker.h"
 31
 32 int GetPidHooker::m_nDiff = 0;
 33 FARPROC GetPidHooker::m_pfnOrgMethod = NULL;
 34
 35 GetPidHooker::GetPidHooker(int nDiff)
 36 {
 37    m_nDiff = nDiff;
 38
 39    // GetCurrentProcessId() is in kernel32.dll
 40    HMODULE hOldModule = GetModuleHandle(L"kernel32.dll");
 41    if (NULL == hOldModule)
 42    {
 43        printf("GetPidHooker:: Can't find MODULE - kernel32.dll!\n");
 44        return;
 45    }
 46    m_pfnOrgMethod = GetProcAddress(hOldModule, "GetCurrentProcessId");
 47    if (NULL == m_pfnOrgMethod)
 48    {
 49        printf("GetPidHooker:: Can't locate address of function - GetCurrentProcessID!\n");
 50        return;
 51    }
 52
 53    // Do the hooking
 54    FARPROC pfnNew = (FARPROC)HookedGetPid;
 55    HookIt(m_pfnOrgMethod, pfnNew);
 56 }
 57
 58 void GetPidHooker::HookIt(FARPROC pfnOld, FARPROC pfnNew)
 59 {
 60    // get the first Import Descriptor in Import Directory Table of MyNetwork.dll
 61    ULONG ulSize = 0;
 62    HMODULE hCallerModule = GetModuleHandle(L"MyNetwork.dll");
 63    if (NULL == hCallerModule)
 64    {
 65            printf("GetPidHooker:: can't find MODULE - MyNetwork.dll\n");
 66            return;
 67    }
 68    PIMAGE_IMPORT_DESCRIPTOR pImportDesc =
 69        (PIMAGE_IMPORT_DESCRIPTOR)ImageDirectoryEntryToData(
 70            hCallerModule,
 71            TRUE,
 72            IMAGE_DIRECTORY_ENTRY_IMPORT,
 73            &ulSize);
 74    if (NULL == pImportDesc)
 75    {
 76        printf("GetPidHooker:: Can't locate Import section of MyNetwork.dll!\n");
 77        return;
 78    }
 79
 80    // find KERNEL32.dll's Import Descriptor
 81    for (; pImportDesc->Name; pImportDesc++)
 82    {
 83        PSTR pszModName = (PSTR)((PBYTE)hCallerModule + pImportDesc->Name);
 84        //NOTE: PE format only use ascii chars
 85        if (0 == lstrcmpiA(pszModName, "kernel32.dll"))
 86        {
 87            break;
 88        }
 89    }
 90    if (0 == pImportDesc->Name)
 91    {
 92        printf("GetPidHooker:: Can't locate KERNEL32.dll in MyNetwork.dll's import section!\n");
 93        return;
 94    }
 95
 96    // loop to find the thunk for GetCurrentProcessId() in kernel32.dll's IAT
 97    PIMAGE_THUNK_DATA pThunk = (PIMAGE_THUNK_DATA)((PBYTE)hCallerModule + pImportDesc->FirstThunk);
 98    for (; pThunk->u1.Function; pThunk++)
 99    {
100        // Is this the function we're looking for?
101        if (*(FARPROC*)(&pThunk->u1.Function) == pfnOld)
102        {
103            // hook it - write new address, the old address has been saved elsewhere
104            WriteProcessMemory(
105                GetCurrentProcess(),
106                (LPVOID)(&pThunk->u1.Function),
107                &pfnNew,
108                sizeof(pfnNew),
109                NULL);
110            break;
111        }
112
113    }
114    if (0 == pThunk->u1.Function)
115    {
116        printf("GetPidHooker:: Can't locate specified address in MyNetwork.dll's import section!\n");
117    }
118 }
119
120 GetPidHooker::~GetPidHooker()
121 {
122    // restore the win32 api address in silknetworklib.dll's IAT.
123    FARPROC pfnOld = (FARPROC)HookedGetPid;
124
125    if (NULL != m_pfnOrgMethod)
126    {
127        HookIt(pfnOld, m_pfnOrgMethod);
128    }
129 }
130
131 DWORD GetPidHooker::HookedGetPid(void)
132 {
133    return ((*(GetPIDFuncType)m_pfnOrgMethod)() + m_nDiff);
134 }
135
136 ///////////////////////////////////////////////////////////////////////////////
137 //  main.cpp
138 ///////////////////////////////////////////////////////////////////////////////
139 #include <windows.h>
140 #include <stdio.h>
141 #include "hooker.h"
142 #include "MyNetwork.h"
143
144 int __cdecl main(int argc, const char** argv)
145 {
146     printf("Before Hook - current pid is %d\n", MyNetwork::GetCurPID());
147
148     {
149         GetPidHooker gHooker(5);  
150         printf("In Hook - current pid is %d\n", MyNetwork::GetCurPID());
151     }
152
153     printf("After Hook - current pid is %d\n", MyNetwork::GetCurPID());
154 }
155

NOTE:
1. Add dbghelp.lib as your addtional reference library.
2. Make sure that you are referencing MyNetwork.Dll and MyNetwork.dll calls GetCurrentProcessId() in its implementation.
3. Mem Pages that hold DLL data is copy-on-write, so it's safe when multiple processes share the same DLL. But my code here is not thread safe within one process, you can use any thread synchronization mechanism to ensure thread-safety.

full source code package:
http://code4cs.googlecode.com/files/DllHookWin.zip

No comments: