Skip to content

Commit

Permalink
Merge branch 'fix-remove-max-limit-patch'
Browse files Browse the repository at this point in the history
  • Loading branch information
poizan42 committed Mar 5, 2014
2 parents efa4d7e + 7140645 commit e96bed5
Show file tree
Hide file tree
Showing 4 changed files with 175 additions and 1,010 deletions.
247 changes: 175 additions & 72 deletions mhook-lib/mhook.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -110,9 +110,10 @@ struct MHOOKS_TRAMPOLINE {
// in the original location
BYTE codeUntouched[MHOOKS_MAX_CODE_BYTES]; // placeholder for unmodified original code
// (we patch IP-relative addressing)
MHOOKS_TRAMPOLINE* pPrevTrampoline; // When in the free list, thess are pointers to the prev and next entry.
MHOOKS_TRAMPOLINE* pNextTrampoline; // When not in the free list, this is a pointer to the prev and next trampoline in use.
};


//=========================================================================
// The patch data structures - store info about rip-relative instructions
// during hook placement
Expand All @@ -134,26 +135,28 @@ struct MHOOKS_PATCHDATA
// Global vars
static BOOL g_bVarsInitialized = FALSE;
static CRITICAL_SECTION g_cs;
static MHOOKS_TRAMPOLINE* g_pHooks[MHOOKS_MAX_SUPPORTED_HOOKS];
static MHOOKS_TRAMPOLINE* g_pHooks = NULL;
static MHOOKS_TRAMPOLINE* g_pFreeList = NULL;
static DWORD g_nHooksInUse = 0;
static HANDLE* g_hThreadHandles = NULL;
static DWORD g_nThreadHandles = 0;
#define MHOOK_JMPSIZE 5
#define MHOOK_MINALLOCSIZE 4096

//=========================================================================
// Toolhelp defintions so the functions can be dynamically bound to
typedef HANDLE (WINAPI * _CreateToolhelp32Snapshot)(
DWORD dwFlags,
DWORD dwFlags,
DWORD th32ProcessID
);

typedef BOOL (WINAPI * _Thread32First)(
HANDLE hSnapshot,
HANDLE hSnapshot,
LPTHREADENTRY32 lpte
);

typedef BOOL (WINAPI * _Thread32Next)(
HANDLE hSnapshot,
HANDLE hSnapshot,
LPTHREADENTRY32 lpte
);

Expand All @@ -163,11 +166,48 @@ _CreateToolhelp32Snapshot fnCreateToolhelp32Snapshot = (_CreateToolhelp32Snapsho
_Thread32First fnThread32First = (_Thread32First) GetProcAddress(GetModuleHandle(L"kernel32"), "Thread32First");
_Thread32Next fnThread32Next = (_Thread32Next) GetProcAddress(GetModuleHandle(L"kernel32"), "Thread32Next");

//=========================================================================
// Internal function:
//
// Remove the trampoline from the specified list, updating the head pointer
// if necessary.
//=========================================================================
static VOID ListRemove(MHOOKS_TRAMPOLINE** pListHead, MHOOKS_TRAMPOLINE* pNode) {
if (pNode->pPrevTrampoline) {
pNode->pPrevTrampoline->pNextTrampoline = pNode->pNextTrampoline;
}

if (pNode->pNextTrampoline) {
pNode->pNextTrampoline->pPrevTrampoline = pNode->pPrevTrampoline;
}

if ((*pListHead) == pNode) {
(*pListHead) = pNode->pNextTrampoline;
assert((*pListHead)->pPrevTrampoline == NULL);
}

pNode->pPrevTrampoline = NULL;
pNode->pNextTrampoline = NULL;
}

//=========================================================================
// Internal function:
//
// Prepend the trampoline from the specified list and update the head pointer.
//=========================================================================
static VOID ListPrepend(MHOOKS_TRAMPOLINE** pListHead, MHOOKS_TRAMPOLINE* pNode) {
pNode->pPrevTrampoline = NULL;
pNode->pNextTrampoline = (*pListHead);
if ((*pListHead)) {
(*pListHead)->pPrevTrampoline = pNode;
}
(*pListHead) = pNode;
}

//=========================================================================
static VOID EnterCritSec() {
if (!g_bVarsInitialized) {
InitializeCriticalSection(&g_cs);
ZeroMemory(g_pHooks, sizeof(g_pHooks));
g_bVarsInitialized = TRUE;
}
EnterCriticalSection(&g_cs);
Expand Down Expand Up @@ -265,66 +305,128 @@ static PBYTE EmitJump(PBYTE pbCode, PBYTE pbJumpTo) {
return pbCode;
}


//=========================================================================
// Internal function:
//
// Will try to allocate the trampoline structure within 2 gigabytes of
// the target function.
// Round down to the next multiple of rndDown
//=========================================================================
static MHOOKS_TRAMPOLINE* TrampolineAlloc(PBYTE pSystemFunction, S64 nLimitUp, S64 nLimitDown) {
static size_t RoundDown(size_t addr, size_t rndDown)
{
return (addr / rndDown) * rndDown;
}

MHOOKS_TRAMPOLINE* pTrampoline = NULL;
//=========================================================================
// Internal function:
//
// Will attempt allocate a block of memory within the specified range, as
// near as possible to the specified function.
//=========================================================================
static MHOOKS_TRAMPOLINE* BlockAlloc(PBYTE pSystemFunction, PBYTE pbLower, PBYTE pbUpper) {
SYSTEM_INFO sSysInfo = {0};
::GetSystemInfo(&sSysInfo);

// Always allocate in bulk, in case the system actually has a smaller allocation granularity than MINALLOCSIZE.
const ptrdiff_t cAllocSize = max(sSysInfo.dwAllocationGranularity, MHOOK_MINALLOCSIZE);

MHOOKS_TRAMPOLINE* pRetVal = NULL;
PBYTE pModuleGuess = (PBYTE) RoundDown((size_t)pSystemFunction, cAllocSize);
int loopCount = 0;
for (PBYTE pbAlloc = pModuleGuess; pbLower < pbAlloc && pbAlloc < pbUpper; ++loopCount) {
// determine current state
MEMORY_BASIC_INFORMATION mbi;
ODPRINTF((L"mhooks: BlockAlloc: Looking at address %p", pbAlloc));
if (!VirtualQuery(pbAlloc, &mbi, sizeof(mbi)))
break;
// free & large enough?
if (mbi.State == MEM_FREE && mbi.RegionSize >= (unsigned)cAllocSize) {
// and then try to allocate it
pRetVal = (MHOOKS_TRAMPOLINE*) VirtualAlloc(pbAlloc, cAllocSize, MEM_COMMIT|MEM_RESERVE, PAGE_EXECUTE_READWRITE);
if (pRetVal) {
size_t trampolineCount = cAllocSize / sizeof(MHOOKS_TRAMPOLINE);
ODPRINTF((L"mhooks: BlockAlloc: Allocated block at %p as %d trampolines", pRetVal, trampolineCount));

pRetVal[0].pPrevTrampoline = NULL;
pRetVal[0].pNextTrampoline = &pRetVal[1];

// prepare them by having them point down the line at the next entry.
for (size_t s = 1; s < trampolineCount; ++s) {
pRetVal[s].pPrevTrampoline = &pRetVal[s - 1];
pRetVal[s].pNextTrampoline = &pRetVal[s + 1];
}

// do we have room to store this guy?
if (g_nHooksInUse < MHOOKS_MAX_SUPPORTED_HOOKS) {

// determine lower and upper bounds for the allocation locations.
// in the basic scenario this is +/- 2GB but IP-relative instructions
// found in the original code may require a smaller window.
PBYTE pLower = pSystemFunction + nLimitUp;
pLower = pLower < (PBYTE)(DWORD_PTR)0x0000000080000000 ?
(PBYTE)(0x1) : (PBYTE)(pLower - (PBYTE)0x7fff0000);
PBYTE pUpper = pSystemFunction + nLimitDown;
pUpper = pUpper < (PBYTE)(DWORD_PTR)0xffffffff80000000 ?
(PBYTE)(pUpper + (DWORD_PTR)0x7ff80000) : (PBYTE)(DWORD_PTR)0xfffffffffff80000;
ODPRINTF((L"mhooks: TrampolineAlloc: Allocating for %p between %p and %p", pSystemFunction, pLower, pUpper));

SYSTEM_INFO sSysInfo = {0};
::GetSystemInfo(&sSysInfo);

// go through the available memory blocks and try to allocate a chunk for us
for (PBYTE pbAlloc = pLower; pbAlloc < pUpper;) {
// determine current state
MEMORY_BASIC_INFORMATION mbi;
ODPRINTF((L"mhooks: TrampolineAlloc: Looking at address %p", pbAlloc));
if (!VirtualQuery(pbAlloc, &mbi, sizeof(mbi)))
// last entry points to the current head of the free list
pRetVal[trampolineCount - 1].pNextTrampoline = g_pFreeList;
break;
// free & large enough?
if (mbi.State == MEM_FREE && mbi.RegionSize >= sizeof(MHOOKS_TRAMPOLINE) && mbi.RegionSize >= sSysInfo.dwAllocationGranularity) {
// yes, align the pointer to the 64K boundary first
pbAlloc = (PBYTE)(ULONG_PTR((ULONG_PTR(pbAlloc) + (sSysInfo.dwAllocationGranularity-1)) / sSysInfo.dwAllocationGranularity) * sSysInfo.dwAllocationGranularity);
// and then try to allocate it
pTrampoline = (MHOOKS_TRAMPOLINE*)VirtualAlloc(pbAlloc, sizeof(MHOOKS_TRAMPOLINE), MEM_COMMIT|MEM_RESERVE, PAGE_EXECUTE_READ);
if (pTrampoline) {
ODPRINTF((L"mhooks: TrampolineAlloc: Allocated block at %p as the trampoline", pTrampoline));
break;
}
}
// continue the search
pbAlloc = (PBYTE)mbi.BaseAddress + mbi.RegionSize;
}

// This is a spiral, should be -1, 1, -2, 2, -3, 3, etc. (* cAllocSize)
ptrdiff_t bytesToOffset = (cAllocSize * (loopCount + 1) * ((loopCount % 2 == 0) ? -1 : 1));
pbAlloc = pbAlloc + bytesToOffset;
}

return pRetVal;
}

// found and allocated a trampoline?
if (pTrampoline) {
// put it into our list so we know we'll have to free it
for (DWORD i=0; i<MHOOKS_MAX_SUPPORTED_HOOKS; i++) {
if (g_pHooks[i] == NULL) {
g_pHooks[i] = pTrampoline;
g_nHooksInUse++;
break;
}
}
//=========================================================================
// Internal function:
//
// Will try to allocate a big block of memory inside the required range.
//=========================================================================
static MHOOKS_TRAMPOLINE* FindTrampolineInRange(PBYTE pLower, PBYTE pUpper) {
if (!g_pFreeList) {
return NULL;
}

// This is a standard free list, except we're doubly linked to deal with soem return shenanigans.
MHOOKS_TRAMPOLINE* curEntry = g_pFreeList;
while (curEntry) {
if ((MHOOKS_TRAMPOLINE*) pLower < curEntry && curEntry < (MHOOKS_TRAMPOLINE*) pUpper) {
ListRemove(&g_pFreeList, curEntry);

return curEntry;
}

curEntry = curEntry->pNextTrampoline;
}

return NULL;
}

//=========================================================================
// Internal function:
//
// Will try to allocate the trampoline structure within 2 gigabytes of
// the target function.
//=========================================================================
static MHOOKS_TRAMPOLINE* TrampolineAlloc(PBYTE pSystemFunction, S64 nLimitUp, S64 nLimitDown) {

MHOOKS_TRAMPOLINE* pTrampoline = NULL;

// determine lower and upper bounds for the allocation locations.
// in the basic scenario this is +/- 2GB but IP-relative instructions
// found in the original code may require a smaller window.
PBYTE pLower = pSystemFunction + nLimitUp;
pLower = pLower < (PBYTE)(DWORD_PTR)0x0000000080000000 ?
(PBYTE)(0x1) : (PBYTE)(pLower - (PBYTE)0x7fff0000);
PBYTE pUpper = pSystemFunction + nLimitDown;
pUpper = pUpper < (PBYTE)(DWORD_PTR)0xffffffff80000000 ?
(PBYTE)(pUpper + (DWORD_PTR)0x7ff80000) : (PBYTE)(DWORD_PTR)0xfffffffffff80000;
ODPRINTF((L"mhooks: TrampolineAlloc: Allocating for %p between %p and %p", pSystemFunction, pLower, pUpper));

// try to find a trampoline in the specified range
pTrampoline = FindTrampolineInRange(pLower, pUpper);
if (!pTrampoline) {
// if it we can't find it, then we need to allocate a new block and
// try again. Just fail if that doesn't work
g_pFreeList = BlockAlloc(pSystemFunction, pLower, pUpper);
pTrampoline = FindTrampolineInRange(pLower, pUpper);
}

// found and allocated a trampoline?
if (pTrampoline) {
ListPrepend(&g_pHooks, pTrampoline);
}

return pTrampoline;
Expand All @@ -336,12 +438,16 @@ static MHOOKS_TRAMPOLINE* TrampolineAlloc(PBYTE pSystemFunction, S64 nLimitUp, S
// Return the internal trampoline structure that belongs to a hooked function.
//=========================================================================
static MHOOKS_TRAMPOLINE* TrampolineGet(PBYTE pHookedFunction) {
for (DWORD i=0; i<MHOOKS_MAX_SUPPORTED_HOOKS; i++) {
if (g_pHooks[i]) {
if (g_pHooks[i]->codeTrampoline == pHookedFunction)
return g_pHooks[i];
MHOOKS_TRAMPOLINE* pCurrent = g_pHooks;

while (pCurrent) {
if (pCurrent->pHookFunction == pHookedFunction) {
return pCurrent;
}

pCurrent = pCurrent->pNextTrampoline;
}

return NULL;
}

Expand All @@ -351,20 +457,17 @@ static MHOOKS_TRAMPOLINE* TrampolineGet(PBYTE pHookedFunction) {
// Free a trampoline structure.
//=========================================================================
static VOID TrampolineFree(MHOOKS_TRAMPOLINE* pTrampoline, BOOL bNeverUsed) {
for (DWORD i=0; i<MHOOKS_MAX_SUPPORTED_HOOKS; i++) {
if (g_pHooks[i] == pTrampoline) {
g_pHooks[i] = NULL;
// It might be OK to call VirtualFree, but quite possibly it isn't:
// If a thread has some of our trampoline code on its stack
// and we yank the region from underneath it then it will
// surely crash upon returning. So instead of freeing the
// memory we just let it leak. Ugly, but safe.
if (bNeverUsed)
VirtualFree(pTrampoline, 0, MEM_RELEASE);
g_nHooksInUse--;
break;
}
ListRemove(&g_pHooks, pTrampoline);

// If a thread could feasinbly have some of our trampoline code
// on its stack and we yank the region from underneath it then it will
// surely crash upon returning. So instead of freeing the
// memory we just let it leak. Ugly, but safe.
if (bNeverUsed) {
ListPrepend(&g_pFreeList, pTrampoline);
}

g_nHooksInUse--;
}

//=========================================================================
Expand Down
3 changes: 0 additions & 3 deletions mhook-lib/mhook.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,3 @@

BOOL Mhook_SetHook(PVOID *ppSystemFunction, PVOID pHookFunction);
BOOL Mhook_Unhook(PVOID *ppHookedFunction);

#define MHOOKS_MAX_SUPPORTED_HOOKS 64

Loading

9 comments on commit e96bed5

@RelicOfTesla
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BAD CODE, DON'T USE IT, IT NOT WORK IN Mhook_Unhook

@SergiusTheBest
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@RelicOfTesla Could you provide a sample code that shows the bug?

@poizan42
Copy link
Author

@poizan42 poizan42 commented on e96bed5 Sep 19, 2014 via email

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@poizan42
Copy link
Author

@poizan42 poizan42 commented on e96bed5 Sep 19, 2014 via email

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@RelicOfTesla
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@SergiusTheBest
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@RelicOfTesla @poizan42 Confirming, unhooking doesn't work in the provided sample:

int (WINAPI* OldMessageBoxA)(
    __in_opt HWND hWnd,
    __in_opt LPCSTR lpText,
    __in_opt LPCSTR lpCaption,
    __in UINT uType) = MessageBoxA;
int WINAPI NewMessageBoxA(
    __in_opt HWND hWnd,
    __in_opt LPCSTR lpText,
    __in_opt LPCSTR lpCaption,
    __in UINT uType)
{
    return 0;
}
int _tmain(int argc, _TCHAR* argv[])
{
    Mhook_SetHook((void**)&OldMessageBoxA, NewMessageBoxA);
    Mhook_Unhook((void**)&OldMessageBoxA);
    MessageBoxA(0, 0, 0, 0); // calls NewMessageBoxA instead of original MessageBoxA
    return 0;
}

@dreamlayers
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here is a patch which makes unhooking work. This was tested on e58a58c via mhook-test.cpp. I was compiling in Cygwin with i686-w64-mingw32-gcc 4.8.3, with a few other minor changes to enable compiling with GCC. Those changes aren't included in the patch and shouldn't affect this issue.

diff --git a/mhook-lib/mhook.cpp b/mhook-lib/mhook.cpp
index 3380dce..a347633 100644
--- a/mhook-lib/mhook.cpp
+++ b/mhook-lib/mhook.cpp
@@ -183,7 +183,9 @@ static VOID ListRemove(MHOOKS_TRAMPOLINE** pListHead, MHOOKS_TRAMPOLINE* pNode)

    if ((*pListHead) == pNode) {
        (*pListHead) = pNode->pNextTrampoline;
-       assert((*pListHead)->pPrevTrampoline == NULL);
+       if (*pListHead != NULL) {
+           assert((*pListHead)->pPrevTrampoline == NULL);
+       }
    }

    pNode->pPrevTrampoline = NULL;
@@ -441,7 +443,7 @@ static MHOOKS_TRAMPOLINE* TrampolineGet(PBYTE pHookedFunction) {
    MHOOKS_TRAMPOLINE* pCurrent = g_pHooks;

    while (pCurrent) {
-       if (pCurrent->pHookFunction == pHookedFunction) {
+       if ((PBYTE)&(pCurrent->codeTrampoline) == pHookedFunction) {
            return pCurrent;
        }

The second change shows why unhooking currently fails. Mhook_Unhook(PVOID _ppHookedFunction) calls TrampolineGet((PBYTE)_ppHookedFunction). In other words, Mhook_Unhook() gets a pointer to a pointer, and it passes the value of that pointer to TrampolineGet(). The value of that pointer points to the trampoline code which jumps to the original function. So, it has to be compared to the location where that trampoline code would be in pCurrent.

The first change fixes a crash while removing the last element from a list of trampolines. In that case pNode->pNextTrampoline is NULL, and so *pListHead becomes NULL above the assert, causing NULL pointer dereferencing when evaluating argument of assert.

The patch contains tabs, and I'm not sure how to make them visible here. They appear in the source of the comment if I edit it, but if I copy from the displayed comment I get spaces.

@SergiusTheBest
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dreamlayers Looks good. The MessageBoxA sample is working now. Thanks.

@dreamlayers
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I created a pull request with that unhook fix: martona#3

Please sign in to comment.