Skip to content

Add SlimDetoursDetachEx to allow to free the trampoline manually #19

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 25 additions & 1 deletion Source/SlimDetours/SlimDetours.h
Original file line number Diff line number Diff line change
Expand Up @@ -62,11 +62,35 @@ SlimDetoursAttach(
_Inout_ PVOID* ppPointer,
_In_ PVOID pDetour);

typedef struct _DETOUR_DETACH_OPTIONS
{
PVOID *ppTrampolineToFreeManually;
} DETOUR_DETACH_OPTIONS, *PDETOUR_DETACH_OPTIONS;

typedef const DETOUR_DETACH_OPTIONS* PCDETOUR_DETACH_OPTIONS;

HRESULT
NTAPI
SlimDetoursDetachEx(
_Inout_ PVOID* ppPointer,
_In_ PVOID pDetour,
_In_ PCDETOUR_DETACH_OPTIONS pOptions);

FORCEINLINE
HRESULT
SlimDetoursDetach(
_Inout_ PVOID* ppPointer,
_In_ PVOID pDetour);
_In_ PVOID pDetour)
{
DETOUR_DETACH_OPTIONS Options;
Options.ppTrampolineToFreeManually = NULL;
return SlimDetoursDetachEx(ppPointer, pDetour, &Options);
}

HRESULT
NTAPI
SlimDetoursFreeTrampoline(
_In_ PVOID pTrampoline);

PVOID
NTAPI
Expand Down
15 changes: 13 additions & 2 deletions Source/SlimDetours/SlimDetours.inl
Original file line number Diff line number Diff line change
Expand Up @@ -100,17 +100,24 @@ _STATIC_ASSERT(sizeof(DETOUR_TRAMPOLINE) == 96);
_STATIC_ASSERT(sizeof(DETOUR_TRAMPOLINE) == 184);
#endif

enum
{
DETOUR_OPERATION_NONE = 0,
DETOUR_OPERATION_ADD,
DETOUR_OPERATION_REMOVE,
};

typedef struct _DETOUR_OPERATION DETOUR_OPERATION, *PDETOUR_OPERATION;

struct _DETOUR_OPERATION
{
PDETOUR_OPERATION pNext;
BOOL fIsAdd : 1;
BOOL fIsRemove : 1;
DWORD dwOperation;
PBYTE* ppbPointer;
PBYTE pbTarget;
PDETOUR_TRAMPOLINE pTrampoline;
ULONG dwPerm;
PVOID* ppTrampolineToFreeManually;
};

/* Memory management */
Expand Down Expand Up @@ -269,6 +276,10 @@ detour_free_trampoline(

VOID detour_free_unused_trampoline_regions(VOID);

VOID
detour_free_trampoline_region_if_unused(
_In_ PDETOUR_TRAMPOLINE pTrampoline);

BYTE
detour_align_from_trampoline(
_In_ PDETOUR_TRAMPOLINE pTrampoline,
Expand Down
4 changes: 2 additions & 2 deletions Source/SlimDetours/Thread.c
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@ detour_thread_update(
bUpdateContext = FALSE;
for (o = PendingOperations; o != NULL && !bUpdateContext; o = o->pNext)
{
if (o->fIsRemove)
if (o->dwOperation == DETOUR_OPERATION_REMOVE)
{
if (cxt.CONTEXT_PC >= (ULONG_PTR)o->pTrampoline->rbCode &&
cxt.CONTEXT_PC < ((ULONG_PTR)o->pTrampoline->rbCode + RTL_FIELD_SIZE(DETOUR_TRAMPOLINE, rbCode)))
Expand All @@ -204,7 +204,7 @@ detour_thread_update(
bUpdateContext = TRUE;
}
#endif
} else if (o->fIsAdd)
} else if (o->dwOperation == DETOUR_OPERATION_ADD)
{
if (cxt.CONTEXT_PC >= (ULONG_PTR)o->pbTarget &&
cxt.CONTEXT_PC < ((ULONG_PTR)o->pbTarget + o->pTrampoline->cbRestore))
Expand Down
47 changes: 39 additions & 8 deletions Source/SlimDetours/Trampoline.c
Original file line number Diff line number Diff line change
Expand Up @@ -400,24 +400,29 @@ detour_is_region_empty(
return TRUE;
}

static
VOID
detour_free_unused_trampoline_regions(VOID)
detour_free_region(
_In_ PDETOUR_REGION* ppRegionBase,
_In_ PDETOUR_REGION pRegion)
{
PVOID pMem;
SIZE_T sMem;
*ppRegionBase = pRegion->pNext;
PVOID pMem = pRegion;
SIZE_T sMem = 0;
NtFreeVirtualMemory(NtCurrentProcess(), &pMem, &sMem, MEM_RELEASE);
}

VOID
detour_free_unused_trampoline_regions(VOID)
{
PDETOUR_REGION* ppRegionBase = &s_pRegions;
PDETOUR_REGION pRegion = s_pRegions;

while (pRegion != NULL)
{
if (detour_is_region_empty(pRegion))
{
*ppRegionBase = pRegion->pNext;

pMem = pRegion;
sMem = 0;
NtFreeVirtualMemory(NtCurrentProcess(), &pMem, &sMem, MEM_RELEASE);
detour_free_region(ppRegionBase, pRegion);
s_pRegion = NULL;
} else
{
Expand All @@ -427,6 +432,32 @@ detour_free_unused_trampoline_regions(VOID)
}
}

VOID
detour_free_trampoline_region_if_unused(
_In_ PDETOUR_TRAMPOLINE pTrampoline)
{
PDETOUR_REGION pTargetRegion = (PDETOUR_REGION)((ULONG_PTR)pTrampoline & ~(ULONG_PTR)0xffff);

PDETOUR_REGION* ppRegionBase = &s_pRegions;
PDETOUR_REGION pRegion = s_pRegions;

while (pRegion != NULL)
{
if (pRegion == pTargetRegion)
{
if (detour_is_region_empty(pRegion))
{
detour_free_region(ppRegionBase, pRegion);
s_pRegion = NULL;
}
break;
}

ppRegionBase = &pRegion->pNext;
pRegion = *ppRegionBase;
}
}

BYTE
detour_align_from_trampoline(
_In_ PDETOUR_TRAMPOLINE pTrampoline,
Expand Down
100 changes: 85 additions & 15 deletions Source/SlimDetours/Transaction.c
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ SlimDetoursTransactionAbort(VOID)
PVOID pMem;
SIZE_T sMem;
DWORD dwOld;
BOOL freed = FALSE;

if (s_nPendingThreadId != NtCurrentThreadId())
{
Expand All @@ -119,17 +120,22 @@ SlimDetoursTransactionAbort(VOID)
pMem = o->pbTarget;
sMem = o->pTrampoline->cbRestore;
NtProtectVirtualMemory(NtCurrentProcess(), &pMem, &sMem, o->dwPerm, &dwOld);
if (o->fIsAdd)
if (o->dwOperation == DETOUR_OPERATION_ADD)
{
detour_free_trampoline(o->pTrampoline);
o->pTrampoline = NULL;
freed = TRUE;
}

PDETOUR_OPERATION n = o->pNext;
detour_memory_free(o);
o = n;
}
s_pPendingOperations = NULL;
if (freed)
{
detour_free_unused_trampoline_regions();
}

// Make sure the trampoline pages are no longer writable.
detour_runnable_trampoline_regions();
Expand Down Expand Up @@ -171,7 +177,7 @@ SlimDetoursTransactionCommit(VOID)
o = s_pPendingOperations;
do
{
if (o->fIsRemove)
if (o->dwOperation == DETOUR_OPERATION_REMOVE)
{
// Check if the jmps still points where we expect, otherwise someone might have hooked us.
BOOL hookIsStillThere =
Expand All @@ -188,14 +194,16 @@ SlimDetoursTransactionCommit(VOID)
NtFlushInstructionCache(NtCurrentProcess(), o->pbTarget, o->pTrampoline->cbRestore);
} else
{
// Don't remove in this case, put in bypass mode and leak trampoline.
o->fIsRemove = FALSE;
o->pTrampoline->pbDetour = o->pTrampoline->rbCode;
// Don't remove and leak trampoline in this case.
o->dwOperation = DETOUR_OPERATION_NONE;
DETOUR_TRACE("detours: Leaked hook on pbTarget=%p due to external hooking\n", o->pbTarget);
}

// Put hook in bypass mode.
o->pTrampoline->pbDetour = o->pTrampoline->rbCode;

*o->ppbPointer = o->pbTarget;
} else if (o->fIsAdd)
} else if (o->dwOperation == DETOUR_OPERATION_ADD)
{
DETOUR_TRACE("detours: pbTramp =%p, pbRemain=%p, pbDetour=%p, cbRestore=%u\n",
o->pTrampoline,
Expand Down Expand Up @@ -262,11 +270,18 @@ SlimDetoursTransactionCommit(VOID)
pMem = o->pbTarget;
sMem = o->pTrampoline->cbRestore;
NtProtectVirtualMemory(NtCurrentProcess(), &pMem, &sMem, o->dwPerm, &dwOld);
if (o->fIsRemove)
if (o->dwOperation == DETOUR_OPERATION_REMOVE)
{
detour_free_trampoline(o->pTrampoline);
if (!o->ppTrampolineToFreeManually)
{
detour_free_trampoline(o->pTrampoline);
freed = TRUE;
} else
{
// The caller is responsible for freeing the trampoline.
*o->ppTrampolineToFreeManually = o->pTrampoline;
}
o->pTrampoline = NULL;
freed = TRUE;
}

n = o->pNext;
Expand Down Expand Up @@ -333,6 +348,7 @@ SlimDetoursAttach(
if (pTrampoline != NULL)
{
detour_free_trampoline(pTrampoline);
detour_free_trampoline_region_if_unused(pTrampoline);
pTrampoline = NULL;
}
if (o != NULL)
Expand Down Expand Up @@ -485,8 +501,7 @@ SlimDetoursAttach(
pTrampoline->rbCode[8], pTrampoline->rbCode[9],
pTrampoline->rbCode[10], pTrampoline->rbCode[11]);

o->fIsAdd = TRUE;
o->fIsRemove = FALSE;
o->dwOperation = DETOUR_OPERATION_ADD;
o->ppbPointer = (PBYTE*)ppPointer;
o->pTrampoline = pTrampoline;
o->pbTarget = pbTarget;
Expand All @@ -499,9 +514,10 @@ SlimDetoursAttach(

HRESULT
NTAPI
SlimDetoursDetach(
SlimDetoursDetachEx(
_Inout_ PVOID* ppPointer,
_In_ PVOID pDetour)
_In_ PVOID pDetour,
_In_ PCDETOUR_DETACH_OPTIONS pOptions)
{
NTSTATUS Status;
PVOID pMem;
Expand Down Expand Up @@ -549,18 +565,72 @@ SlimDetoursDetach(
goto fail;
}

o->fIsAdd = FALSE;
o->fIsRemove = TRUE;
o->dwOperation = DETOUR_OPERATION_REMOVE;
o->ppbPointer = (PBYTE*)ppPointer;
o->pTrampoline = pTrampoline;
o->pbTarget = pbTarget;
o->dwPerm = dwOld;
o->ppTrampolineToFreeManually = pOptions->ppTrampolineToFreeManually;
o->pNext = s_pPendingOperations;
s_pPendingOperations = o;

return HRESULT_FROM_NT(STATUS_SUCCESS);
}

HRESULT
NTAPI
SlimDetoursFreeTrampoline(
_In_ PVOID pTrampoline)
{
if (pTrampoline == NULL)
{
return HRESULT_FROM_NT(STATUS_SUCCESS);
}

// This function can be called as part of a transaction or outside of a transaction.
ULONG nPrevPendingThreadId = _InterlockedCompareExchange(&s_nPendingThreadId, NtCurrentThreadId(), 0);
BOOL bInTransaction = nPrevPendingThreadId != 0;
if (bInTransaction && nPrevPendingThreadId != NtCurrentThreadId())
{
return HRESULT_FROM_NT(STATUS_TRANSACTIONAL_CONFLICT);
}

NTSTATUS Status;

if (!bInTransaction)
{
// Make sure the trampoline pages are writable.
Status = detour_writable_trampoline_regions();
if (!NT_SUCCESS(Status))
{
goto fail;
}
}

detour_free_trampoline((PDETOUR_TRAMPOLINE)pTrampoline);
detour_free_trampoline_region_if_unused((PDETOUR_TRAMPOLINE)pTrampoline);

if (!bInTransaction)
{
detour_runnable_trampoline_regions();
}

Status = STATUS_SUCCESS;

fail:
if (!bInTransaction)
{
#ifdef _MSC_VER
#pragma warning(disable: __WARNING_INTERLOCKED_ACCESS)
#endif
s_nPendingThreadId = 0;
#ifdef _MSC_VER
#pragma warning(default: __WARNING_INTERLOCKED_ACCESS)
#endif
}
return HRESULT_FROM_NT(Status);
}

HRESULT
NTAPI
SlimDetoursUninitialize(VOID)
Expand Down