Hooking with the Microsoft Detours library in C++

The DLL Injection example described how to inject a DLL into an existing process. The snippets below demonstrate a combination of hooking and DLL injection in C++ with the Microsoft Detours library.

Quote:

In computer programming, the term hooking covers a range of techniques used to alter or augment the behavior of an operating system, of applications, or of other software components by intercepting function calls or messages or events passed between software components. Code that handles such intercepted function calls, events or messages is called a “hook”.

The RegOpenKeyExW function within kernel32.dll is responsible for opening a specified registry key. To demonstrate hooking, the RegOpenKeyExW function will be intercepted within the Google Chrome process. A message will be logged at each call of RegOpenKeyExW. These messages can be viewed with DebugView.

DLLInjector.cpp: Responsible for launching Google Chrome and injecting the specified DLL into the process

// DLLInjector.cpp : Defines the entry point for the console application.
//
#include <windows.h>
#include <stdio.h>
#include <iostream>
#include <Psapi.h>
#include <string.h>
#include "stdafx.h"
#include "detours.h"

#pragma comment(lib, "detours.lib")

#define DLL_PATH "\\..\\Debug\\exampledll.dll" // The DLL we would like to inject
#define PROC_DIR "C:\\Users\\Ferhat\\AppData\\Local\\Google\\Chrome\\Application"
#define PROC_PATH "C:\\Users\\Ferhat\\AppData\\Local\\Google\\Chrome\\Application\\chrome.exe" // Google Chrome

int _tmain(int argc, _TCHAR* argv[])
{
	STARTUPINFO si;
	PROCESS_INFORMATION pi;

	ZeroMemory( &si, sizeof(si) );
	si.cb = sizeof(si);
	ZeroMemory( &pi, sizeof(pi) );

	TCHAR currentDir[MAX_PATH];
	TCHAR dllDir[MAX_PATH];
	GetCurrentDirectory(MAX_PATH,currentDir);

	strcpy(dllDir,currentDir);
	strcat(dllDir,DLL_PATH);

	printf("Current directory: %s\n", currentDir);
	printf("DLL path: %s\n", dllDir);
	printf("Process path: %s\n", PROC_PATH);

	bool detourProcess = DetourCreateProcessWithDll(PROC_PATH,"",0,0,FALSE,CREATE_DEFAULT_ERROR_MODE,0,PROC_DIR,&si,&pi, dllDir, NULL);

	if(detourProcess){
		printf("Injected DLL: %s\n", dllDir);
	} else {
		printf("Failed to inject DLL: %s\n", dllDir);
		system("PAUSE");
	}

	return 0;
}

FunctionHook: represents a hook

Header file

#pragma once

#include <Windows.h>
#include <string>

using namespace std;

// Used for detouring class virtual member functions
#define Naked __declspec( naked )

class FunctionHook
{
public:
	FunctionHook(void);
	~FunctionHook(void);

	// Used for fetching the hook (e.g. "CPlayer::ctor" or "sprintf")
	string Identifier;

	// Optional module (e.g. "msvcrt.dll")
	string Module;

	// Signature of the function in memory
	string Signature;

	// Mask
	string Mask;

	// The original address of the function
	DWORD OriginalAddress;

	// The detour function
	PBYTE Detour;

	// The address of the detour function
	DWORD DetourAddress;
};

CPP file

#include "FunctionHook.h"

FunctionHook::FunctionHook(void)
{
	this->Identifier = "";
	this->Module = "";
	this->Mask = "";
	this->Signature = "";
	this->DetourAddress = 0;
	this->OriginalAddress = 0;
}

FunctionHook::~FunctionHook(void)
{
}

HookManager: responsible for hooking, retrieving hooks and keeping a list of active hooks

Header file

#pragma once

#include <Windows.h>
#include <vector>
#include "FunctionHook.h"
#include "Hook.h"
#include "detours.h"
#include "Log.h"

#pragma comment(lib, "ws2_32.lib")
#pragma comment(lib, "psapi.lib")
#pragma comment(lib, "dbghelp.lib")
#pragma comment(lib, "detours.lib")

using namespace std;

typedef std::tr1::shared_ptr<FunctionHook> fhPtr;

class HookManager
{
public:
	HookManager(void);
	~HookManager(void);
	vector<fhPtr> Hooks;
	FunctionHook* GetHook(char* identifier);
	void CreateHook(FunctionHook* hook);
	static HookManager* Instance();
};

CPP file

#include "HookManager.h"

static HookManager* instance;

HookManager::HookManager(void)
{

}

HookManager::~HookManager(void)
{
}

HookManager* HookManager::Instance(){
	if(!instance){
		instance = new HookManager();
	}

	return instance;
}

void SignatureToByteArray(char* signature, BYTE* bytes){
	int tmp;
	for(int i=0;i<strlen(signature)/2;i++){
		sscanf(signature+2*i,"%2x",&tmp);
		bytes[i] = tmp;
	}
}

void HookManager::CreateHook(FunctionHook* hook){
	DWORD functionAddress;
	DWORD detouredAddress;
	PBYTE foundAddress;

	FunctionHook* hookInst = new FunctionHook();
	hookInst->Detour = hook->Detour;
	hookInst->DetourAddress = hook->DetourAddress;
	hookInst->Identifier = hook->Identifier;
	hookInst->Mask = hook->Mask;
	hookInst->Module = hook->Module;
	hookInst->OriginalAddress = hook->OriginalAddress;
	hookInst->Signature = hook->Signature;

	if(strlen(hookInst->Module.c_str()) <= 0){
		BYTE* sigArray = new BYTE[strlen(hookInst->Signature.c_str())/2];
		SignatureToByteArray(const_cast<char*>(hookInst->Signature.c_str()), sigArray);
		functionAddress = sigFindPattern(getMainModule(), sigArray, const_cast<char*>(hookInst->Mask.c_str()));
		hookInst->OriginalAddress = functionAddress;
		LogDebug("Hooking %s at @0x%08X\n", const_cast<char*>(hookInst->Identifier.c_str()), hookInst->OriginalAddress);
		detouredAddress = (DWORD)DetourFunction((PBYTE)functionAddress, hookInst->Detour);
		hookInst->DetourAddress = detouredAddress;
	} else {
		foundAddress = DetourFindFunction(const_cast<char*>(hookInst->Module.c_str()),const_cast<char*>(hookInst->Identifier.c_str()));
		hookInst->OriginalAddress = reinterpret_cast<DWORD>(foundAddress);
		LogDebug("Hooking %s at @0x%08X\n", const_cast<char*>(hookInst->Identifier.c_str()), hookInst->OriginalAddress);
		detouredAddress = (DWORD)DetourFunction(foundAddress, hookInst->Detour);
		hookInst->DetourAddress = detouredAddress;
	}

	// Add the hook to the list of hooks
	HookManager::Instance()->Hooks.push_back(fhPtr(hookInst));
}

FunctionHook* HookManager::GetHook(char* identifier){
	for(int i=0;i<HookManager::Instance()->Hooks.size();i++){
		FunctionHook* functionHook = HookManager::Instance()->Hooks[i].get();
		if(functionHook != NULL){
			if(functionHook->Identifier == identifier){
				return functionHook;
			}
		}
	}

	LogDebug("Hook not found\n");
	return NULL;
}

Log: responsible for debug messages

Header file

#pragma once

#include "stdafx.h"

#ifndef LOG_H
#define LOG_H

void LogDebug(const char *format, ...);

void LogMsgBox(const char *format, ...);

void LogClearScreen();
void LogScreen(const char *format, ...);
void LogDraw();
void LogPrintScreen(int posX, int posY, int width, const char *format, ...);

#endif

CPP file

#define _CRT_SECURE_NO_WARNINGS
#include <Windows.h>
#include <cstdio>
#include <string>
#include "Log.h"

using namespace std;

string screenlogbuffer;

void LogScreen(const char *format, ...)
{
	va_list args;
	va_start(args, format);
	char tmp[1024];
	vsprintf(tmp, format, args);
	va_end(args);
	screenlogbuffer.append(tmp);
}

void LogDebug(const char *format, ...)
{
	char msgBuf[4096];
	va_list args;
	va_start(args, format);
	vsprintf(msgBuf, format, args);
	va_end(args);
	OutputDebugString(msgBuf);
}

DLLMain: The DLL injected into the Google Chrome process

CPP file

// dllmain.cpp : Defines the entry point for the DLL application.
#define _CRT_SECURE_NO_WARNINGS
#include <WinSock2.h>
#include <Windows.h>
#include <intrin.h>
#include <cstdio>
#include <cmath>
#include <ctime>
#include <set>
#include <vector>

#include "HookManager.h"

#pragma comment(lib, "ws2_32.lib")
#pragma comment(lib, "psapi.lib")
#pragma comment(lib, "dbghelp.lib")
#pragma comment(lib, "detours.lib")

typedef LSTATUS (__stdcall* pRegOpenKeyExW)(HKEY, LPCWSTR, DWORD, REGSAM, PHKEY);
pRegOpenKeyExW pOriginalRegOpenKeyExW;

LSTATUS __stdcall MyRegOpenKeyExW(HKEY hKey, LPCWSTR lpSubKey, DWORD ulOptions, REGSAM samDesired, PHKEY phkResult){
	LogDebug("RegOpenKeyExW called: %S\n", lpSubKey);
	return pOriginalRegOpenKeyExW(hKey, lpSubKey, ulOptions, samDesired, phkResult);
}

void main()
{
	FunctionHook* regOpenKeyExWHook = new FunctionHook();
	regOpenKeyExWHook->Identifier = "RegOpenKeyExW";
	regOpenKeyExWHook->Module = "kernel32.dll";
	regOpenKeyExWHook->Detour = (PBYTE)MyRegOpenKeyExW;
	HookManager::Instance()->CreateHook(regOpenKeyExWHook);
	pOriginalRegOpenKeyExW = (pRegOpenKeyExW)(HookManager::Instance()->GetHook("RegOpenKeyExW")->DetourAddress);
}

BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
{
	int processId = GetCurrentProcessId();

	if(fdwReason == DLL_PROCESS_ATTACH)
	{
		DWORD id;
		LogDebug("Attach...\n");
		LogDebug("Process ID: %d\n", processId);
		CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)main, NULL, 0, &id);
	}
	if(fdwReason == DLL_PROCESS_DETACH)
	{
		char buffer[40];
		_strtime(buffer);
		LogDebug("Detach...\n");
		LogDebug("Time:%s\n", buffer);
	}
	return TRUE;
}

The function MyRegOpenKeyExW represents the hook for RegOpenKeyExW. It prints a debug message and calls the original RegOpenKeyExW function.

DebugView output

DebugView output

The archive containing the VC++ source code can be downloaded from here.

This entry was posted in C++ and tagged , , , , , , , , , , , , , , , , . Bookmark the permalink. Trackbacks are closed, but you can post a comment.

5 Comments

  1. Stephen
    Posted August 30, 2012 at 12:00 am | Permalink

    does this work on windows 7?

    • FY
      Posted September 1, 2012 at 9:56 am | Permalink

      Yes it does

  2. Vitalii
    Posted October 19, 2012 at 7:59 am | Permalink

    Nice in general, but why so many ugly const_cast usage?
    For instance, let’s look at implementation of SignatureToByteArray. Does it modify its input argument ‘signature’? Nope, it does not! So it _should_ be ‘const char*’, not ‘char*’! And so on.

    • Posted October 19, 2012 at 11:30 pm | Permalink

      Hi,

      Thanks for caring. The majority of the snippets aren’t optimized. For example, the snippets regarding algorithms can probably be optimized. They’ve been posted for illustrative purposes and should not be copied and pasted without taking extra care.

      Ferhat

  3. HaeRim Lee
    Posted April 20, 2014 at 10:15 pm | Permalink

    Hi,
    With your hooking techniques, I understand it is possible to inject a dll and hook RegOpenKeyExW in kernel32.dll for any running process (in this article, chrome.exe used).
    So far so good.
    Next, I have a challenging task to do that I can’t find out how.
    There is an exe (P.exe, no source code) that loads my own dll D.dll (naturally source available).
    Both P.exe and D.dll calls RegOpenKeyExW.
    When P.exe is running, it loads D.dll.
    First, I inject exampledll.dll as shown in this article.
    Then all RegOpenKeyExW calls are detoured and prints log messages.
    However, one problem is that it detours RegOpenExW calls from D.dll.
    Since D.dll is my own and naturally have source code, I don’t need and want to detour it at all.
    So, I need to know how to call the ‘undetoured, native’ RegOpenKeyExW call from D.dll.
    I tried to
    – export pOriginalRegOpenKeyExW and import if from D.dll => still calls the detoured one
    - GetProcAddressA(“kernel32.dll”, “RegOpenKeyExW”) => again, calls the detoured one

    I search all over the internet, but can’t find any good answer.
    Is this inherently impossible challenge?

    If anyone knows answer for this, please show me how.

    thx a lot.
    HaeRim

Post a Comment

Your email is never published nor shared. Required fields are marked *

*
*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

Why ask?