Implement new way of return address hijacking (#55946)
authorJan Vorlicek <jan.vorlicek@volny.cz>
Mon, 26 Jul 2021 14:39:22 +0000 (16:39 +0200)
committerGitHub <noreply@github.com>
Mon, 26 Jul 2021 14:39:22 +0000 (16:39 +0200)
* Implement new way of return address hijacking

This change implements a new way of return address hijacking for Intel
CET enabled Windows devices. It uses a mechanism created by the Windows
team for this purpose. The existing mechanism that just patches the
return address by an address of our helper routine would result in
process killing as it would seem like a ROP exploit.

src/coreclr/vm/amd64/AsmHelpers.asm
src/coreclr/vm/amd64/cgenamd64.cpp
src/coreclr/vm/amd64/cgencpu.h
src/coreclr/vm/excep.cpp
src/coreclr/vm/threadsuspend.cpp
src/coreclr/vm/threadsuspend.h

index 172b800..0fd77a2 100644 (file)
@@ -432,13 +432,15 @@ endif ; _DEBUG
 NESTED_ENTRY OnHijackTripThread, _TEXT
 
         ; Don't fiddle with this unless you change HijackFrame::UpdateRegDisplay
-        ; and HijackObjectArgs
+        ; and HijackArgs
+        mov                 rdx, rsp
         push                rax ; make room for the real return address (Rip)
+        push                rdx
         PUSH_CALLEE_SAVED_REGISTERS
         push_vol_reg        rax
         mov                 rcx, rsp
 
-        alloc_stack         30h ; make extra room for xmm0
+        alloc_stack         38h ; make extra room for xmm0, argument home slots and align the SP
         save_xmm128_postrsp xmm0, 20h
 
 
@@ -448,9 +450,10 @@ NESTED_ENTRY OnHijackTripThread, _TEXT
 
         movdqa              xmm0, [rsp + 20h]
 
-        add                 rsp, 30h
+        add                 rsp, 38h
         pop                 rax
         POP_CALLEE_SAVED_REGISTERS
+        pop                 rdx
         ret                 ; return to the correct place, adjusted by our caller
 NESTED_END OnHijackTripThread, _TEXT
 
index d00f7b7..af91cc3 100644 (file)
@@ -286,7 +286,11 @@ void HijackFrame::UpdateRegDisplay(const PREGDISPLAY pRD)
     pRD->IsCallerSPValid      = FALSE;        // Don't add usage of this field.  This is only temporary.
 
     pRD->pCurrentContext->Rip = m_ReturnAddress;
+#ifdef TARGET_WINDOWS
+    pRD->pCurrentContext->Rsp = m_Args->Rsp;
+#else
     pRD->pCurrentContext->Rsp = PTR_TO_MEMBER_TADDR(HijackArgs, m_Args, Rip) + sizeof(void *);
+#endif
 
     UpdateRegDisplayFromCalleeSavedRegisters(pRD, &(m_Args->Regs));
 
index 6300876..93797a4 100644 (file)
@@ -465,7 +465,7 @@ struct HijackArgs
         ULONG64 Rax;
         ULONG64 ReturnValue[1];
     };
-#else // FEATURE_MULTIREG_RETURN
+#else // !FEATURE_MULTIREG_RETURN
     union
     {
         struct
@@ -475,8 +475,11 @@ struct HijackArgs
         };
         ULONG64 ReturnValue[2];
     };
-#endif // TARGET_UNIX
+#endif // !FEATURE_MULTIREG_RETURN
     CalleeSavedRegisters Regs;
+#ifdef TARGET_WINDOWS
+    ULONG64 Rsp;
+#endif
     union
     {
         ULONG64 Rip;
index 7ee6baa..74a1f9f 100644 (file)
@@ -7186,6 +7186,52 @@ LONG WINAPI CLRVectoredExceptionHandler(PEXCEPTION_POINTERS pExceptionInfo)
         }
 
     }
+#ifdef TARGET_AMD64
+
+#ifndef STATUS_RETURN_ADDRESS_HIJACK_ATTEMPT
+#define STATUS_RETURN_ADDRESS_HIJACK_ATTEMPT ((DWORD)0x80000033L)
+#endif
+
+    if (pExceptionInfo->ExceptionRecord->ExceptionCode == STATUS_RETURN_ADDRESS_HIJACK_ATTEMPT)
+    {
+        HijackArgs hijackArgs;
+        hijackArgs.Rax = pExceptionInfo->ContextRecord->Rax;
+        hijackArgs.Rsp = pExceptionInfo->ContextRecord->Rsp;
+
+        bool areCetShadowStacksEnabled = Thread::AreCetShadowStacksEnabled();
+        if (areCetShadowStacksEnabled)
+        {
+            // When the CET is enabled, the return address is still on stack, so we need to set the Rsp as
+            // if it was popped.
+            hijackArgs.Rsp += 8;
+        }
+        hijackArgs.Rip = 0 ; // The OnHijackWorker sets this
+        #define CALLEE_SAVED_REGISTER(regname) hijackArgs.Regs.regname = pExceptionInfo->ContextRecord->regname;
+        ENUM_CALLEE_SAVED_REGISTERS();
+        #undef CALLEE_SAVED_REGISTER
+
+        OnHijackWorker(&hijackArgs);
+
+        #define CALLEE_SAVED_REGISTER(regname) pExceptionInfo->ContextRecord->regname = hijackArgs.Regs.regname;
+        ENUM_CALLEE_SAVED_REGISTERS();
+        #undef CALLEE_SAVED_REGISTER
+        pExceptionInfo->ContextRecord->Rax = hijackArgs.Rax;
+
+        if (areCetShadowStacksEnabled)
+        {
+            // The context refers to the return instruction
+            // Set the return address on the stack to the original one
+            *(size_t *)pExceptionInfo->ContextRecord->Rsp = hijackArgs.ReturnAddress;
+        }
+        else
+        {
+            // The context refers to the location after the return was processed
+            pExceptionInfo->ContextRecord->Rip = hijackArgs.ReturnAddress;
+        }
+
+        return EXCEPTION_CONTINUE_EXECUTION;
+    }
+#endif
 
     // We need to unhijack the thread here if it is not unhijacked already.  On x86 systems,
     // we do this in Thread::StackWalkFramesEx, but on amd64 systems we have the OS walk the
index 02e4747..8359706 100644 (file)
@@ -28,6 +28,10 @@ CLREvent* ThreadSuspend::g_pGCSuspendEvent = NULL;
 
 ThreadSuspend::SUSPEND_REASON ThreadSuspend::m_suspendReason;
 
+#if defined(TARGET_WINDOWS) && defined(TARGET_AMD64)
+void* ThreadSuspend::g_returnAddressHijackTarget = NULL;
+#endif
+
 // If you add any thread redirection function, make sure the debugger can 1) recognize the redirection
 // function, and 2) retrieve the original CONTEXT.  See code:Debugger.InitializeHijackFunctionAddress and
 // code:DacDbiInterfaceImpl.RetrieveHijackedContext.
@@ -4678,7 +4682,17 @@ void Thread::HijackThread(ReturnKind returnKind, ExecutionState *esb)
     CONTRACTL_END;
 
     _ASSERTE(IsValidReturnKind(returnKind));
+
     VOID *pvHijackAddr = reinterpret_cast<VOID *>(OnHijackTripThread);
+
+#if defined(TARGET_WINDOWS) && defined(TARGET_AMD64)
+    void* returnAddressHijackTarget = ThreadSuspend::GetReturnAddressHijackTarget();
+    if (returnAddressHijackTarget != NULL)
+    {
+        pvHijackAddr = returnAddressHijackTarget;
+    }
+#endif // TARGET_WINDOWS && TARGET_AMD64
+    
 #ifdef TARGET_X86
     if (returnKind == RT_Float)
     {
@@ -5975,9 +5989,26 @@ bool Thread::InjectActivation(ActivationReason reason)
 // Initialize thread suspension support
 void ThreadSuspend::Initialize()
 {
-#if defined(FEATURE_HIJACK) && defined(TARGET_UNIX)
+#ifdef FEATURE_HIJACK
+#if defined(TARGET_UNIX)
     ::PAL_SetActivationFunction(HandleSuspensionForInterruptedThread, CheckActivationSafePoint);
-#endif
+#elif defined(TARGET_WINDOWS) && defined(TARGET_AMD64)
+    // Only versions of Windows that have the special user mode APC have a correct implementation of the return address hijack handling
+    if (Thread::UseSpecialUserModeApc())
+    {
+        HMODULE hModNtdll = WszLoadLibrary(W("ntdll.dll"));
+        if (hModNtdll != NULL)
+        {
+            typedef ULONG_PTR (NTAPI *PFN_RtlGetReturnAddressHijackTarget)();
+            PFN_RtlGetReturnAddressHijackTarget pfnRtlGetReturnAddressHijackTarget = (PFN_RtlGetReturnAddressHijackTarget)GetProcAddress(hModNtdll, "RtlGetReturnAddressHijackTarget");
+            if (pfnRtlGetReturnAddressHijackTarget != NULL)
+            {
+                g_returnAddressHijackTarget = (void*)pfnRtlGetReturnAddressHijackTarget();
+            }
+        }
+    }
+#endif // TARGET_WINDOWS && TARGET_AMD64
+#endif // FEATURE_HIJACK
 }
 
 #ifdef _DEBUG
index f36b39f..9274327 100644 (file)
@@ -194,6 +194,10 @@ public:
 private:
     static CLREvent * g_pGCSuspendEvent;
 
+#if defined(TARGET_WINDOWS) && defined(TARGET_AMD64)
+    static void* g_returnAddressHijackTarget;
+#endif // TARGET_WINDOWS && TARGET_AMD64
+
     // This is true iff we're currently in the process of suspending threads.  Once the
     // threads have been suspended, this is false.  This is set via an instance of
     // SuspendRuntimeInProgressHolder placed in SuspendRuntime, SysStartSuspendForDebug,
@@ -247,6 +251,13 @@ public:
         return g_pSuspensionThread;
     }
 
+#if defined(TARGET_WINDOWS) && defined(TARGET_AMD64)
+    static void* GetReturnAddressHijackTarget()
+    {
+        return g_returnAddressHijackTarget;
+    }
+#endif // TARGET_WINDOWS && TARGET_AMD64
+
 private:
     static LONG m_DebugWillSyncCount;
 };