Click here to Skip to main content
15,896,557 members
Articles / Desktop Programming / Win32

EasyHook - The reinvention of Windows API hooking

Rate me:
Please Sign up or sign in to vote.
4.94/5 (79 votes)
14 Aug 2008LGPL329 min read 653.2K   24.3K   359  
Now supports an unmanaged API, kernel mode hooking, and extending unmanaged APIs with pure managed handlers since Windows 2000 SP4.
/*
    EasyHook - The reinvention of Windows API hooking
 
    Copyright (C) 2008 Christoph Husse

    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Lesser General Public
    License as published by the Free Software Foundation; either
    version 2.1 of the License, or (at your option) any later version.

    This library is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public
    License along with this library; if not, write to the Free Software
    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA

    Please visit http://www.codeplex.com/easyhook for more information
    about the project and latest updates.

PLEASE NOTE:
    The LGPL allows you to sell propritary software based on this library
    (EasyHook) without releasing the source code for your application.
    This is a big difference to the original GPL. Refer to the attached
    "LICENSE" document for more information about the LGPL!
 
    To wrap it up (without warranty):
        
        1)  You are granted to sell any software that uses EasyHook over
            DLL or NET bindings. This is covered by the native API and the 
            managed interface.
        2)  You are NOT granted to sell any software that includes parts
            of the EasyHook source code or any modification! If you want
            to modify EasyHook, you are forced to release your work under
            the LGPL or GPL... Of course this only applies to the library
            itself. For example you could release a modification of EasyHook
            under LGPL, while still being able to release software, which
            takes advantage of this modification over DLL or NET bindings,
            under a proprietary license!
        3)  You shall include a visible hint in your software that EasyHook
            is used as module and also point out, that this module in
            particular is released under the terms of the LGPL and NOT
            under the terms of your software (assuming that your software
            has another license than LGPL or GPL).
 
    I decided to release EasyHook under LGPL to prevent commercial abuse
    of this free work. I didn't release it under GPL, because I also want to
    address commercial vendors which are more common under Windows.

BUG REPORTS:

    Reporting bugs is the only chance to get them fixed! Don't consider your
    report useless... I will fix any serious bug within a short time! Bugs with
    lower priority will always be fixed in the next release...

DONATIONS:

    I want to add support for Itanium (II - III) processors. If you have any hardware
    that you don't need anymore or could donate, which >supports< a recent Windows
    Itanium edition (Windows license is not required), please contact me. Of course we 
    could discuss a reasonable sponsorship reference for your company. Money for
    buying such hardware is also appreciated...
*/
#include "stdafx.h"

#include <dbgeng.h>

#ifdef __cplusplus
extern "C"{
#endif

typedef HRESULT __stdcall DebugCreate_PROC(
	        __in REFIID InterfaceId,
	        __out PVOID* Interface
	        );

typedef LONG NTAPI ZwQueryInformationProcess_PROC(
            IN HANDLE ProcessHandle,
            IN ULONG ProcessInformationClass,
            OUT PVOID ProcessInformation,
            IN ULONG ProcessInformationLength,
            OUT PULONG ReturnLength OPTIONAL
            );

typedef DWORD WINAPI ZwGetProcessId_PROC(
            IN HANDLE Process
            );

typedef LONG NTAPI ZwQueryInformationThread_PROC(
            IN HANDLE ThreadHandle,
            IN ULONG ThreadInformationClass,
            OUT PVOID ThreadInformation,
            IN ULONG ThreadInformationLength,
            OUT PULONG ReturnLength OPTIONAL
            );

typedef DWORD WINAPI ZwGetThreadId_PROC(
            IN HANDLE Thread
            );

#define ObjectNameInformation           1

typedef LONG ZwQueryObject_PROC(
            HANDLE InHandle,
            ULONG InInfoClass,
            PVOID OutInfo,
            ULONG InInfoSize,
            PULONG OutRequiredSize);

typedef struct _CLIENT_ID
{
    DWORD	    UniqueProcess;
    DWORD	    UniqueThread;
}CLIENT_ID, * PCLIENT_ID;

typedef struct _THREAD_BASIC_INFORMATION
{
    LONG ExitStatus;
    PNT_TIB TebBaseAddress;
    CLIENT_ID ClientId;
    DWORD AffinityMask;
    LONG Priority;
    LONG BasePriority;
}THREAD_BASIC_INFORMATION, *PTHREAD_BASIC_INFORMATION;

static HMODULE                          hDbgEng = NULL;
static IDebugClient*			        DebugClient = NULL;
static IDebugControl*			        DebugControl = NULL;
static DebugCreate_PROC*                CreateDebugEngine = NULL;
static ZwGetThreadId_PROC*              ZwGetThreadId = NULL;
static ZwGetProcessId_PROC*             ZwGetProcessId = NULL;
static ZwQueryInformationThread_PROC*   ZwQueryInformationThread = NULL;
static ZwQueryInformationProcess_PROC*  ZwQueryInformationProcess = NULL;
static ZwQueryObject_PROC*              ZwQueryObject = NULL;

EASYHOOK_NT_EXPORT DbgAttachDebugger()
{
/*
Description:

    Attaches a debugger to the current process. This is currently
    only necessary for RIP-relocation and disassembling. Multiple
    calls will do nothing.

*/
    NTSTATUS            NtStatus;


    if(hDbgEng != NULL)
        RETURN;

#ifdef _M_X64    

    if((hDbgEng = LoadLibraryW(L"dbgeng.dll")) == NULL)
        THROW(STATUS_NOT_SUPPORTED, L"Unable to load 'dbgeng.dll'.");

    if((CreateDebugEngine = (DebugCreate_PROC*)GetProcAddress(hDbgEng, "DebugCreate")) == NULL)
        THROW(STATUS_NOT_SUPPORTED, L"'dbgeng.dll' does not export 'DebugCreate'.");

    // initialize microsoft debugging engine
    if(!RTL_SUCCESS(CreateDebugEngine(__uuidof(IDebugClient), (void**)&DebugClient)))
        THROW(STATUS_NOT_SUPPORTED, L"Unable to obtain IDebugClient interface.");

    if(!RTL_SUCCESS(DebugClient->QueryInterface(__uuidof(IDebugControl), (void**)&DebugControl)))
        THROW(STATUS_NOT_SUPPORTED, L"Unable to obtain IDebugControl interface.");

    // attach to current process
	if(!RTL_SUCCESS(NtStatus = DebugClient->AttachProcess(0, GetCurrentProcessId(), DEBUG_ATTACH_NONINVASIVE |
			DEBUG_ATTACH_NONINVASIVE_NO_SUSPEND)))
	{
		NtStatus = NtStatus;

		THROW(STATUS_NOT_SUPPORTED, L"Unable to attach debugger to current process.");
	}

	// wait for completion
	if(!RTL_SUCCESS(DebugControl->WaitForEvent(0, 5000)))
	    THROW(STATUS_INTERNAL_ERROR, L"Unable to wait for debugger.");
#else
	hDbgEng = (HMODULE)-1;
#endif

    RETURN;

THROW_OUTRO:
    {
        DbgDetachDebugger();
    }
FINALLY_OUTRO:
    {
        return NtStatus;
    }

}

EASYHOOK_NT_EXPORT DbgDetachDebugger()
{
#ifdef _M_X64
/*
Description:

    Detaches the debugger from the current process. If there
    is none attached, the method will do nothing.

*/
    if(DebugClient != NULL)
    {
	    DebugClient->DetachProcesses();
	    DebugClient->Release();
    }

    if(DebugControl != NULL)
	    DebugControl->Release();

    if(hDbgEng != NULL)
	    FreeLibrary(hDbgEng);
#endif

    DebugClient = NULL;
    DebugControl = NULL;
    hDbgEng = NULL;

    return STATUS_SUCCESS;
}



EASYHOOK_BOOL_EXPORT DbgIsEnabled()
{
/*
Description:

    Returns TRUE if the debugger is attached, FALSE otherwise.

*/
    return hDbgEng != NULL;
}




EASYHOOK_BOOL_EXPORT DbgIsAvailable()
{
/*
Description:

    Returns TRUE if a debugger can be attached to the current process,
    FALSE otherwise.

*/
#ifdef _M_x64
    HMODULE     Module = NULL;

    if((Module = LoadLibraryW(L"dbgeng.dll")) == NULL)
        return FALSE;

    FreeLibrary(Module);

    Module = NULL;
#endif
    return TRUE;
}




EASYHOOK_NT_INTERNAL DbgCriticalInitialize()
{
/*
Description:

    Loads some special APIs required for handler utilities.
    This should work on any sane system and will be called on
    DLL load.
*/

    // try to load newer methods first
    if((ZwGetProcessId = (ZwGetProcessId_PROC*)GetProcAddress(hKernel32, "GetProcessId")) == NULL)
    {
	    if((ZwQueryInformationProcess = (ZwQueryInformationProcess_PROC*)::GetProcAddress(
			    hNtDll, "NtQueryInformationProcess")) == NULL)
		    return STATUS_PROCEDURE_NOT_FOUND;
    }

    if((ZwGetThreadId = (ZwGetThreadId_PROC*)GetProcAddress(hKernel32, "GetThreadId")) == NULL)
    {
	    if((ZwQueryInformationThread = (ZwQueryInformationThread_PROC*)::GetProcAddress(
			    hNtDll, "NtQueryInformationThread")) == NULL)
            return STATUS_PROCEDURE_NOT_FOUND;
    }

    if((ZwQueryObject = (ZwQueryObject_PROC*)GetProcAddress(hNtDll, "ZwQueryObject")) == NULL)
        return STATUS_PROCEDURE_NOT_FOUND;

    return STATUS_SUCCESS;
}





EASYHOOK_NT_INTERNAL DbgCriticalFinalize()
{
/*
Description:

    Will be called on DLL unload and just ensures that
    the debugger is detached.

*/
    return DbgDetachDebugger();
}



EASYHOOK_NT_INTERNAL DbgRelocateRIPRelative(
	        ULONGLONG InOffset,
	        ULONGLONG InTargetOffset,
            BOOL* OutWasRelocated)
{
/*
Description:

    Check whether the given instruction is RIP relative and
    relocates it. If it is not RIP relative, nothing is done.

Parameters:

    - InOffset

        The instruction pointer to check for RIP addressing and relocate.

    - InTargetOffset

        The instruction pointer where the RIP relocation should go to.
        Please note that RIP relocation are relocated relative to the
        offset you specify here and therefore are still not absolute!

    - OutWasRelocated

        TRUE if the instruction was RIP relative and has been relocated,
        FALSE otherwise.
*/
#ifndef _M_X64
	return FALSE;
#else
	NTSTATUS            NtStatus;
	CHAR					Buf[MAX_PATH];
	ULONG					AsmSize;
	ULONG64					NextInstr;
	CHAR					Line[MAX_PATH];
	LONG					Pos;
	LONGLONG				RelAddr;
	LONGLONG				MemDelta = InTargetOffset - InOffset;

	ASSERT(MemDelta == (LONG)MemDelta);

    *OutWasRelocated = FALSE;

	// test field...
	/*BYTE t[10] = {0x8b, 0x05, 0x12, 0x34, 0x56, 0x78};

	InOffset = (LONGLONG)t;

	MemDelta = InTargetOffset - InOffset;
*/
	if(!RTL_SUCCESS(DebugControl->Disassemble(InOffset, 0, Buf, sizeof(Buf), &AsmSize, &NextInstr)))
		THROW(STATUS_INVALID_PARAMETER_1, L"Unable to disassemble entry point. ");

	Pos = RtlAnsiIndexOf(Buf, '[');

	if(Pos < 0)
		RETURN;

	// parse content
	if(RtlAnsiSubString(Buf, Pos + 1, RtlAnsiIndexOf(Buf, ']') - Pos - 1, Line, MAX_PATH) != 16)
		RETURN;

	if(!RtlAnsiHexToLongLong(Line, 16, &RelAddr))
		RETURN;
	
	// verify that we are really RIP relative...
	RelAddr -= NextInstr;

	if(RelAddr != (LONG)RelAddr)
		RETURN;

	if(*((LONG*)(NextInstr - 4)) != RelAddr)
		RETURN;

	/*
		Just relocate this instruction...
	*/
	RelAddr = RelAddr - MemDelta;

	if(RelAddr != (LONG)RelAddr)
		THROW(STATUS_NOT_SUPPORTED, L"The given entry point contains at least one RIP-Relative instruction that could not be relocated!");

	// copy instruction
	RtlCopyMemory((void*)InTargetOffset, (void*)InOffset, (ULONG)(NextInstr - InOffset));

	*((LONG*)(InTargetOffset + (NextInstr - InOffset) - 4)) = (LONG)RelAddr;

    *OutWasRelocated = TRUE;

	RETURN;

THROW_OUTRO:
FINALLY_OUTRO:
    return NtStatus;
#endif
}







EASYHOOK_NT_EXPORT DbgGetThreadIdByHandle(
                HANDLE InThreadHandle,
                ULONG* OutThreadId)
{
/*
Description:

    Translates the given thread handle back to its thread ID if possible.

Parameters:

    - InThreadHandle

        A thread handle with THREAD_QUERY_INFORMATION access.

    - OutThreadId

        Receives the thread ID.
*/
    THREAD_BASIC_INFORMATION        ThreadInfo;
    NTSTATUS                        NtStatus;

    if(!IsValidPointer(OutThreadId, sizeof(ULONG)))
        THROW(STATUS_INVALID_PARAMETER_2, L"Invalid TID storage specified."); 

    if(ZwQueryInformationThread != NULL)
    {
	    // use deprecated API
	    FORCE(ZwQueryInformationThread(
            InThreadHandle, 
            0 /* ThreadBasicInformation */, 
            &ThreadInfo, 
            sizeof(ThreadInfo), 
            NULL));

        *OutThreadId = ThreadInfo.ClientId.UniqueThread;
    }
    else
    {
	    ASSERT(ZwGetThreadId != NULL);

	    // use new support API
	    if((*OutThreadId = ZwGetThreadId(InThreadHandle)) == 0)
		    THROW(STATUS_INVALID_PARAMETER_1, L"Invalid thread handler or improper privileges.");
    }

    RETURN;

THROW_OUTRO:
FINALLY_OUTRO:
    return NtStatus;
}






EASYHOOK_NT_EXPORT DbgGetProcessIdByHandle(
                HANDLE InProcessHandle,
                ULONG* OutProcessId)
{
/*
Description:

    Translates the given process handle back to its process ID if possible.

Parameters:

    - InProcessHandle

        A process handle with PROCESS_QUERY_INFORMATION access.

    - OutProcessId

        Receives the process ID.
*/
    PROCESS_BASIC_INFORMATION       ProcInfo;
    NTSTATUS                        NtStatus;

    if(!IsValidPointer(OutProcessId, sizeof(ULONG)))
        THROW(STATUS_INVALID_PARAMETER_2, L"The given process ID storage is invalid.");

    if(ZwQueryInformationProcess != NULL)
    {
	    // use deprecated API
	    FORCE(ZwQueryInformationProcess(
            InProcessHandle, 
            0 /* ProcessBasicInformation */, 
            &ProcInfo, 
            sizeof(ProcInfo), 
            NULL));

	    *OutProcessId = (ULONG)ProcInfo.UniqueProcessId;
    }
    else
    {
	    ASSERT(ZwGetProcessId != NULL);

	    // use new support API
	    if((*OutProcessId = ZwGetProcessId(InProcessHandle)) == 0)
		    THROW(STATUS_INVALID_PARAMETER_1, L"Invalid process handle or improper privileges.");
    }

RETURN;

THROW_OUTRO:
FINALLY_OUTRO:
    return NtStatus;
}





EASYHOOK_NT_EXPORT DbgHandleToObjectName(
                HANDLE InNamedHandle,
                PUNICODE_STRING OutNameBuffer,
                ULONG InBufferSize,
                ULONG* OutRequiredSize)
{
/*
Description:

    Queries the kernel space name of a named object. This is
    only possible if the handle refers to a named object of course.

Parameters:

    - InNamedHandle

        A valid file, event, section, etc.

    - OutNameBuffer

        A buffer large enough to hold the kernel space object name.
        To query the required size in bytes, set this parameter to
        NULL.

    - InBufferSize

        The maximum size in bytes the given buffer can hold.

    - OutRequiredSize

        Receives the required size in bytes. This parameter can be NULL.
*/
    ULONG           RequiredSize;
    NTSTATUS        NtStatus;


    if((InNamedHandle == NULL) || (InNamedHandle == INVALID_HANDLE_VALUE))
        THROW(STATUS_INVALID_PARAMETER_1, L"The given handle is invalid.");

    if(!IsValidPointer(OutNameBuffer, InBufferSize))
    {
        // determine required length
        if(InBufferSize != 0)
            THROW(STATUS_INVALID_PARAMETER_3, L"If no buffer is specified, the buffer size is expected to be zero.");

        if(OutRequiredSize == NULL)
            THROW(STATUS_INVALID_PARAMETER_4, L"If no buffer is specified, you are expected to query the required size.");
    }

    if((NtStatus = ZwQueryObject(InNamedHandle, ObjectNameInformation, NULL, 0, &RequiredSize)) 
            != STATUS_INFO_LENGTH_MISMATCH)
        FORCE(NtStatus);

    if(IsValidPointer(OutNameBuffer, InBufferSize))
    {
        // query string
        if(InBufferSize < RequiredSize)
            THROW(STATUS_BUFFER_TOO_SMALL, L"The given buffer is not long enough to hold all the data.");

        FORCE(ZwQueryObject(InNamedHandle, ObjectNameInformation, OutNameBuffer, InBufferSize, &RequiredSize));
    }

    if(IsValidPointer(OutRequiredSize, sizeof(ULONG)))
        *OutRequiredSize = RequiredSize;

    RETURN;

THROW_OUTRO:
FINALLY_OUTRO:
    return NtStatus;
}
#ifdef __cplusplus
}
#endif

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The GNU Lesser General Public License (LGPLv3)


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

Comments and Discussions