运行 来自用户模式的内核模式系统调用

Running kernel mode syscalls from user mode

我正在尝试使用 OpenProcess() 读取某些进程的内存,例如 csrss.exe。问题是,一旦我使用 PROCESS_ALL_ACCESS 这些进程就无法打开。所以我尝试使用 PROCESS_QUERY_LIMITED_INFORMATION 参数,但没有得到很好的结果。

根据我对系统的理解,这个函数最终调用的是ZwOpenProcess()。我目前的理解是,如果它被用户模式下的应用程序访问,则此调用也将被视为来自用户模式而不是内核模式的调用。

为了绕过这个检查,我做了以下操作:

  1. 使用 IDA 打开 ntdll.dll 所有这些进程都存在的地方。
  2. 找到函数,这是我找到的。

因此,根据我的理解,它再次执行测试,然后如果它的计算结果为 0,它会执行低延迟系统调用,我认为这是该函数的内核模式版本。

接下来我对 ZwReadVirtualMemory() 做了同样的事情:

所以这是我的问题:

  1. 我可以直接制作一个 .asm 文件并在其中编写这些程序并调用它们以获得对这些函数的内核模式访问吗?
  2. 如果我使用上述方法调用这些例程,它们会 PROCESS_ALL_ACCESS 工作吗?
  3. 我还需要使用 VirtualQueryEx(),但找不到内核模式替代品,在这种情况下,我计划将 VirtualQueryEx() 与上述自定义调用一起使用。现在我的问题是,因为 VirtualQueryEx() 不是内核模式调用(至少不在顶部,我的意思是 ReadProcessMemory() 也调用 ZwReadVirtualMemory 但如果由用户启动则不是内核模式调用mode 程序,VirtualQueryEx() 也是如此),这会是一个问题还是会在我进行下一次自定义调用时恢复到用户模式?

我自己做这一切的最终目的是能够以内核级权限打开所有进程,读取它们的内存并将它们转储到一个文件中。这还包括在系统级别 运行 的进程,例如 csrss.exe。如果有任何更简单的方法存在,请用相同的方法启发我。

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);

    }

所以,我在这里尝试将所有内存信息保存到给定进程的 DUMP.txt 文件中。

Windows Vista - Windows 10

感谢 指出自 Windows 10 以来,情况发生了变化。 实际上,他们从 Windows Vista 开始发生变化。

Windows Vista 引入了 protected process 的概念 强制将 DRM 应用于媒体内容。
受保护的进程不能被正常进程读取或修改(除了其他限制)。
受保护的进程只有在签名后才能加载,并且仅当它们仅执行一组受限制的 操作。

With Windows 8.1 微软重新审视了受保护的进程并引入了 Protected Process Light (PPL) class 进程。
一个 PPL 只能由另一个 PPL 启动;使用安全启动或 specific registry key/environment variable 关键系统进程是 PPL。
这意味着它们无法打开,即使是 admin/system 帐户。

csrss.exe 也是 PPL,因为 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

每个进程除了其保护级别外,还有一个签名者
此字段指定流程在系统中的真实性。 值为:

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

最后,每个签名者类型都有一个结构确定:

  1. 允许与其他类型的签名者一起开启进程。
  2. 两个访问掩码,用于指定在尝试打开进程时不允许哪些权限。

PsProtectedSignerNone 之外的每个签名者都将权限限制为

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

为了打开 csrss.exe 其必要的 运行 请求进程作为受保护的进程具有适当的 signer 但这需要 Microsoft 的二进制文件 to be signed。 或者, 指出,如果您可以加载内核驱动程序,则可以调整 EPROCESS 结构,更改保护级别位。


Windows 7

这个非常简单的程序打开一个由 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);
    
}

当运行在具有SeDebugPrivilege的账户下,如管理员账户下,获取进程句柄成功
如果不是 运行 在上面的条件下,它会在 AdjustTokenPrivileges 步骤失败。
我在我的系统上对其进行了测试,运行将其设置为成熟的管理员和普通用户。只有管​​理员可以打开它。

它的工作原理与我在之前的评论中解释的一样:它在其访问令牌中启用 SeDebugPrivilege,然后打开任意系统进程句柄。

注意进程id是硬编码的,改一下。

需要了解的一件重要事情是 AdjustTokenPrivileges 无法添加 新权限,它只能 enable/disable 进程从用户继承的权限。
据我所知,没有public API 向访问令牌添加新权限。
如果程序在非管理用户下 运行,则该过程将失败,因为 no sane admin will grant such a powerful privilege to any non-admin.

此外,即使您的帐户是管理帐户,您也可能 运行宁作为 protected administrator,因此分配给进程的令牌不会有 SeDebugPrivilege

您可以通过 运行 以完全管理员身份或 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.

这种理解是不正确的。
用户space无法访问内核,有一个entry-point将控制权从用户space转移到内核,但用户程序不能read/write内核内存。
关于系统的所有重要信息,包括句柄,都保存在内核中,远离用户程序。

当一个程序使用系统调用转换到内核时,它也放弃了对内核的控制;简单地说,您可以执行设计的内核函数,但不能 read/write 它的代码或数据。
这是一个架构(阅读CPU)约束,它非常健壮,即使经常发现漏洞可以绕过它。
如果您正在寻找这样的漏洞,那么您就是在寻找 zero-day 漏洞。
OS.

中的错误

显然,访问权限检查是由内核代码执行的,因此您无法看到、触摸或绕过它,除非您将程序作为内核的一部分。
这是一个 driver, you can develop one with the WDK(相对于 SDK)。
加载驱动程序仍然需要管理权限,但管理员或特权设置可以使驱动程序在启动时加载(它们是一种服务,不需要用户)。
实际上在这种情况下服务就足够了。

您在反汇编中看到的检查 只是一个兼容性路径,Windows 使用 syscall 指令而不是遗留 int 2eh.
我以为遗留机制已经消失了。
两条路径最终都会合并到内核中。

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

如上所述,这是不正确的。
你可以感觉到它不可能是真的,因为两个路径中都没有代码(函数的非内核模式版本在哪里?)。


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?

代码直接调用系统调用,此接口是私有的
Microsoft 保留更改系统调用编号 and/or 调用约定的权利。
只有 public API 在 Windows.
的不同版本中稳定 如果需要,可以通过以下方式直接调用系统调用:

mov r10, rcx
mov eax, 26h
syscall

然后按照 NtOpenProcess.
的方式在寄存器中设置参数 不过你什么也得不到,这只是原始函数的一个内联的、不可移植的版本。

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

不,检查仍然由内核执行。

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?

我不能完全理解这个问题,很抱歉。
我只能说:a) WinAPIs 和 private API 是两组不同的 APIs b) WinAPIs 不使用以 1:1 方式私有 APIs,它们可能涉及多个系统调用(有趣的事实:似乎 Windows 甚至对已经在 ring 0 的代码使用系统调用)c) 全部特权操作由内核执行,这不是调用程序的选择。

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.

内核没有特权,用户有特权,进程也有。
您应该能够使用程序 运行ning 作为管理员或服务 运行ning 作为系统转储任何用户模式进程。