前言
在本篇文章中,主要介紹sid相關(guān)的概念,并介紹mimikatz的sid模塊,著重分析sid::patch功能的原理。
SID簡(jiǎn)介
1. 安全標(biāo)識(shí)符(SID)
在windows操作系統(tǒng)中,系統(tǒng)使用安全標(biāo)識(shí)符來(lái)唯一標(biāo)識(shí)系統(tǒng)中執(zhí)行各種動(dòng)作的實(shí)體,每個(gè)用戶有SID,計(jì)算機(jī)、用戶組和服務(wù)同樣也有SID,并且這些SID互不相同,這樣才能保證所標(biāo)識(shí)實(shí)體的唯一性
SID一般由以下組成:
- “S”表示SID,SID始終以S開頭
- “1”表示版本,該值始終為1
- “5”表示W(wǎng)indows安全權(quán)威機(jī)構(gòu)
- “21-1463437245-1224812800-863842198”是子機(jī)構(gòu)值,通常用來(lái)表示并區(qū)分域
- “1128”為相對(duì)標(biāo)識(shí)符(RID),如域管理員組的RID為512

Windows也定義了一些內(nèi)置的本地SID和域SID來(lái)表示一些常見的組或身份
SID |
Name |
S-1-1-0 |
World |
S-1-3-0 |
Creator Owner |
S-1-5-18 |
Local SYSTEM |
S-1-5-11 |
Authenticated Users |
S-1-5-7 |
Anonymous |
2. AD域中的SID
在AD域中,SID同樣用來(lái)唯一標(biāo)識(shí)一個(gè)對(duì)象,在LDAP中對(duì)應(yīng)的屬性名稱為objectSid:

重點(diǎn)需要了解的是LDAP上的sIDHistory屬性
(1) SIDHistory
SIDHistory是一個(gè)為支持域遷移方案而設(shè)置的屬性,當(dāng)一個(gè)對(duì)象從一個(gè)域遷移到另一個(gè)域時(shí),會(huì)在新域創(chuàng)建一個(gè)新的SID作為該對(duì)象的objectSid,在之前域中的SID會(huì)添加到該對(duì)象的sIDHistory屬性中,此時(shí)該對(duì)象將保留在原來(lái)域的SID對(duì)應(yīng)的訪問(wèn)權(quán)限
比如此時(shí)域A有一個(gè)用戶User1,其LDAP上的屬性如下:
cn |
objectSid |
sIDHistory |
User1 |
S-1-5-21-3464518600-3836984554-627238718-2103 |
null |
此時(shí)我們將用戶User1從域A遷移到域B,那么他的LDAP屬性將變?yōu)椋?/p>
cn |
objectSid |
sIDHistory |
User1 |
S-1-5-21-549713754-3312163066-842615589-2235 |
S-1-5-21-3464518600-3836984554-627238718-2103 |
此時(shí)當(dāng)User1訪問(wèn)域A中的資源時(shí),系統(tǒng)會(huì)將目標(biāo)資源的DACL與User1的sIDHistory進(jìn)行匹配,也就是說(shuō)User1仍具有原SID在域A的訪問(wèn)權(quán)限
值得注意的是,該屬性不僅在兩個(gè)域之間起作用,它同樣也可以用于單個(gè)域中,比如實(shí)戰(zhàn)中我們將一個(gè)用戶A的sIDHistory屬性設(shè)置為域管的objectSid,那么該用戶就具有域管的權(quán)限
另一個(gè)實(shí)戰(zhàn)中常用的利用,是在金票中添加Enterprise Admins組的SID作為sIDHistory,從而實(shí)現(xiàn)同一域林下的跨域操作,這個(gè)將在后面關(guān)于金票的文章中闡述
(2) SID Filtering
SID Filtering簡(jiǎn)單地說(shuō)就是跨林訪問(wèn)時(shí)目標(biāo)域返回給你的服務(wù)票據(jù)中,會(huì)過(guò)濾掉非目標(biāo)林中的SID,即使你添加了sIDHistory屬性。SID Filtering在信任中默認(rèn)開啟,在單林中默認(rèn)關(guān)閉
mimikatz的sid模塊
1. sid::lookup
該功能實(shí)現(xiàn)SID與對(duì)象名之間的相互轉(zhuǎn)換,有三個(gè)參數(shù):
- /name:指定對(duì)象名,將其轉(zhuǎn)換為SID
- /sid:指定SID,將其轉(zhuǎn)換為對(duì)象名
- /system:指定查詢的目標(biāo)計(jì)算機(jī)

2. sid::query
該功能支持通過(guò)SID或?qū)ο竺麃?lái)查詢對(duì)象的信息,同樣有三個(gè)參數(shù),使用時(shí)指定/sam或/sid,/system可選
- /sam:指定要查詢對(duì)象的sAmaccountName
- /sid:指定要查詢對(duì)象的objectSid
- /system:指定查詢的目標(biāo)域控(LDAP)

這個(gè)功能其原理就是直接使用LDAP查詢,通過(guò)sAMAccountName查詢對(duì)應(yīng)的objectSid,或者通過(guò)objectSid查詢對(duì)應(yīng)的sAMAccountName
其核心是調(diào)用Windows一系列的LDAP操作API,主要是ldap_search_s():

3. sid::modify
該功能用于修改一個(gè)域?qū)ο蟮腟ID,可以使用的參數(shù)有三個(gè):
- /sam:通過(guò)sAMAccountName指定要修改SID的對(duì)象
- /sid:通過(guò)objectSid指定要修改SID的對(duì)象
- /new:要修改對(duì)象的新SID
使用該功能是需要先使用sid::patch功能對(duì)xxxx進(jìn)行patch(自然也需要先開啟debug特權(quán)),需要在域控上執(zhí)行

修改時(shí)的操作就很簡(jiǎn)單了,調(diào)用LDAP操作的API對(duì)域?qū)ο蟮膐bjectSid進(jìn)行修改,主要使用的是ldap_modify_s():

4. sid::add
該功能用來(lái)向一個(gè)域?qū)ο筇砑觭IDHistoy屬性,有兩個(gè)參數(shù):
- /sam:通過(guò)sAMAccountName指定要修改的對(duì)象
- /sid:通過(guò)objectSid指定要修改的對(duì)象
- /new:要修改sIDHistory為哪個(gè)對(duì)象的SID,該參數(shù)可指定目標(biāo)的sAMAccountName或objectSid,當(dāng)指定名稱時(shí)先調(diào)用LookupAccountSid將其轉(zhuǎn)換為SID
使用該功能也要先執(zhí)行sid::patch,修改時(shí)同樣是操作LDAP通過(guò)ldap_modify_s()修改,不再贅述

5. sid::clear
該功能用來(lái)清空一個(gè)對(duì)象的sIDHistory屬性
- /sam:要清空sIDHistory的對(duì)象的sAMAccountName
- /sid:要清空sIDHistory的對(duì)象的objectSid

原理就是使用ldap_modify_s()將目標(biāo)對(duì)象sIDHistory屬性修改為空
6. sid::patch
對(duì)域控LDAP修改過(guò)程中的驗(yàn)證函數(shù)進(jìn)行patch,需要在域控上執(zhí)行,該功能沒(méi)有參數(shù)
patch共分為兩個(gè)步驟,如果僅第一步patch成功的話,那么可以使用sid::add功能,兩步都patch成功的話才可以使用sid::modify功能

sid::patch分析
sid::patch在系統(tǒng)版本 < Vista時(shí),patch的是samss服務(wù)中ntdsa.dll的內(nèi)存,更高版本patch的是ntds服務(wù)中ntdsai.dll的內(nèi)存

整個(gè)patch過(guò)程分為兩步:
- 第一步patch的是SampModifyLoopbackCheck()的內(nèi)存
- 第二步patch的是ModSetAttsHelperPreProcess()的內(nèi)存

我們以Windows Server 2012 R2環(huán)境為例來(lái)分析,首先我們需要找到NTDS服務(wù)所對(duì)應(yīng)的進(jìn)程,我們打開任務(wù)管理器選中NTDS服務(wù),單擊右鍵,選擇“轉(zhuǎn)到詳細(xì)信息”就會(huì)跳轉(zhuǎn)到對(duì)應(yīng)進(jìn)程,這里NTDS服務(wù)對(duì)應(yīng)的進(jìn)程是lsass.exe

1. 域控對(duì)LDAP請(qǐng)求的處理
大致分析一下域控對(duì)本地LDAP修改請(qǐng)求的過(guò)濾與處理流程,當(dāng)我們修改objectSid和sIDHistory時(shí),SampModifyLoopbackCheck()會(huì)過(guò)濾我們的請(qǐng)求,即使繞過(guò)該函數(shù)修改objectSid時(shí),仍會(huì)受到SysModReservedAtt()的限制
侵入式切換到lsass進(jìn)程并重新加載用戶態(tài)符號(hào)表:

給兩個(gè)檢查函數(shù)打斷點(diǎn)

此時(shí)我們修改一個(gè)用戶的描述來(lái)觸發(fā)LDAP修改請(qǐng)求

命中斷點(diǎn)后的調(diào)用棧如下:

SampModifyLoopbackCheck()函數(shù)中存在大量Check函數(shù),通過(guò)動(dòng)態(tài)調(diào)試發(fā)現(xiàn)修改sIDHistoy的請(qǐng)求經(jīng)過(guò)該函數(shù)后便會(huì)進(jìn)入返回錯(cuò)誤代碼的流程

繼續(xù)調(diào)試到下一個(gè)斷點(diǎn)

在SysModReservedAtt()執(zhí)行結(jié)束后,正常的修改請(qǐng)求不會(huì)在jne處跳轉(zhuǎn),而當(dāng)修改objectSid時(shí)會(huì)在jne處跳轉(zhuǎn),進(jìn)入返回錯(cuò)誤的流程

2. Patch 1/2
當(dāng)我們想要進(jìn)行內(nèi)存patch時(shí),通常會(huì)尋找目標(biāo)內(nèi)存地址附近的一塊內(nèi)存的值作為標(biāo)記,編寫程序時(shí)首先在內(nèi)存中搜索該標(biāo)記并拿到標(biāo)記的首地址,然后再根據(jù)偏移找到要patch的內(nèi)存地址,然后再進(jìn)行相應(yīng)的修改操作
mimikatz正是使用這種方法,其在內(nèi)存中搜索的標(biāo)記在代碼中有明確的體現(xiàn):

我們將域控的ntdsai.dll拿回本地分析,在其中搜索標(biāo)記41 be 01 00 00 00 45 89 34 24 83

這一部分內(nèi)容是在函數(shù)SampModifyLoopbackCheck()函數(shù)的流程中,我們可以使用windbg本地調(diào)試對(duì)比一下patch前后的函數(shù)內(nèi)容
首先我們找到lsass.exe的基址并切換到該進(jìn)程上下文:

使用lm列出模塊,可以看到lsass進(jìn)程中加載了ntdsai.dll,表明此時(shí)我們可以訪問(wèn)ntdsai.dll對(duì)應(yīng)的內(nèi)存了

我們直接查看SampModifyLoopbackCheck()函數(shù)在內(nèi)存中的反匯編

為了對(duì)比patch前后的區(qū)別,我們使用mimikatz執(zhí)行sid::patch,然后再查看函數(shù)的反匯編。如下圖所示,箭頭所指處原本是74也就是je,而patch后直接改為eb即jmp,使流程直接跳轉(zhuǎn)到0x7ffc403b2660

而0x7ffc403b2660處的代碼之后基本沒(méi)有條件檢查的函數(shù)了,恢復(fù)堆棧和寄存器后就直接返回了,這樣就達(dá)到了繞過(guò)檢查邏輯的目的
3. Patch 2/2
同理,按照mimikatz代碼中的標(biāo)記搜索第二次patch的位置0f b7 8c 24 b8 00 00 00

查看ModSetAttsHelperPreProcess()處要patch的內(nèi)存,patch前如下圖所示

patch完成后內(nèi)存如下圖,其實(shí)本質(zhì)是讓SysModReservedAtt()函數(shù)失效,在內(nèi)存中尋找到標(biāo)記后偏移-6個(gè)字節(jié),然后將驗(yàn)證后的跳轉(zhuǎn)邏輯nop掉

4. 解決patch失敗的問(wèn)題
由于mimikatz中內(nèi)存搜索的標(biāo)記覆蓋的windows版本不全,所以經(jīng)常會(huì)出現(xiàn)patch失敗的問(wèn)題。例如在我的Windows Server 2016上,第二步patch就會(huì)失敗,這種情況多半是因?yàn)閙imikatz中沒(méi)有該系統(tǒng)版本對(duì)應(yīng)的內(nèi)存patch標(biāo)記

此時(shí)我們只需要將目標(biāo)的ntdsai.dll拿下來(lái)找到目標(biāo)地址

然后修改為正確的內(nèi)存標(biāo)記和對(duì)應(yīng)的偏移地址即可,如果新增的話記得定義好版本號(hào)等信息

此時(shí)重新編譯后就可以正常patch了

滲透測(cè)試中的應(yīng)用
在滲透測(cè)試中的利用,一個(gè)是使用SIDHistory屬性來(lái)留后門,另一個(gè)是修改域?qū)ο蟮腟ID來(lái)實(shí)現(xiàn)域內(nèi)的“影子賬戶”或者跨域等操作
1. SIDHistoy后門
拿下域控后,我們將普通域用戶test1的sIDHistory屬性設(shè)置為域管的SID:

此時(shí)test1將具有域管權(quán)限,我們可以利用這個(gè)特性來(lái)留后門

2. 域內(nèi)“影子賬戶”
假設(shè)我們此時(shí)拿到了域控,然后設(shè)置一個(gè)普通域用戶的SID為域管的SID

此時(shí)我們這個(gè)用戶仍然只是Domain Users組中的普通域成員

但該用戶此時(shí)已經(jīng)具有了域管的權(quán)限,例如dcsync:
并且此時(shí)也可以用該用戶的賬號(hào)和密碼登錄域控,登錄成功后是administrator的session。但該操作很有可能造成域內(nèi)一些訪問(wèn)沖突(猜測(cè),未考證),建議在生產(chǎn)環(huán)境中慎用
3. 跨域
通常我們拿到一個(gè)域林下的一個(gè)子域,會(huì)通過(guò)黃金票據(jù)+SIDHistory的方式獲取企業(yè)管理員權(quán)限,控制整個(gè)域林
除了這種方法,我們也可以直接修改當(dāng)前子域?qū)ο蟮膕IDHistory屬性,假設(shè)我們現(xiàn)在拿到一個(gè)子域域控,通過(guò)信任關(guān)系發(fā)現(xiàn)存在一個(gè)父域,此時(shí)我們無(wú)法訪問(wèn)父域域控的CIFS

但我們給子域域管的sIDHistory屬性設(shè)置為父域域管的SID

此時(shí)就可以訪問(wèn)父域域控的CIFS了:
