Click here to Skip to main content
Click here to Skip to main content

MinHook - The Minimalistic x86/x64 API Hooking Library

By , 28 Nov 2009
 
MinHook_110_bin.zip
MinHook_110_bin
DynamicSample.x64.exe
DynamicSample.x86.exe
libMinHook.x64.lib
libMinHook.x86.lib
MinHook.x64.dll
MinHook.x64.lib
MinHook.x86.dll
MinHook.x86.lib
StaticSample.x64.exe
StaticSample.x86.exe
MinHook_110_src.zip
MinHook_110_src
MinHook
libMinHook
src
HDE32
HDE64
include
src
MinHook
MinHook.def
MinHookSample
DynamicSample
DynamicSample.vcproj.Asenath.Kageyu.user
MinHook.x64.lib
MinHook.x86.lib
StaticSample
libMinHook.x64.lib
libMinHook.x86.lib
MinHook_bin.zip
MinHook_bin
DynamicSample.x64.exe
DynamicSample.x86.exe
libMinHook.x64.lib
libMinHook.x86.lib
MinHook.x64.dll
MinHook.x64.lib
MinHook.x86.dll
MinHook.x86.lib
StaticSample.x64.exe
StaticSample.x86.exe
MinHook_src.zip
MinHook_src
MinHook
libMinHook
libMinHook.vcproj.Asenath.Kageyu.user
src
HDE32
HDE64
include
src
MinHook.suo
MinHook
MinHook.def
MinHook.vcproj.Asenath.Kageyu.user
MinHookSample
DynamicSample
MinHook.x64.lib
MinHook.x86.lib
StaticSample
libMinHook.x64.lib
libMinHook.x86.lib
/* 
 *  MinHook - Minimalistic API Hook Library	
 *  Copyright (C) 2009 Tsuda Kageyu. All rights reserved.
 *  
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions
 *  are met:
 *  
 *  1. Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *  2. Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 *  3. The name of the author may not be used to endorse or promote products
 *     derived from this software without specific prior written permission.
 *  
 *  THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 *  IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 *  OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 *  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 *  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 *  NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 *  DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 *  THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 *  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 *  THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include <cassert>
#include <vector>
#include <algorithm>
#include <functional>
#include <boost/scope_exit.hpp>
#include <boost/foreach.hpp>
#include <Windows.h>
#include "pstdint.h"

#include "../MinHook.h"
#include "hook.h"
#include "buffer.h"
#include "trampoline.h"
#include "thread.h"

namespace MinHook { namespace
{
	struct HOOK_ENTRY
	{
		void*	pTarget;
		void*	pDetour;
#if defined _M_X64
		void*	pRelay;
#endif
		void*	pTrampoline;
		void*	pBackup;
		bool	isInstalled;
		std::vector<uintptr_t>	oldIPs;
		std::vector<uintptr_t>	newIPs;
	};

	// ���ߏ������ݗp�\����
#pragma pack(push, 1)
	struct JMP_REL
	{
		uint8_t		opcode;
		uint32_t	operand;
	};

	struct JMP_ABS
	{
		uint16_t	opcode;
		uint32_t	operand;
	};
#pragma pack(pop)

	bool		IsHookInstalled(const HOOK_ENTRY& hook);
	HOOK_ENTRY* FindHook(void* const pTarget);
	bool		IsExecutableAddress(void* pAddress);
	void		WriteRelativeJump(void* pFrom, void* const pTo);
	void		WriteAbsoluteJump(void* pFrom, void* const pTo, void* pTable);

	template <typename T>
	bool operator <(const HOOK_ENTRY& lhs, const T& rhs) ;
	template <typename T>
	bool operator <(const T& lhs, const HOOK_ENTRY& rhs) ;
	bool operator <(const HOOK_ENTRY& lhs, const HOOK_ENTRY& rhs);

	CriticalSection gCS;
	std::vector<HOOK_ENTRY> gHooks;
	bool gIsInitialized = false;
}}

namespace MinHook
{
	MH_STATUS Initialize()
	{
		CriticalSection::ScopedLock lock(gCS);

		if (gIsInitialized)
		{
			return MH_ERROR_ALREADY_INITIALIZED;
		}

		// ����֐��o�b�t�@�̏�����
		InitializeBuffer();

		gIsInitialized = true;
		return MH_OK;
	}

	MH_STATUS Uninitialize()
	{
		CriticalSection::ScopedLock lock(gCS);

		if (!gIsInitialized)
		{
			return MH_ERROR_NOT_INITIALIZED;
		}

		// ���ׂẴt�b�N����
		BOOST_FOREACH (const HOOK_ENTRY& hook, gHooks)
		{
			if (!hook.isInstalled)
			{
				continue;
			}
			
			MH_STATUS status = UninstallHook(hook.pTarget);
			if (status != MH_OK)
			{
				return status;
			}
		}

		std::vector<HOOK_ENTRY> v;
		gHooks.swap(v);

		// ����֐��o�b�t�@�̊J��
		UninitializeBuffer();

		gIsInitialized = false;
		return MH_OK;
	}

	MH_STATUS InstallHook(void* pTarget, void* const pDetour)
	{
		CriticalSection::ScopedLock lock(gCS);
		if (!IsExecutableAddress(pTarget) || !IsExecutableAddress(pDetour))
		{
			return MH_ERROR_NOT_EXECUTABLE;
		}

		HOOK_ENTRY *pHook = FindHook(pTarget);
		if (pHook != NULL && pHook->isInstalled)
		{
			return MH_ERROR_ALREADY_INSTALLED;
		}

		if (pHook == NULL)
		{
			bool committed = false;
			BOOST_SCOPE_EXIT((&committed))
			{
				if (!committed)
				{
					RollbackBuffer();
				}
			}
			BOOST_SCOPE_EXIT_END;

			// �g�����|�����֐���쐬����
			CREATE_TREMPOLINE_T ct = { 0 };
			ct.pTarget = pTarget;
			if (!CreateTrampolineFunction(ct))
			{
				return MH_ERROR_UNSUPPORTED_FUNCTION;
			}

			void* pTrampoline = AllocateCodeBuffer(pTarget, ct.trampoline.size());
			if (pTrampoline == NULL)
			{
				return MH_ERROR_MEMORY_ALLOC;
			}
#if defined _M_X64
			void* pTable = AllocateDataBuffer(pTrampoline, (ct.table.size() + 1) * sizeof(uintptr_t));
			if (pTable == NULL)
			{
				return MH_ERROR_MEMORY_ALLOC;
			}
#endif

			ct.pTrampoline = pTrampoline;
#if defined _M_X64
			ct.pTable = pTable;
#endif
			if (!ResolveTemporaryAddresses(ct))
			{
				return MH_ERROR_UNSUPPORTED_FUNCTION;
			}

			memcpy(pTrampoline, &ct.trampoline[ 0 ], ct.trampoline.size());
#if defined _M_X64
			if (ct.table.size() != 0)
			{
				memcpy(pTable, &ct.table[ 0 ], ct.table.size() * sizeof(uintptr_t));
			}
#endif

			// �^�[�Q�b�g�֐��̃o�b�N�A�b�v��Ƃ�
			void* pBackup = AllocateDataBuffer(NULL, sizeof(JMP_REL));
			if (pBackup == NULL)
			{
				return MH_ERROR_MEMORY_ALLOC;
			}

			memcpy(pBackup, pTarget, sizeof(JMP_REL));

			// ���p�֐���쐬����
#if defined _M_X64
			void* pRelay = AllocateCodeBuffer(pTarget, sizeof(JMP_ABS));
			if (pRelay == NULL)
			{
				return MH_ERROR_MEMORY_ALLOC;
			}

			WriteAbsoluteJump(pRelay, pDetour, reinterpret_cast<uintptr_t*>(pTable) + ct.table.size());
#endif
			CommitBuffer();
			committed = true;

			// �t�b�N���̓o�^
			HOOK_ENTRY hook = { 0 };
			hook.pTarget = pTarget;
			hook.pDetour = pDetour;
#if defined _M_X64
			hook.pRelay  = pRelay;
#endif
			hook.pTrampoline = pTrampoline;
			hook.pBackup = pBackup;
			hook.oldIPs = ct.oldIPs;
			hook.newIPs = ct.newIPs;

			std::vector<HOOK_ENTRY>::iterator i	= std::lower_bound(gHooks.begin(), gHooks.end(), hook);
			i = gHooks.insert(i, hook);
			pHook = &(*i);

		}

		// �^�[�Q�b�g�֐��̖`���ɁA���p�֐��܂��̓t�b�N�֐��ւ̃W�����v���������
		{
			ScopedThreadExclusive tex(pHook->oldIPs, pHook->newIPs);

			DWORD oldProtect;
			if (!VirtualProtect(pHook->pTarget, sizeof(JMP_REL), PAGE_EXECUTE_READWRITE, &oldProtect))
			{
				return MH_ERROR_MEMORY_PROTECT;
			}

#if defined _M_X64
			WriteRelativeJump(pHook->pTarget, pHook->pRelay);
#elif defined _M_IX86
			WriteRelativeJump(pHook->pTarget, pHook->pDetour);
#endif
			VirtualProtect(pHook->pTarget, sizeof(JMP_REL), oldProtect, &oldProtect);
		}

		pHook->isInstalled = true;
		return MH_OK;
	}

	MH_STATUS UninstallHook(void* pTarget)
	{
		CriticalSection::ScopedLock lock(gCS);

		if (!IsExecutableAddress(pTarget))
		{
			return MH_ERROR_NOT_EXECUTABLE;
		}

		HOOK_ENTRY *pHook = FindHook(pTarget);
		if (pHook == NULL || !pHook->isInstalled)
		{
			return MH_ERROR_NOT_INSTALLED;
		}

		// �^�[�Q�b�g�֐��̖`��������߂������B���͍ė��p�̂��ߎc���Ă���
		{
			ScopedThreadExclusive tex(pHook->oldIPs, pHook->newIPs);

			DWORD oldProtect;
			if (!VirtualProtect(pHook->pTarget, sizeof(JMP_REL), PAGE_EXECUTE_READWRITE, &oldProtect))
			{
				return MH_ERROR_MEMORY_PROTECT;
			}

			memcpy(pHook->pTarget, pHook->pBackup, sizeof(JMP_REL));

			VirtualProtect(pHook->pTarget, sizeof(JMP_REL), oldProtect, &oldProtect);
		}

		pHook->isInstalled = false;
		return MH_OK;
	}

	MH_STATUS GetOriginalFunction(void* pTarget, void*& pTrampoline)
	{
		CriticalSection::ScopedLock lock(gCS);

		HOOK_ENTRY *pHook = FindHook(pTarget);
		if (pHook == NULL || !pHook->isInstalled)
		{
			return MH_ERROR_NOT_INSTALLED;
		}

		pTrampoline = pHook->pTrampoline;
		return MH_OK;
	}

	MH_STATUS GetHookCount(size_t& count)
	{
		using std::tr1::bind;
		using std::tr1::placeholders::_1;

		CriticalSection::ScopedLock lock(gCS);

		count = std::count_if(gHooks.begin(), gHooks.end(), bind(IsHookInstalled, _1));
		return MH_OK;
	}
}
namespace MinHook { namespace
{
	bool IsHookInstalled(const HOOK_ENTRY& hook)
	{
		return (hook.isInstalled);
	}

	HOOK_ENTRY* FindHook(void* const pTarget)
	{
		std::vector<HOOK_ENTRY>::iterator i 
			= std::lower_bound(gHooks.begin(), gHooks.end(), pTarget);
		if (i != gHooks.end() && i->pTarget == pTarget)
		{
			return &(*i);
		}

		return NULL;
	}

	bool IsExecutableAddress(void* pAddress)
	{
		static const DWORD PageExecuteMask 
			= (PAGE_EXECUTE | PAGE_EXECUTE_READ | PAGE_EXECUTE_READWRITE | PAGE_EXECUTE_WRITECOPY); 

		// �����蓖�Ă���s�s�”\�ȗ̈��`�F�b�N
		MEMORY_BASIC_INFORMATION mi = { 0 };
		VirtualQuery(pAddress, &mi, sizeof(mi));

		return ((mi.Protect & PageExecuteMask) != 0);
	}

	void WriteRelativeJump(void* pFrom, void* const pTo)
	{
		JMP_REL *pInst = reinterpret_cast<JMP_REL*>(pFrom);
		pInst->opcode = 0xE9;
		pInst->operand = static_cast<uint32_t>(
			reinterpret_cast<char*>(pTo) - (reinterpret_cast<char*>(pFrom) + sizeof(JMP_REL)));
	}

	void WriteAbsoluteJump(void* pFrom, void* const pTo, void* pTable)
	{
		JMP_ABS *pInst = reinterpret_cast<JMP_ABS*>(pFrom);
		pInst->opcode = 0x25FF;
		pInst->operand = static_cast<uint32_t>(
			reinterpret_cast<char*>(pTable) - (reinterpret_cast<char*>(pFrom) + sizeof(JMP_ABS)));

		void** pAddr = reinterpret_cast<void**>(pTable);
		*pAddr = pTo;
	}



	template <typename T>
	bool operator <(const HOOK_ENTRY& lhs, const T& rhs) 
	{ 
		return lhs.pTarget < reinterpret_cast<void*>(rhs); 
	}

	template <typename T>
	bool operator <(const T& lhs, const HOOK_ENTRY& rhs) 
	{ 
		return reinterpret_cast<void*>(lhs) < rhs.pTarget; 
	}

	bool operator <(const HOOK_ENTRY& lhs, const HOOK_ENTRY& rhs)
	{ 
		return lhs.pTarget < rhs.pTarget;
	}
}}

By viewing downloads associated with this article you agree to the Terms of use 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 BSD License

About the Author

Tsuda Kageyu
Software Developer
Japan Japan
Member
In 1985, I got my first computer Casio MX-10, the cheapest one of the MSX computers. Then I began programming in BASIC and assembly language, and have experienced over ten languages from that time on.
Now, my primary languages are C++ and C#. Working for a small company in my home town, the countryside of Japan.

Permalink | Advertise | Privacy | Mobile
Web02 | 2.6.130516.1 | Last Updated 28 Nov 2009
Article Copyright 2009 by Tsuda Kageyu
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid