Skip to content
Open
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
31 changes: 30 additions & 1 deletion Source/KNSoft.SlimDetours/SlimDetours.h
Original file line number Diff line number Diff line change
Expand Up @@ -62,11 +62,40 @@ SlimDetoursAttach(
_Inout_ PVOID* ppPointer,
_In_ PVOID pDetour);

typedef struct _DETOUR_DETACH_OPTIONS
{
// Receives the trampoline of the detached hook, to be released by the caller with
// SlimDetoursFreeTrampoline, or NULL to let the transaction release it. It is written when the
// transaction commits, so the variable must stay alive until then, and is set to NULL when
// there is no trampoline to release.
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);
}

// Release a trampoline taken over via DETOUR_DETACH_OPTIONS.
HRESULT
NTAPI
SlimDetoursFreeTrampoline(
_Frees_ptr_opt_ _Post_invalid_ PVOID pTrampoline);

PVOID
NTAPI
Expand Down
11 changes: 9 additions & 2 deletions Source/KNSoft.SlimDetours/SlimDetours.inl
Original file line number Diff line number Diff line change
Expand Up @@ -102,20 +102,27 @@ _STATIC_ASSERT(sizeof(DETOUR_TRAMPOLINE) == 104);
_STATIC_ASSERT(sizeof(DETOUR_TRAMPOLINE) == 192);
#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;
#if defined(_M_ARM64EC)
BOOL fTargetArm64Ec : 1;
#endif
PBYTE* ppbPointer;
PBYTE pbTarget;
PDETOUR_TRAMPOLINE pTrampoline;
ULONG dwPerm;
PVOID* ppTrampolineToFreeManually;
};

/* Memory management */
Expand Down
4 changes: 2 additions & 2 deletions Source/KNSoft.SlimDetours/Thread.c
Original file line number Diff line number Diff line change
Expand Up @@ -317,7 +317,7 @@ detour_thread_update(
bUpdateContext = FALSE;
for (PDETOUR_OPERATION 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 @@ -339,7 +339,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
93 changes: 78 additions & 15 deletions Source/KNSoft.SlimDetours/Transaction.c
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ SlimDetoursTransactionBeginEx(
Status = detour_writable_trampoline_regions();
if (!NT_SUCCESS(Status))
{
detour_runnable_trampoline_regions();
goto fail;
}

Expand Down Expand Up @@ -145,7 +146,7 @@ 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;
Expand Down Expand Up @@ -205,7 +206,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 Down Expand Up @@ -234,14 +235,19 @@ 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);
}

if (o->dwOperation == DETOUR_OPERATION_NONE || o->ppTrampolineToFreeManually != NULL)
{
// The trampoline outlives the transaction, put the 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 @@ -323,11 +329,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 @@ -599,15 +612,15 @@ SlimDetoursAttach(
pTrampoline->rbCode[8], pTrampoline->rbCode[9],
pTrampoline->rbCode[10], pTrampoline->rbCode[11]);

o->fIsAdd = TRUE;
o->fIsRemove = FALSE;
o->dwOperation = DETOUR_OPERATION_ADD;
#if defined(_M_ARM64EC)
o->fTargetArm64Ec = fTargetArm64Ec;
#endif
o->ppbPointer = (PBYTE*)ppPointer;
o->pTrampoline = pTrampoline;
o->pbTarget = pbTarget;
o->dwPerm = dwOld;
o->ppTrampolineToFreeManually = NULL;
o->pNext = s_pPendingOperations;
s_pPendingOperations = o;

Expand All @@ -616,9 +629,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 All @@ -628,6 +642,11 @@ SlimDetoursDetach(
BOOL fTargetArm64Ec, fDetourArm64Ec;
#endif

if (pOptions->ppTrampolineToFreeManually != NULL)
{
*pOptions->ppTrampolineToFreeManually = NULL;
}

if (s_nPendingThreadId != NtCurrentThreadId())
{
return HRESULT_FROM_NT(STATUS_TRANSACTIONAL_CONFLICT);
Expand Down Expand Up @@ -682,21 +701,65 @@ SlimDetoursDetach(
goto fail;
}

o->fIsAdd = FALSE;
o->fIsRemove = TRUE;
o->dwOperation = DETOUR_OPERATION_REMOVE;
#if defined(_M_ARM64EC)
o->fTargetArm64Ec = fTargetArm64Ec;
#endif
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(
_Frees_ptr_opt_ _Post_invalid_ PVOID pTrampoline)
{
NTSTATUS Status;

if (pTrampoline == NULL)
{
return HRESULT_FROM_NT(STATUS_SUCCESS);
}

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

// Make sure the trampoline pages are writable.
Status = bInTransaction ? STATUS_SUCCESS : detour_writable_trampoline_regions();
if (NT_SUCCESS(Status))
{
detour_free_trampoline((PDETOUR_TRAMPOLINE)pTrampoline);
detour_free_trampoline_region_if_unused((PDETOUR_TRAMPOLINE)pTrampoline);
}

if (!bInTransaction)
{
// Make sure the trampoline pages are no longer writable, including the ones that were
// already flipped when detour_writable_trampoline_regions failed part way through.
detour_runnable_trampoline_regions();
#ifdef _MSC_VER
#pragma warning(disable: __WARNING_INTERLOCKED_ACCESS)
#endif
s_nPendingThreadId = NULL;
#ifdef _MSC_VER
#pragma warning(default: __WARNING_INTERLOCKED_ACCESS)
#endif
}
return HRESULT_FROM_NT(Status);
}

HRESULT
NTAPI
SlimDetoursUninitialize(VOID)
Expand Down
Loading