Click here to Skip to main content
15,867,568 members
Articles / Desktop Programming / Win32

API Hooking with MS Detours

Rate me:
Please Sign up or sign in to vote.
4.93/5 (70 votes)
14 Oct 2008Ms-PL16 min read 513.9K   25.3K   254   88
In this article, I will talk about the theories and implementations of API hooking. API hooking is a powerful technique that allows someone to hijack a function and redirect it to a custom one. Anything can be done in these functions before passing control back to the original API.

Contents

  1. Introduction
  2. Getting Started: Traditional API Hooking
  3. Detours API Hooking
  4. DLL Injection
  5. Common Errors
  6. Conclusion

1. Introduction

In this article, I will be discussing the topic of API hooking. API hooking consists of intercepting a function call in a program and redirecting it to another function. By doing this, the parameters can be modified, the original program can be tricked if you choose to return an error code when really it should be successful, and so on. All of this is done before the real function is called, and in the end, after modifying/storing/extending the original function/parameters, control is handed back over to the original function until it is called again. This article requires an in-depth knowledge of C++ to fully comprehend. I will be using the Microsoft Detours Library, which is free to download. In order to successfully compile the code examples provided, you need to run the Makefile that comes with the Detours library and have it build the library files and everything else. Instructions for doing this can be found on the MSDN forums or elsewhere on the Internet. For the sake of space, the code samples posted in this article are uncommented, but have explanations that lead into them or follow from them. The code in the sample download is fully commented.

2. Getting Started: Traditional API Hooking

Before getting into the Detours API, I will discuss a traditional approach to API hooking by overwriting the address of a function with that of a custom one. This is just one of various methods of API hooking – others include modifying the Import Address Table (link provided later), using proxy DLLs and manifest files, hooking through loading drivers in the kernel address space, and so on. This technique that I will be using is rather rudimentary in the sense that the hooked API needs to be unhooked each time, which may cause conflicts with concurrency in multi-threaded programs. There is a way around this by allocating memory elsewhere for the original function and setting up a hook within the hook to prevent having to constantly rewrite the detour. I chose to just leave it with the hook/unhook method, for the sake of code and debugging simplicity. Granted this is not the best method as I’ve mentioned, but this article is about using MS Detours for API hooking, so it’s not really too important.

C++
#include <windows.h>

#define SIZE 6

typedef int (WINAPI *pMessageBoxW)(HWND, LPCWSTR, LPCWSTR, UINT);
int WINAPI MyMessageBoxW(HWND, LPCWSTR, LPCWSTR, UINT);

void BeginRedirect(LPVOID);

pMessageBoxW pOrigMBAddress = NULL;
BYTE oldBytes[SIZE] = {0};
BYTE JMP[SIZE] = {0};
DWORD oldProtect, myProtect = PAGE_EXECUTE_READWRITE;

INT APIENTRY DllMain(HMODULE hDLL, DWORD Reason, LPVOID Reserved)
{
    switch(Reason)
    {
    case DLL_PROCESS_ATTACH:
        pOrigMBAddress = (pMessageBoxW)
            GetProcAddress(GetModuleHandle("user32.dll"), 
                           "MessageBoxW");
        if(pOrigMBAddress != NULL)
            BeginRedirect(MyMessageBoxW);    
        break;
    case DLL_PROCESS_DETACH:
        memcpy(pOrigMBAddress, oldBytes, SIZE);
    case DLL_THREAD_ATTACH:
    case DLL_THREAD_DETACH:
        break;
    }
    return TRUE;
}

void BeginRedirect(LPVOID newFunction)
{
    BYTE tempJMP[SIZE] = {0xE9, 0x90, 0x90, 0x90, 0x90, 0xC3};
    memcpy(JMP, tempJMP, SIZE);
    DWORD JMPSize = ((DWORD)newFunction - (DWORD)pOrigMBAddress - 5);
    VirtualProtect((LPVOID)pOrigMBAddress, SIZE, 
                    PAGE_EXECUTE_READWRITE, &oldProtect);
    memcpy(oldBytes, pOrigMBAddress, SIZE);
    memcpy(&JMP[1], &JMPSize, 4);
    memcpy(pOrigMBAddress, JMP, SIZE);
    VirtualProtect((LPVOID)pOrigMBAddress, SIZE, oldProtect, NULL);
}

int  WINAPI MyMessageBoxW(HWND hWnd, LPCWSTR lpText, LPCWSTR lpCaption, UINT uiType)
{
    VirtualProtect((LPVOID)pOrigMBAddress, SIZE, myProtect, NULL);
    memcpy(pOrigMBAddress, oldBytes, SIZE);
    int retValue = MessageBoxW(hWnd, lpText, lpCaption, uiType);
    memcpy(pOrigMBAddress, JMP, SIZE);
    VirtualProtect((LPVOID)pOrigMBAddress, SIZE, oldProtect, NULL);
    return retValue;
}

This is the framework of a standard API hook. All of this resides in a DLL that will be injected into a process. For this example, I chose to hook the MessageBoxW function. Once this DLL is injected, it will (hopefully) get the address of the MessageBoXW function from user32.dll, and then the hooking begins. In the BeginRedirect function, an unconditional relative jump (JMP) opcode (0xE9) instruction will contain the distance to jump to. In terms of Assembly code, this looks something like:

ASM
JMP <distance>
RET

Doing the following in the BeginRedirect(…) function can help clarify this some more:

C++
sprintf_s(debugBuffer, 128, "pOrigMBAddress: %x", pOrigMBAddress);
OutputDebugString(debugBuffer);
..
memcpy(oldBytes, pOrigMBAddress, SIZE);
sprintf_s(debugBuffer, 128, "Old bytes: %x%x%x%x%x", oldBytes[0], oldBytes[1],
    oldBytes[2], oldBytes[3], oldBytes[4], oldBytes[5]);
OutputDebugString(debugBuffer);
..
memcpy(&JMP[1], &JMPSize, 4);
sprintf_s(debugBuffer, 128, "JMP: %x%x%x%x%x", JMP[0], JMP[1],
    JMP[2], JMP[3], JMP[4], JMP[5]);
OutputDebugString(debugBuffer);

Injecting the DLL and looking through DebugView:

funapihook/Overwrite.jpg

We see that before we set our API hook, the MessageBoxW flow of code starting at 0x7E466534 had the following five bytes: 8B, FF, 55, 8B, EC. After the memcpy(pOrigMBAddress, JMP, SIZE);, the flow of code would be the bytes that jump to our function (E9, A7, AC, B9, 91). So, every time the address 0x7E466534 (MessageBoxW) is called, the application will immediately jump to the address of the custom function. This is also where a problem is encountered with this particular technique of API hooking.

C++
int  WINAPI MyMessageBoxW(HWND hWnd, LPCWSTR lpText, LPCWSTR lpCaption, UINT uiType)
{
    OutputDebugString("In MyMessageBoxW");
//    VirtualProtect((LPVOID)pOrigMBAddress, SIZE, myProtect, NULL);
//    memcpy(pOrigMBAddress, oldBytes, SIZE);
    int retValue = MessageBoxW(hWnd, lpText, lpCaption, uiType);
//    memcpy(pOrigMBAddress, JMP, SIZE);
//    VirtualProtect((LPVOID)pOrigMBAddress, SIZE, oldProtect, NULL);
    return retValue;
}

Why exactly is it that the bytes need to be written back in and the function called? If you were to comment everything out (as I did above), inject this DLL, and have the process invoke a MessageBoxW call, this is what would happen:

funapihook/Loop.jpg

When you think about it and see what is happening, you see that you’re calling MessageBoxW in your custom function and returning the value. But, the problem is that MessageBoxW is redirecting itself to MyMessageBoxW, thereby causing MyMessageBoxW to basically be calling itself recursively with no end – an infinite loop. This is why the function unhooks itself. In order to actually function as a real MessageBoxW function, the overwritten bytes to our jump need to be written back. Then, the original function needs to be called and the return value saved. After that, writing the jump back in and returning the value will work. As a demonstration, I’ve added the line:

C++
MessageBoxW(NULL, L"This should pop up", L"Hooked MBW", MB_ICONEXCLAMATION);

after the line that copies the original bytes back into memory. I then tried this out with Notepad, which pops up a MessageBoxW when you search for text that can’t be found. The result?

funapihook/Hooked.jpg

That’s about it for this approach. Ideally, advanced methods such as Import Address Table (IAT) Hooking are better in the sense that the hook can stay in place and be removed any time we desire.

3. Detours API Hooking

The Microsoft Detours library works much the same way. From the overview:

"Detours is a library for intercepting arbitrary Win32 binary functions on x86 machines. Interception code is applied dynamically at runtime. Detours replaces the first few instructions of the target function with an unconditional jump to the user-provided detour function. Instructions from the target function are placed in a trampoline. The address of the trampoline is placed in a target pointer. The detour function can either replace the target function, or extend its semantics by invoking the target function as a subroutine through the target pointer to the trampoline."

So, this is sort of the way that was demonstrated above, albeit in a more sophisticated and elegant way. The function that drives all of this is the DetourAttach(…) function.

C++
LONG DetourAttach(
    PVOID * ppPointer,
    PVOID pDetour
    );

This is the function that is responsible for hooking the target API. The first parameter is a pointer to a pointer of the function that is to be detoured. The second one is a pointer to the function that will act as the detour. However, before the detouring begins, there are a few things that need to be done:

  • A detour transaction needs to be initiated.
  • A thread needs to be updated with the transaction.

This is easily done with the following calls:

  • DetourTransactionBegin()
  • DetourUpdateThread(GetCurrentThread())

After these two things are done, the detour is ready to be attached. After attaching, it is important to call DetourTransactionCommit() to make the detour go into effect and check for success or failure, if need be.

3.1 An Example: A Process Specific Packet Logger

As an example of API hooking with detours, I’m going to present a code sample that hooks the Winsock functions send(…) and recv(…). In these functions, I’m going to write the buffer that was sent or received to a log file before passing control over to the original function. An extremely important thing to note is that the function that is the detour function must have exactly the same calling convention and parameters as the function to be detoured. For example, the send function looks like this:

C++
int send(
  __in  SOCKET s,
  __in  const char *buf,
  __in  int len,
  __in  int flags
);

Therefore, the pointer to this function should look something like this:

C++
int (WINAPI *pSend)(SOCKET, const char*, int, int) = send;

It’s fine to set the pointer equal to the function; another way, which I use later, is to set the initial pointer to NULL and then use DetourFindFunction(…) to locate the address. Doing this for both send(…) and recv(…):

C++
int (WINAPI *pSend)(SOCKET s, const char* buf, int len, int flags) = send;
int WINAPI MySend(SOCKET s, const char* buf, int len, int flags);
int (WINAPI *pRecv)(SOCKET s, char* buf, int len, int flags) = recv;
int WINAPI MyRecv(SOCKET s, char* buf, int len, int flags);

Now, the functions to be hooked and the functions that they’re going to be redirected to are all defined. The use of WINAPI is there because these functions are exported under the __stdcall calling convention. Now, to get this thing started:

C++
INT APIENTRY DllMain(HMODULE hDLL, DWORD Reason, LPVOID Reserved)
{
    switch(Reason)
    {
        case DLL_PROCESS_ATTACH:
            DisableThreadLibraryCalls(hDLL);
            DetourTransactionBegin();
            DetourUpdateThread(GetCurrentThread());
            DetourAttach(&(PVOID&)pSend, MySend);
            if(DetourTransactionCommit() == NO_ERROR)
                OutputDebugString("send() detoured successfully");
            DetourTransactionBegin();
            DetourUpdateThread(GetCurrentThread());
            DetourAttach(&(PVOID&)pRecv, MyRecv);
            if(DetourTransactionCommit() == NO_ERROR)
                OutputDebugString("recv() detoured successfully");
            break;
            .
            .
            .

It basically starts off as I mentioned before – by initiating a detour transaction, updating the thread for the transaction, then carrying out the actual detour with DetourAttach(…). Finalization and error checking is done with the DetourTransactionCommit() function, which returns NO_ERROR upon a successful detour, or a specified error code upon failure. In the DetourAttach(…) function, the function to be detoured needs to be passed as a pointer to a pointer, so typecasting it as &(PVOID&) works. That’s basically it – it’s now ready to have a detour with MS Detours. Now, for the functions that will intercept send(…) and recv(…). In order to log the packets being sent and received in real time, I chose to open, write, and close the log files upon receiving or sending anything. I’m not sure if this is the best approach, but regardless. The functions that catch and write are defined as follows:

C++
int WINAPI MySend(SOCKET s, const char* buf, int len, int flags)
{
    fopen_s(&pSendLogFile, "C:\\SendLog.txt", "a+");
    fprintf(pSendLogFile, "%s\n", buf);
    fclose(pSendLogFile);
    return pSend(s, buf, len, flags);
}

int WINAPI MyRecv(SOCKET s, char* buf, int len, int flags)
{
    fopen_s(&pRecvLogFile, "C:\\RecvLog.txt", "a+");
    fprintf(pRecvLogFile, "%s\n", buf);
    fclose(pRecvLogFile);
    return pRecv(s, buf, len, flags);
}

with pRecvLogFile and pSendLogFile both being declared as FILE*. I chose to test this out on the “Internet Checkers” game that comes with Windows XP – both files were successful in capturing the data. In the detour function, it is important to note the return statements. Unlike the other approach that involved patching memory, Detours allows you to hand control back over to the program, simply by calling the function by address. There is no modification needed once the function has been detoured, the only thing that is required is to pass control back to the original function (or return something valid if you’re blocking the API from being processed).

3.2 A More Complicated Example: MSN Messenger

To show a more complicated example of what can be done with API hooking, I decided to expand the above example into something that will allow a user to send MSN instant messages to active sessions.

funapihook/client.jpg

By hooking the WSARecv(…) that MSN Messenger uses, it is possible to parse the email from the packet and also to store the number of the active SOCKET in the conversation. I decided to have a fun conversation with a chatbot (MSN: smarterchild@hotmail.com) and log some packets to see what the chat received looks like:

MSG smarterchild@hotmail.com 
-%20SmarterChild%20-%20*unicef%20contributing%20to%20charity 
137..MIME-Version: 1.0..Content-Type: text/plain; 
charset=UTF-8..X-MMS-IM-Format: FN=Courier%20New; 
EF=; CO=800000; CS=0; PF=22....:-D :-) :-)

The protocol is plaintext, which is great since there’s no need to reverse engineer any sort of encryption/decryption algorithms. The packet contains the email of the sender, some text flags, and the actual message itself. The MSN Messenger protocol is fully documented here; however, only plaintext messages are what’s going to be needed. What I did was check for certain attributes that are only in the received message packets. When all of these are found, it’s safe to start parsing out the email and storing the active session.

C++
if(strstr(lpBuffers->buf, "MSG ") != 0 && 
  (strstr(lpBuffers->buf, "MIME-Version") != 0 && 
   strstr(lpBuffers->buf, "X-MMS-IM-Format") != 0))
    ParseAndStoreEmail(socket, lpBuffers->buf);

lpBuffers is the LPWSABUF structure that is a parameter in WSARecv/WSASend. Once parsed, the email and active SOCKET sessions can be stored in two parallel vectors, which makes it easy to match and update later on.

C++
vector<string>emailList;
vector<socket>sessionList;
The parsing and storing function works like this
void ParseAndStoreEmail(SOCKET session, const char* buffer)
{
    string email;
    int i = 4; //4 to skip "MSG " part
    while(buffer[i] != ' ')
    {
        email += buffer[i];
        i++;
    }
    if(SearchForDuplicates(session, email.c_str()) != -1)
    {
        emailList.push_back(email);
        sessionList.push_back(session);
        SendDlgItemMessage(::g_hDlg, IDC_CBUSERS, CB_ADDSTRING, NULL,
            (LPARAM)email.c_str());
        SendDlgItemMessage(::g_hDlg, IDC_CBUSERS, CB_SETCURSEL, emailList.size()-1, 0);
    }
}

Since this function is only called when a chat packet is received, I decided to parse the email out by getting whatever text is after the first space after the “MSG” and before the next space in the packet. If it’s not a duplicate, then it’s safe to store the email in the vector and store the active SOCKET parallel to it in the other vector. The email is then added to the combo box. Sending a custom message is pretty easy as well:

C++
case IDOK:
    {
        int index = SendDlgItemMessage(hDlg, IDC_CBUSERS, CB_GETCURSEL, 0, 0);
        int textLength = SendDlgItemMessage(hDlg, IDC_CHAT, WM_GETTEXTLENGTH, 0, 0);
        if(textLength == 0)
            break;
        char* emailSelected = new char[128];
        char* packet = new char[textLength+1];
        GetDlgItemText(hDlg, IDC_CHAT, packet, textLength+1);
        SendDlgItemMessage(hDlg, IDC_CBUSERS, CB_GETLBTEXT, index, (LPARAM)emailSelected);
        SOCKET sessionToSendTo = GetSessionFromEmail(emailSelected);
        BuildPacket(sessionToSendTo, packet);
        delete [] emailSelected;
        delete [] packet;
    }
    break;

It just requires getting the current email in the combo box, getting the appropriate SOCKET for that email, and then building and sending a packet. The BuildPacket(…) function works like this:

C++
void BuildPacket(SOCKET session, const char* message)
{
    char packetSize[8];
    ZeroMemory(packetSize, 8);
    string packetHeader = "MSG 10 N ";
    string packetSettings = "MIME-Version: 1.0\r\nContent-Type: " 
                            "text/plain; charset=UTF-8\r\n";
    packetSettings += "X-MMS-IM-Format: FN=MS%20Shell%20Dlg; " 
                      "EF=; CO=0; CS=0; PF=0\r\n\r\n";
    string packetMessage = message;
    int sizeOfPacket = packetSettings.length() + packetMessage.length();
    _itoa_s(sizeOfPacket, packetSize, 8, 10);
    packetHeader += packetSize;
    packetHeader += "\r\n";
    string fullPacket = packetHeader;
            fullPacket += packetSettings;
            fullPacket += packetMessage;
    psend(session, fullPacket.c_str(), fullPacket.length(), 0);
}

The overall breakdown of an outgoing MSN Messenger packet is:

MSG [Message #] [Acknowledge Flag] [Size of packet] [Text flags] [Message]

I found that Message #, at least for outgoing packets, is not really important for the end user receiving the message. It didn’t matter whether it was 01 or 99, so I just ended up hard-coding that part of the packet. Packet size is important, and if a wrong packet size is sent, then the session will terminate and have to be re-established under a new SOCKET. I also hard-coded the text attributes to just normal MS Shell dialog text with no bold, underlines, colors, etc. Here’s what a sample run looks like:

funapihook/MSNConvo.jpg

You may notice that the message sent isn’t in the chat windows. This is because adding text to the window has nothing to do with sockets, it is simply appended upon hitting the “Enter” key (or clicking “Send”). The messages are being sent over the active SOCKET and the chatbot's response to the questions verifies that it is receiving the message successfully. An important thing that I’d like to mention before ending this section is that this application was coded specifically for this article. It’s not meant to be any sort of replacement of the MSN Messenger window, and it does have some shortcomings such as not processing timeouts in chat sessions. My goal with this was to simply be able to send packets in a non-traditional method, not to replace the MSN Messenger interface, or write a fully functioning integrated client.

4. DLL Injection

I’ve done a lot of talking about DLL injection in this article. The DLLs with the API hooks in them have to be injected into the address space of the process in order for them to work. DLL injection involves somehow loading a DLL that is not normally loaded by the process. There are multitudes of ways to do this. A very common one is to use the SetWindowsHookEx(…) with a dummy hook procedure. Another common one is to use the CreateRemoteThread(…) API. It’s possible to even hardcode a CodeCave stub to load a DLL (I did this in the Solitaire article). The approach that I will be taking and explaining in this article will be the CreateRemoteThread one. It is extremely easy, and relatively efficient. Before starting though, it is important to actually find the process to inject into.

4.1 Process Enumeration

The Windows API provides a great function for doing this – CreateToolhelp32Snapshot(…). This function takes a snapshot of the running processes, threads, heaps, etc. By using the Process32First(…) and Process32Next(…) functions, it is possible to enumerate through every running process. Knowing this, coding it is not a problem.

C++
#undef UNICODE
#include <vector>
#include <string>
#include <windows.h>
#include <Tlhelp32.h>
using std::vector;
using std::string;

int main(void)
{
    vector<string>processNames;
    PROCESSENTRY32 pe32;
    pe32.dwSize = sizeof(PROCESSENTRY32);
    HANDLE hTool32 = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, NULL);
    BOOL bProcess = Process32First(hTool32, &pe32);
    if(bProcess == TRUE)
    {
        while((Process32Next(hTool32, &pe32)) == TRUE)
            processNames.push_back(pe32.szExeFile);
    }
    CloseHandle(hTool32);
    return 0;
}

funapihook/processes.jpg

I didn’t bother storing the value after I called Process32First because that will always be “[System Process]”, so there’s really no need. Process32Next returns TRUE on success, so just simply putting it in a loop and pushing the name of the process it received in a vector is what is needed. Once the loop is finished, every single process should be stored in processNames. This is great and all, but where does the DLL injection come in? Well, the PROCESSENTRY32 structure also has a member that holds the Process ID. Inside that loop, while we’re pushing the process names in our vector, we’re also going to inject the DLL.

4.2 CreateRemoteThread

C++
while((Process32Next(hTool32, &pe32)) == TRUE)
{
    processNames.push_back(pe32.szExeFile);
    if(strcmp(pe32.szExeFile, "notepad.exe") == 0)
    {
        char* DirPath = new char[MAX_PATH];
        char* FullPath = new char[MAX_PATH];
        GetCurrentDirectory(MAX_PATH, DirPath);
        sprintf_s(FullPath, MAX_PATH, "%s\\testdll.dll", DirPath);
        HANDLE hProcess = OpenProcess(PROCESS_CREATE_THREAD    | PROCESS_VM_OPERATION    |
            PROCESS_VM_WRITE, FALSE, pe32.th32ProcessID);
        LPVOID LoadLibraryAddr = (LPVOID)GetProcAddress(GetModuleHandle("kernel32.dll"),
            "LoadLibraryA");
        LPVOID LLParam = (LPVOID)VirtualAllocEx(hProcess, NULL, strlen(FullPath),
            MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
        WriteProcessMemory(hProcess, LLParam, FullPath, strlen(FullPath), NULL);
        CreateRemoteThread(hProcess, NULL, NULL, (LPTHREAD_START_ROUTINE)LoadLibraryAddr,
            LLParam, NULL, NULL);
        CloseHandle(hProcess);
        delete [] DirPath;
        delete [] FullPath;
    }
}

What is going on here, from start to finish, is first, we’re getting the current directory and appending the DLL name, “testdll.dll” in this case. This makes sure that the DLL is found by the executable. Then, we’re opening the process with three flags that we need to perform the DLL injection. Ideally, the PROCESS_ALL_ACCESS flag is the best one to set, but that requires changing the process privileges to SE_DEBUG_NAME, which for the sake of code, I did not bother to implement. The code for changing to SE_DEBUG_NAME can be found here. Continuing on, after the process is opened with the appropriate flags, the address of the LoadLibraryA function is found with GetProcAddress(…). We next allocate some memory for the path to the DLL in the address space of the process that is to be injected. Once this is done, we can use WriteProcessMemory(…) to write the string into memory. After that, it’s simply a matter of using CreateRemoteThread and having the start address be LoadLibraryA, with our string passed as the parameter. The result?

funapihook/dllloaded.jpg

4.3 DetourCreateProcessWithDll

The above technique is good for injecting a DLL into processes that are already running. But, what about injecting a DLL into a process upon that process executing? The Detours library provides a function to do this – DetourCreateProcessWithDll(…). Using that function is basically the equivalent of calling CreateProcess with the CREATE_SUSPENDED creation flag. This creates the process with its main thread in a suspended state, so the DLL can be injected before the actual application shows up. An important thing to note is that the DLL to be injected must export a function on ordinal 1. If it doesn’t, the DetourCreateProcessWithDll(…) function will fail. A quick implementation that starts notepad.exe with testdll.dll (which again, must export a function at ordinal 1):

C++
#undef UNICODE
#include <cstdio>
#include <windows.h>
#include <detours\detours.h>

int main()
{
    STARTUPINFO si;
    PROCESS_INFORMATION pi;
    ZeroMemory(&si, sizeof(STARTUPINFO));
    ZeroMemory(&pi, sizeof(PROCESS_INFORMATION));
    si.cb = sizeof(STARTUPINFO);
    char* DirPath = new char[MAX_PATH];
    char* DLLPath = new char[MAX_PATH]; //testdll.dll
    char* DetourPath = new char[MAX_PATH]; //detoured.dll
    GetCurrentDirectory(MAX_PATH, DirPath);
    sprintf_s(DLLPath, MAX_PATH, "%s\\testdll.dll", DirPath);
    sprintf_s(DetourPath, MAX_PATH, "%s\\detoured.dll", DirPath);
    DetourCreateProcessWithDll(NULL, "C:\\windows\\notepad.exe", NULL,
        NULL, FALSE, CREATE_DEFAULT_ERROR_MODE, NULL, NULL,
        &si, &pi, DetourPath, DLLPath, NULL);
    delete [] DirPath;
    delete [] DLLPath;
    delete [] DetourPath;
    return 0;
}

The DetourCreateProcessWithDll function is basically an extended version of CreateProcess. It takes all of the parameters of CreateProcess, plus some additional ones that contain the path of the DLL to be injected and of detoured.dll, which is injected automatically with every DLL that uses the Detours API. Doing this just using CreateProcess would simply involve setting CREATE_SUSPENDED as the creation flag, injecting the DLL, and then calling ResumeThread(…) with the handle of the thread stored in the PROCESS_INFORMATION structure.

4.4 Detouring by Address

There might come a time when it is needed to detour a function that isn’t a standard Win32 API or known exported function. This is where detouring needs to be done by the hardcoded address of the function. Knowing the address and the parameters the function takes (after reverse engineering them, or finding documentation, or something), it is entirely possible to hook the function. The code below demonstrates how:

C++
#undef UNICODE
#include <cstdio>
#include <windows.h>
#include <detours\detours.h>

typedef void (WINAPI *pFunc)(DWORD);
void WINAPI MyFunc(DWORD);

pFunc FuncToDetour = (pFunc)(0x0100347C); //Set it at address to detour in
                    //the process
INT APIENTRY DllMain(HMODULE hDLL, DWORD Reason, LPVOID Reserved)
{
    switch(Reason)
    {
    case DLL_PROCESS_ATTACH:
        {
            DisableThreadLibraryCalls(hDLL);
            DetourTransactionBegin();
            DetourUpdateThread(GetCurrentThread());
            DetourAttach(&(PVOID&)FuncToDetour, MyFunc);
            DetourTransactionCommit();
        }
        break;
    case DLL_PROCESS_DETACH:
        DetourTransactionBegin();
        DetourUpdateThread(GetCurrentThread());
        DetourDetach(&(PVOID&)FuncToDetour, MyFunc);
        DetourTransactionCommit();
        break;
    case DLL_THREAD_ATTACH:
    case DLL_THREAD_DETACH:
        break;
    }
    return TRUE;
}

void WINAPI MyFunc(DWORD someParameter)
{
    //Some magic can happen here
}

The function to detour simply points to the hardcoded address of the function, and is then ready to be detoured. This particular example has a void function taking a DWORD as a parameter.

5. Common Errors

The most common error that I’ve encountered writing all of these applications for this article was having the DLL load correctly into the process. It is very important to pay attention to the directory that the process that is to be injected is running from. Depending on the method of injection and its implementation, the DLL(s) to be injected might have to be in the same directory as the running process. That shouldn’t be an issue with my implementation since I find the current directory and append the DLL names. However, the loader and the DLLs must be in the same path. After all, since my method is getting the current directory of the loader and appending the DLL name, if the DLL is in a different directory, then it simply won’t work. I would say that’s probably the most common error people might encounter after compiling this code and trying for themselves. The file detoured.dll must also be in the same directory as the DLL that is being injected.

6. Conclusion

That’s about all for API hooking in this article. There are certainly more ways to implement API hooks, some more efficient, some not. However, hopefully after reading this, it’ll be easier to understand API hooking from a Detours perspective. By using MS Detours, API hooking is greatly simplified since the details of modifying address table entries, changing program flow and so on are already implemented. This saves a lot of time debugging, and allows the programmer to set and remove hooks as they please and (if done right) without worry of corrupting memory, parsing through PE headers, and so on. Hopefully, you, as the reader, have learned something from this about the techniques and theories of API hooking, in general, and the implementations of these techniques to every day applications.

License

This article, along with any associated source code and files, is licensed under The Microsoft Public License (Ms-PL)


Written By
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
GeneralHook a module instead of the process Pin
syntax2k3@yahoo.fr3-Mar-10 3:29
syntax2k3@yahoo.fr3-Mar-10 3:29 
GeneralWsHook.dll attach to IE but nothing logged in sendlog.txt Pin
Member 18710687-Nov-09 19:09
Member 18710687-Nov-09 19:09 
QuestionCan you share testdll.dll source code Pin
Member 18710687-Nov-09 19:05
Member 18710687-Nov-09 19:05 
Questioncan't compile the source code... Pin
Tongdong Ma29-Sep-09 4:35
Tongdong Ma29-Sep-09 4:35 
AnswerRe: can't compile the source code... Pin
Tongdong Ma29-Sep-09 4:38
Tongdong Ma29-Sep-09 4:38 
GeneralGreat tutorial, but could you send me compiled MS Detours Pin
TRaga26-Aug-09 23:24
TRaga26-Aug-09 23:24 
GeneralRe: Great tutorial, but could you send me compiled MS Detours Pin
pianocomposer14-Dec-12 9:28
pianocomposer14-Dec-12 9:28 
GeneralVery nice! Pin
Rajesh R Subramanian23-Aug-09 21:05
professionalRajesh R Subramanian23-Aug-09 21:05 
Generalhooking 3rd party DLL Pin
jchanfreckles11-Aug-09 10:44
jchanfreckles11-Aug-09 10:44 
GeneralRe: hooking 3rd party DLL Pin
AlexAbramov11-Aug-09 18:17
AlexAbramov11-Aug-09 18:17 
GeneralRe: hooking 3rd party DLL Pin
jchanfreckles18-Aug-09 8:15
jchanfreckles18-Aug-09 8:15 
Questioncan i use MS Detours to make System -Wide User-Mode hooking ???? Pin
Mohanned4-Jul-09 12:39
Mohanned4-Jul-09 12:39 
AnswerRe: can i use MS Detours to make System -Wide User-Mode hooking ???? Pin
AlexAbramov4-Jul-09 14:42
AlexAbramov4-Jul-09 14:42 
QuestionHow to hook Process32Next? Pin
cnlife27-May-09 15:17
cnlife27-May-09 15:17 
AnswerRe: How to hook Process32Next? Pin
AlexAbramov28-May-09 18:27
AlexAbramov28-May-09 18:27 
GeneralRe: How to hook Process32Next? Pin
cnlife28-May-09 22:25
cnlife28-May-09 22:25 
GeneralRe: How to hook Process32Next? Pin
cnlife29-May-09 2:39
cnlife29-May-09 2:39 
GeneralRe: How to hook Process32Next? Pin
AlexAbramov29-May-09 16:11
AlexAbramov29-May-09 16:11 
GeneralQuery for a project Pin
AMber Chaudhry219-May-09 1:49
AMber Chaudhry219-May-09 1:49 
QuestionIs this traditional API hooking reliable in different OS versions? Pin
Nishad S18-May-09 0:04
Nishad S18-May-09 0:04 
AnswerRe: Is this traditional API hooking reliable in different OS versions? Pin
AlexAbramov18-May-09 15:53
AlexAbramov18-May-09 15:53 
GeneralRe: Is this traditional API hooking reliable in different OS versions? Pin
Nishad S18-May-09 17:58
Nishad S18-May-09 17:58 
GeneralRe: Is this traditional API hooking reliable in different OS versions? Pin
AlexAbramov27-May-09 19:15
AlexAbramov27-May-09 19:15 
GeneralRe: Is this traditional API hooking reliable in different OS versions? Pin
Nishad S27-May-09 20:01
Nishad S27-May-09 20:01 
QuestionInjected DLL doesn't seem to run [modified] Pin
sparklight22-Apr-09 11:09
sparklight22-Apr-09 11:09 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.