简体   繁体   中英

Running kernel mode syscalls from user mode

I was trying to read the memory of certain processes like csrss.exe using OpenProcess() . The problem is that, as soon as I use PROCESS_ALL_ACCESS these processes can't be opened. So I tried using PROCESS_QUERY_LIMITED_INFORMATION parameter and that doesn't fetch juicy results.

From my understanding of the system, this function ultimately calls ZwOpenProcess() . My current understanding is that, if it is being accessed by an application in the user mode, this call will also be treated as call from the user mode and not the kernel mode.

To bypass this check, I did the following:

  1. Used IDA to open ntdll.dll where all these processes are present.
  2. Find the function, and here is what I found.

第_1章 So again from my understanding, it performs a test, then if it evaluates to 0, it executes the low latency system call, which I believe is the Kernel Mode Version of the function.

Next I did the same for ZwReadVirtualMemory() : 第_2章

So here are my questions:

  1. Can I directly make a .asm file and write these procedures in the same and call them to get kernel mode access to these functions?
  2. Will PROCESS_ALL_ACCESS work for these routines if I call them using the above method.
  3. I also need to use VirtualQueryEx() for which I couldn't find a kernel mode replacement, in that case, I plan to use VirtualQueryEx() along with the above mentioned custom calls. Now my question here is that, since VirtualQueryEx() is not a kernel mode call(not on the top atleast, by that I mean ReadProcessMemory() also calls ZwReadVirtualMemory but isn't a kernel mode call if initiated by user mode program, so is the case with VirtualQueryEx() ), will it be a problem or will it revert back to user mode when I make the next custom call?

The ultimate agenda of myself doing all this is to be able to open all processes with kernel level privileges, read their memory and dump them into a file. This also includes processes that are running at system level like csrss.exe. If any easier approach exists, kindly enlighten me with the same.

Code :

HANDLE hProc = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);
HANDLE lProc = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, pid);
DWORD error = GetLastError();

if (hProc || lProc)
    {


            //(!hProc && lProc) ? printf("lproc") : printf("hProc"); //Testing Condition
            fProc = (!hProc && lProc) ? lProc : hProc;

        while (1) {
            if ((VirtualQueryEx(fProc, addr1, &meminfo, sizeof(meminfo))) == 0)
            {
                break;
            }


            if ((meminfo.State == MEM_COMMIT))
            {
                static unsigned char display_buffer[1024 * 128];
                SIZE_T bytes_left;
                SIZE_T total_read;
                SIZE_T bytes_to_read;
                SIZE_T bytes_read;
                FILE *f;


                    f = fopen("Dump.txt", "a");


                addr = (unsigned char*)meminfo.BaseAddress;

                //printf("Process Name : %ws\r\n", pName.Buffer);
                fprintf(f, "Process Name : %ws\r\n", pName.Buffer);

                //printf("Base Address : 0x%08x\r\n", addr);
                fprintf(f, "Base Address : 0x%08x\r\n", addr);

                bytes_left = meminfo.RegionSize;

                //printf("Region Size : %d\r\n", bytes_left);
                fprintf(f, "Region Size : %d\r\n", bytes_left);

                total_read = 0;

                //printf("Contents : \r\n");
                fprintf(f, "Contents : \r\n");

                while (bytes_left)
                {
                    bytes_to_read = (bytes_left > sizeof(display_buffer)) ? sizeof(display_buffer) : bytes_left;
                    ReadProcessMemory(hProc, addr + total_read, display_buffer, bytes_to_read, &bytes_read);
                    if (bytes_read != bytes_to_read) break;

                    int j = 0;


                    for (j = 0; j < bytes_to_read; j++)
                    {
                        if ((display_buffer[j] > 31) && (display_buffer[j] < 127)) {
                            //printf("%c ", display_buffer[j]);
                            fprintf(f, "%c", display_buffer[j]);
                        }
                        else {
                            //printf(".");
                            fprintf(f, ".");
                        }
                    }
                    //printf("\r\n");
                    fprintf(f, "\r\n");

                    bytes_left -= bytes_read;
                    total_read += bytes_read;
                }

                fclose(f);

            }

            addr1 = (unsigned char*)meminfo.BaseAddress + meminfo.RegionSize;

        }

    }

        else {

        printf("\nFailed to open process - error - %d\r\n", error);

    }

So, here I'm trying to save all the memory information to a DUMP.txt file for a given process.

Windows Vista - Windows 10

Thanks to RbMm for pointing out that since Windows 10, things changed. Actually, they started changing since Windows Vista.

Windows Vista introduced the concept of protected process to enforce the application of the DRM to media content.
A protected process cannot be read or modified by a normal process (besides other restrictions).
Protected processes can only be loaded if signed and only if they perform only a restricted set of operations.

With Windows 8.1 Microsoft revisited the protected processes and introduced the Protected Process Light (PPL) class of process.
A PPL can only be started by another PPL; with secure-boot or with a specific registry key/environment variable the critical system processes are PPL.
This means they cannot be opened, even by admin/system account.

csrss.exe is also a PPL since Windows 8.1:

Note that it's interesting that Csrss.exe was blessed with a protection level as well. It isn't responsible for launching any special protected processes and doesn't have any interesting data in memory like LSASS or the System process do. It has, however, gained a very nefarious reputation in recent years as being the source of multiple Windows exploits

Each process, besides its protection level, also has a signer .
This fields specifies how the process is truested in the system. The values are:

PsProtectedSignerNone = 0n0
PsProtectedSignerAuthenticode = 0n1
PsProtectedSignerCodeGen = 0n2
PsProtectedSignerAntimalware = 0n3
PsProtectedSignerLsa = 0n4
PsProtectedSignerWindows = 0n5
PsProtectedSignerWinTcb = 0n6
PsProtectedSignerMax/PsProtectedSignerWinSystem = 0n7

Finally, each signer type has a structure determining:

  1. With other types of signers are allowed to open the process.
  2. Two access mask to specify which privileges are not allowed when trying to open the process.

Each signer but the PsProtectedSignerNone limits the privileges to

PROCESS_QUERY_LIMITED_INFORMATION
PROCESS_SUSPEND_RESUME
PROCESS_TERMINATE (excluding WinTcb, Antimalware and Lsa)
PROCESS_SET_LIMITED_INFORMATION

In order to open csrss.exe its necessary run the requestion process as a protected process with the appropriate signer but this require the binary to be signed by Microsoft. Alternatively, as RbMm pointed out, if you can load a kernel driver, it's possible to temper with the EPROCESS structure, altering the protection level bits.


Windows 7

This very simple program opens a process started by SYSTEM.

#include <windows.h>

int CALLBACK WinMain(
  _In_ HINSTANCE hInstance,
  _In_ HINSTANCE hPrevInstance,
  _In_ LPSTR     lpCmdLine,
  _In_ int       nCmdShow
)
{
    HANDLE thisProcess = GetCurrentProcess();
    HANDLE thisToken;
    TOKEN_PRIVILEGES newPrivileges;
    HANDLE thatProcess;

    newPrivileges.PrivilegeCount = 1;
    newPrivileges.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;

    if (LookupPrivilegeValue(NULL, "SeDebugPrivilege", &newPrivileges.Privileges[0].Luid) == 0)
    {
        MessageBox(NULL, "LookupPrivilegeValue", "Cannot find privilege", MB_ICONEXCLAMATION);
        ExitProcess(1);     
    }

    if (OpenProcessToken(thisProcess, TOKEN_ADJUST_PRIVILEGES, &thisToken) == 0)
    {
        MessageBox(NULL, "OpenProcessToken", "Cannot open token", MB_ICONEXCLAMATION);
        ExitProcess(2);
    }

    AdjustTokenPrivileges(thisToken, FALSE, &newPrivileges, 0, NULL, NULL);
    if (GetLastError() != ERROR_SUCCESS)
    {
        MessageBox(NULL, "AdjustTokenPrivileges", "Cannot adjust privileges", MB_ICONEXCLAMATION);
        ExitProcess(3);
    }

    thatProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, 1080);

    if (thatProcess == NULL)
    {
        MessageBox(NULL, "OpenProcess", "Cannot open that process", MB_ICONEXCLAMATION);
        ExitProcess(4);
    }

    MessageBox(NULL, "Done", "Opened", MB_ICONINFORMATION);

}

When run under an account which has the SeDebugPrivilege , such as an administrator account, it succeeds in getting the process handle.
When not run under the condition of above it fails at the AdjustTokenPrivileges step.
I tested it on my system, running it as both a full-fledged admin and as a normal user. Only the admin could open it.

It works as I explained in my previous comment: it enables the SeDebugPrivilege in its access token and then opens an arbitrary system process handle.

Note The process id is hardcoded, change it.

One important thing to understand is that AdjustTokenPrivileges cannot add new privileges, it can only enable/disable the privileges the process inherits from the user.
As far as I know, there is no public API to add a new privilege to an access token.
If the program is run under a non-administrative user the process will fail because no sane admin will grant such a powerful privilege to any non-admin .

Also, even if your account is an administrative one, you are probably running as a protected administrator and thus the token assigned to a process won't have SeDebugPrivilege .

You can fix this by either running as full administrator or by self-elevating the process .


My current understanding is that, if it is being accessed by an application in the user mode, this call will also be treated as call from the user mode and not the kernel mode.

This understanding is incorrect.
The kernel in inaccessible from userspace, there is an entry-point that transfers control from the user space to the kernel but a user program cannot read/write the kernel memory.
Every important information about the system, including handles, are kept in the kernel, away from the user programs reach.

When a program uses a system call to transition into the kernel it also relinquishes control to the kernel; simply put you can execute a designed kernel function but you cannot read/write its code or data.
This is an architectural (read CPU) constraint, it's very robust even though loopholes are routinely found to bypass it.
If you are looking for such a loophole, you are looking for a zero-day vulnerability.
A bug in the OS.

The access rights check is, obviously, performed by the kernel code, so you cannot see, touch or circumvent it unless you make a program part of the kernel.
This is a driver , you can develop one with the WDK (as opposed to the SDK).
Loading drivers still require administrative privileges but an admin, or a privileged setup, can make the driver load at startup (they are kind of services, they don't need a user).
Actually a service should suffice in this case.

The check you are seeing in the disassembly is just a compatibility path, Windows uses the syscall instruction as opposed to the legacy int 2eh .
I thought the legacy mechanism was gone.
Both paths will eventually merge in the kernel.

So again from my understanding, it performs a test, then if it evaluates to 0, it executes the low latency system call, which I believe is the Kernel Mode Version of the function

This is incorrect as explained above.
You can sense that it cannot possibly be true because there is no code in both paths (where is the non-kernel mode version of the function?).


Can I directly make a .asm file and write these procedures in the same and call them to get kernel mode access to these functions?

The code invokes the system call directly, this interface is private.
Microsoft reserves to itself the right to change the system calls' numbers and/or the calling convention.
Only the public API is stable across different version of Windows.
If you want, you can invoke the system call directly by doing:

mov r10, rcx
mov eax, 26h
syscall

and then setting the parameter in the registers the way you would for NtOpenProcess .
You wouldn't get anywhere though, this is just an inlined, non-portable, version of the original function.

Will PROCESS_ALL_ACCESS work for these routines if I call them using the above method.

No, the check is still performed by the kernel.

I also need to use VirtualQueryEx() for which I couldn't find a kernel mode replacement, in that case, I plan to use VirtualQueryEx() along with the above mentioned custom calls. Now my question here is that, since VirtualQueryEx() is not a kernel mode call(not on the top atleast, by that I mean ReadProcessMemory() also calls ZwReadVirtualMemory but isn't a kernel mode call if initiated by user mode program, so is the case with VirtualQueryEx()), will it be a problem or will it revert back to user mode when I make the next custom call?

I cannot fully understand this question, I'm sorry for that.
All I can say is that: a) The WinAPIs and the private API are two different sets of APIs b) The WinAPIs don't use the private APIs in a 1:1 fashion, they may involve more than one system call (Fun fact: It seems that Windows uses system calls even for code already at ring 0) c) All privileged actions are executed by the kernel, this is not at the choice of the invoking program.

The ultimate agenda of myself doing all this is to be able to open all processes with kernel level privileges, read their memory and dump them into a file. This also includes processes that are running at system level like csrss.exe. If any easier approach exists, kindly enlighten me with the same.

The kernel has no privilege, users have privileges and thus so have processes.
You should be able to dump any user-mode process with a program running either as an admin or a service running as System.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM