从用户模式运行内核模式系统调用

时间:2018-05-22 16:28:16

标签: c assembly kernel ida

我试图使用OpenProcess()读取csrss.exe等某些进程的内存。问题是,只要我使用PROCESS_ALL_ACCESS,就无法打开这些流程。所以我尝试使用PROCESS_QUERY_LIMITED_INFORMATION参数,但并没有获得丰富的结果。

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

要绕过此检查,我执行了以下操作:

  1. 使用IDA打开所有这些进程所在的ntdll.dll
  2. 找到这个功能,这就是我找到的。
  3. Cap_1 所以再次从我的理解,它执行测试,然后如果它评估为0,它执行低延迟系统调用,我相信是函数的内核模式版本。

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

    所以这是我的问题:

    1. 我可以直接创建.asm文件并在其中编写这些过程并调用它们以获取内核模式访问这些函数吗?
    2. 如果我使用上述方法调用它们,PROCESS_ALL_ACCESS是否适用于这些例程。
    3. 我还需要使用我无法找到内核模式替换的VirtualQueryEx(),在这种情况下,我计划使用VirtualQueryEx()以及上面提到的自定义调用。现在我的问题是,因为VirtualQueryEx()不是内核模式调用(至少在顶部,我的意思是ReadProcessMemory()也调用ZwReadVirtualMemory但是如果由{i}启动则不是内核模式调用用户模式程序,VirtualQueryEx()}的情况也是如此,它是一个问题还是在我进行下一次自定义调用时会恢复到用户模式?
    4. 我自己做这一切的最终议程是能够以内核级别权限打开所有进程,读取内存并将其转储到文件中。这还包括在系统级运行的进程,如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文件中。

1 个答案:

答案 0 :(得分:6)

Windows Vista - Windows 10

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

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

借助Windows 8.1,Microsoft重新访问受保护的流程并引入了Protected Process Light (PPL)一类过程。
PPL只能由另一个PPL启动;使用安全启动或使用specific registry key/environment variable关键系统进程是PPL 这意味着即使通过管理员/系统帐户也无法打开它们。

csrss.exe也是自Windows 8.1以来的PPL:

  

请注意,有趣的是Csrss.exe也受到保护级别的祝福。它不负责启动任何特殊的受保护进程,并且在内存中没有任何有趣的数据,如LSASS或系统进程。然而,近年来,它作为多种Windows漏洞的来源获得了非常邪恶的声誉

除了保护级别外,每个进程还有一个签名者 此字段指定进程在系统中的运行方式。 值为:

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

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

  1. 允许其他类型的签名者打开该过程。
  2. 两个访问掩码,用于指定尝试打开进程时不允许的权限。
  3. PsProtectedSignerNone之外的每个签名者都限制了

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

    为了打开csrss.exe必要的运行请求流程作为受保护的流程与适当的 签名者,但这需要Microsoft的二进制to be signed。 或者,as RbMm指出,如果您可以加载内核驱动程序,则可以使用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 无法添加新权限,它只能启用/禁用进程从用户继承的权限。
    据我所知,没有公共API可以为访问令牌添加新权限 如果程序在非管理用户下运行,则该过程将失败,因为no sane admin will grant such a powerful privilege to any non-admin

    此外,即使您的帐户是管理帐户,您也可能以protected administrator身份运行,因此分配给流程的令牌不会有SeDebugPrivilege

    您可以通过以完全管理员身份运行或通过self-elevating the process来解决此问题。

      

    我目前的理解是,如果应用程序在用户模式下访问它,则此调用也将被视为来自用户模式而不是内核模式的调用。

    这种理解是不正确的 在用户空间无法访问的内核中,有一个entry-point将控制权从用户空间转移到内核,但是用户程序无法读取/写入内核内存。 有关系统的每个重要信息(包括句柄)都保存在内核中,远离用户程序的范围。

    当程序使用系统调用转换到内核时,它也会放弃对内核的控制;简单地说,你可以执行一个设计的内核函数,但你不能读/写它的代码或数据 这是一种架构(读取CPU)约束,即使经常发现漏洞绕过它,它也非常强大。 如果您正在寻找这样的漏洞,那么您正在寻找zero-day漏洞 操作系统中的错误。

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

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

      

    再次从我的理解,它执行测试,然后如果它评估为0,它执行低延迟系统调用,我相信是函数的内核模式版本

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

      

    我可以直接创建.asm文件并在其中编写这些过程并调用它们以获取内核模式访问这些函数吗?

    代码直接调用系统调用,此接口是私有的 Microsoft保留自行更改系统调用的权利。号码和/或呼叫惯例 只有公共API在不同版本的Windows中才是稳定的 如果需要,可以通过执行以下操作直接调用系统调用:

    mov r10, rcx
    mov eax, 26h
    syscall
    

    然后按照NtOpenProcess的方式设置寄存器中的参数 你不会到达任何地方,这只是原始功能的内联,非便携版本。

      

    如果我使用上述方法调用它们,PROCESS_ALL_ACCESS是否适用于这些例程。

    不,检查仍由内核执行。

      

    我还需要使用我无法找到内核模式替换的VirtualQueryEx(),在这种情况下,我计划使用VirtualQueryEx()以及上面提到的自定义调用。现在我的问题是,因为VirtualQueryEx()不是内核模式调用(至少在顶部,我的意思是ReadProcessMemory()也调用ZwReadVirtualMemory但是如果由用户模式程序启动则不是内核模式调用, VirtualQueryEx()的情况也是如此,当我进行下一次自定义调用时,它是一个问题还是会恢复到用户模式?

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

      

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

    内核没有权限,用户有权限,因此有进程 您应该能够使用作为管理员运行的程序或作为System运行的服务来转储任何用户模式进程。