Skip to main content

Royal Guard

· 2 min read

In this short post I will showcase a fun idea I have been playing with recently. That is, using hardware breakpoints to prevent certain libraries from loading.

Try me!

The exception handler first checks if the exception address is that of LdrLoadDll. If it is, it will grab the name of the library being loaded from the R8 register, as the function's third argument is a PUNICODE_STRING representing just that.

From there we can check if we want to prevent this library from loading, in which case it will emulate a successful result. Otherwise, execution will continue as normal.

LONG BlockDllHandler(IN CONST PEXCEPTION_POINTERS ExceptionInfo)
{
CONST PCONTEXT Context{ ExceptionInfo->ContextRecord };

if (CONST AUTO Exception = ExceptionInfo->ExceptionRecord
;
Exception->ExceptionCode == static_cast<ULONG>(EXCEPTION_SINGLE_STEP) && Exception->ExceptionAddress == LdrLoadDll)
{
//
// LdrLoadDll(PWSTR DllPath, PULONG DllCharacteristics, PUNICODE_STRING DllName, PVOID *DllHandle);
//

CONST PVOID ReturnAddress{ *reinterpret_cast<PVOID*> (Context->Rsp) };
CONST PUNICODE_STRING DllName { reinterpret_cast<PUNICODE_STRING> (Context->R8) };

//
// For the sake of the example, we will block user32
//

if(RtlCompareUnicodeStrings(DllName->Buffer, DllName->Length, L"user32.dll", sizeof(L"user32.dll"), TRUE))
{
//
// If the library is blacklisted, set it's RIP to the return address and the return value to STATUS_SUCCESS, mimicking success
//

Context->Rip = reinterpret_cast<ULONG_PTR>(ReturnAddress);
Context->Rsp += sizeof(PVOID);
Context->Rax = STATUS_SUCCESS;
}
else
{
//
// Otherwise, you may resume execution
//

Context->EFlags = Context->EFlags | (1 << 16);
}

return EXCEPTION_CONTINUE_EXECUTION;
}

return EXCEPTION_CONTINUE_SEARCH;
}

In order for this exception handler to be used, we need to register it as follows

RtlAddVectoredExceptionHandler(TRUE, BlockDllHandler);

And finally put a hardware breakpoint on LdrLoadDll, so that our handler will trigger whenever the function executes, allowing our guard to block enemy DLLs.

NTSTATUS AddBreakpoint(IN CONST BYTE Position)
{
if (Position > 3)
{
return STATUS_INVALID_PARAMETER;
}

CONTEXT Context{ .ContextFlags = CONTEXT_DEBUG_REGISTERS };
NTSTATUS Status { NtGetContextThread(ThreadHandle, &Context) };

if (!NT_SUCCESS(Status))
{
return Status;
}

(&Context.Dr0)[Position] = reinterpret_cast<DWORD64>(LdrLoadDll);

Context.Dr7 &= ~(3ull << (16 + 4 * Position));
Context.Dr7 &= ~(3ull << (18 + 4 * Position));
Context.Dr7 |= 1ull << 2 * Position;

Status = NtSetContextThread(ThreadHandle, &Context);

return Status;
}