0

I'm trying to use an undocumented function called PsGetContextThread to retrieve the context of a usermode thread from a driver, I know this is possible from usermode but I have my reasons to do this from the kernel and this is not negotiable so please do not sidetrack into that. Now back on topic, the code below when debugged contains a valid thread and everything looks good to me, but it returns invalid with error code C0000005 which is ACCESS_VIOLATION but I do not know how this code could trigger that and would love some help to figure this out as I have been stuck for quite a while on this.

NTSTATUS GetThreadContext(PETHREAD thread) {
KPROCESSOR_MODE mode = UserMode;
CONTEXT context;
UNICODE_STRING setContextString, getContextString;
pPsGetContextThread PsGetContextThread;
NTSTATUS status = STATUS_SUCCESS;

RtlInitUnicodeString(&getContextString, L"PsGetContextThread");
RtlZeroMemory(&context, sizeof(CONTEXT));

PsGetContextThread = (pPsGetContextThread)MmGetSystemRoutineAddress(&getContextString);

context.ContextFlags = CONTEXT_FULL;
status = PsGetContextThread(thread, &context, mode);

if (!NT_SUCCESS(status)) {
    return STATUS_UNSUCCESSFUL;
}

return STATUS_SUCCESS;
}

If anyone knows what to try next or got any suggestions please do post below.

Bo Persson
  • 90,663
  • 31
  • 146
  • 203
Paze
  • 49
  • 7
  • Just to understand what you do: you use an **undocumented** function, which is meant to be called from **user** mode, from **kernel** mode. And you wonder why it does not work? Notice something? – too honest for this site Jan 27 '17 at 19:41
  • 1
    @Olaf you are wrong, PsGetContextThread is not supposed to be called from usermode... its part of the kernel exports and can be called by a driver. the pointer to the function is valid, its the call itself that returns ACCESS_VIOLATION in the status variable – Paze Jan 27 '17 at 22:12
  • I suspect the problem is that you're passing a kernel-mode address `&context` but specifying `UserMode`. When `UserMode` is specified, the first thing `PsGetContextThread` does is to check that `&context` is a valid user-mode pointer. Try specifying `KernelMode` instead. – Harry Johnston Jan 27 '17 at 23:43
  • @HarryJohnston - yes, you absolute right, that problem in kernel mode address of `&context` with `UserMode` specified. but specifying `KernelMode` instead give for as another context by sense. if we need exactly `UserMode` - need *allocate* user mode address first and use it – RbMm Jan 28 '17 at 10:13

2 Answers2

1

yes, @HarryJohnston right that when we specifying UserMode PsGetContextThread check that &context is a valid user-mode pointer. so we need pass valid user mode pointer for this. we can get it by call ZwAllocateVirtualMemory - use this code - this is works

NTSTATUS GetThreadContext(PETHREAD thread, PCONTEXT ctx) 
{
#if 0
    typedef NTSTATUS (NTAPI* GETSETCONTEXTTHREAD)(PETHREAD, PCONTEXT,MODE);
    static GETSETCONTEXTTHREAD PsGetContextThread;
    static BOOLEAN bInit;

    if (!bInit)
    {
        STATIC_UNICODE_STRING(aPsGetContextThread, "PsGetContextThread");
        PsGetContextThread = (GETSETCONTEXTTHREAD)MmGetSystemRoutineAddress(&aPsGetContextThread);
        bInit = TRUE;
    }

    if (!PsGetContextThread)
    {
        return STATUS_PROCEDURE_NOT_FOUND;
    }
#endif

    CONTEXT * BaseAddress = 0;
    SIZE_T Size = sizeof(CONTEXT);
    NTSTATUS status = ZwAllocateVirtualMemory(NtCurrentProcess(), (void**)&BaseAddress, 0, &Size, MEM_COMMIT, PAGE_READWRITE);
    if (0 <= status)
    {
        BaseAddress->ContextFlags = ctx->ContextFlags;
        if (0 <= (status = PsGetContextThread(thread, BaseAddress, UserMode)))
        {
            memcpy(ctx, BaseAddress, sizeof(CONTEXT));
        }
        ZwFreeVirtualMemory(NtCurrentProcess(), (void**)&BaseAddress, &Size, MEM_RELEASE);
    }

    return status;
}

and think you not need use MmGetSystemRoutineAddress but static import PsGetContextThread, but if you anyway want get this pointer in runtime - not need do this every time - but only once. make pointer to function static

RbMm
  • 31,280
  • 3
  • 35
  • 56
0

You confused the third parameter - it does not state whether you fetch User mode thread context or Kernel mode thread context, it only implies whether the original call was made from User or Kernel mode. As such you don't need to call the function with user mode and copy data from user to kernel. Simply call it with KernelMode and use kernel memory.

NTSTATUS GetThreadContext(PETHREAD thread, PCONTEXT ctx) 
{
#if 0
    typedef NTSTATUS (NTAPI* GETSETCONTEXTTHREAD)(PETHREAD, PCONTEXT,MODE);
    static GETSETCONTEXTTHREAD PsGetContextThread = NULL;

    if (NULL == PsGetContextThread )
    {
        STATIC_UNICODE_STRING(aPsGetContextThread, "PsGetContextThread");
        PsGetContextThread = (GETSETCONTEXTTHREAD)MmGetSystemRoutineAddress(&aPsGetContextThread);
    }

    if (NULL == PsGetContextThread)
    {
        return STATUS_PROCEDURE_NOT_FOUND;
    }
#endif

    return PsGetContextThread(thread, ctx, KernelMode);
}
Omer Yair
  • 56
  • 3