CVE-2021-31956提權(quán)漏洞分析與利用

聲明

以下內(nèi)容,來自先知社區(qū)的任意門作者原創(chuàng)吟宦,由于傳播公黑,利用此文所提供的信息而造成的任何直接或間接的后果和損失,均由使用者本人負(fù)責(zé)绕德,長白山攻防實(shí)驗(yàn)室以及文章作者不承擔(dān)任何責(zé)任患膛。

漏洞介紹

CVE-2021-31956是發(fā)生在NTFS.sys中一個(gè)提權(quán)漏洞,漏洞的成因是因?yàn)檎我绯鰧?dǎo)致繞過條件判斷導(dǎo)致的耻蛇。最后利用起來完成Windows提權(quán)

前置知識(shí)

在此之前可以大致了解一下關(guān)于NTFSNTFS是一個(gè)文件系統(tǒng)具備3個(gè)功能 錯(cuò)誤預(yù)警功能踪蹬,磁盤自我修復(fù)功能和日志功能 NTFS是一個(gè)日志文件系統(tǒng),這意味著除了向磁盤中寫入信息臣咖,該文件系統(tǒng)還會(huì)為所發(fā)生的所有改變保留一份日志 當(dāng)用戶將硬盤的一個(gè)分區(qū)格式化為NTFS分區(qū)時(shí)跃捣,就建立了一個(gè)NTFS文件系統(tǒng)。

漏洞點(diǎn)分析首先這個(gè)函數(shù)可以通過ntoskrnl 系統(tǒng)調(diào)用來訪問夺蛇,此外還可以控制輸出緩沖區(qū)的大小疚漆,如果擴(kuò)展屬性的大小沒有對(duì)齊,此函數(shù)將計(jì)算下一個(gè)填充刁赦,下一個(gè)擴(kuò)展屬性將存儲(chǔ)為32位對(duì)齊娶聘。(每個(gè)Ea塊都應(yīng)該被填充為32位對(duì)齊) 關(guān)于對(duì)齊的介紹于計(jì)

(padding?=?((ea_block_size??+?3)?&?0xFFFFFFFC)?-?e_block_size?

后邊用到的結(jié)構(gòu)體typedef struct _FILE_FULL_EA_INFORMATION {ULONG  NextEntryOffset;//下一個(gè)同類型結(jié)構(gòu)的偏移,若是左后一個(gè)為0UCHAR  Flags;UCHAR  EaNameLength;//eaname數(shù)組的長度USHORT EaValueLength;//數(shù)組中每個(gè)ea值的長度CHAR   EaName[1];}?FILE_FULL_EA_INFORMATION,?*PFILE_FULL_EA_INFORMATION;typedef struct _FILE_GET_EA_INFORMATION {    ULONG NextEntryOffset;    UCHAR EaNameLength;    CHAR  EaName[1];} FILE_GET_EA_INFORMATION, * PFILE_GET_EA_INFORMATION;

進(jìn)行函數(shù)的部分恢復(fù)甚脉,這樣后續(xù)確認(rèn)漏洞點(diǎn)的話就會(huì)比較明顯

_QWORD *__fastcall NtfsQueryEaUserEaList(        _QWORD *a1,        FILE_FULL_EA_INFORMATION *eas_blocks_for_file,        __int64 a3,        __int64 User_Buffer,        unsigned int User_Buffer_Length,        FILE_GET_EA_INFORMATION *UserEaList,        char a7){  int v8; // edi  unsigned int v9; // ebx  unsigned int padding; // r15d  FILE_GET_EA_INFORMATION *GetEaInfo; // r12  ULONG NextEntryOffset; // r14d  unsigned __int8 EaNameLength; // r13  FILE_GET_EA_INFORMATION *i; // rbx  unsigned int v15; // ebx  _DWORD *out_buf_pos; // r13  unsigned int ea_block_size; // r14d  unsigned int v18; // ebx  FILE_FULL_EA_INFORMATION *ea_block; // rdx  char v21; // al  ULONG v22; // [rsp+20h] [rbp-38h]  unsigned int ea_block_pos; // [rsp+24h] [rbp-34h] BYREF  _DWORD *v24; // [rsp+28h] [rbp-30h]  struct _STRING DesEaName; // [rsp+30h] [rbp-28h] BYREF  STRING SourceString; // [rsp+40h] [rbp-18h] BYREF??unsigned?int?occupied_length;?//?[rsp+A0h]?[rbp+48h]  v8 = 0;  *a1 = 0i64;  v24 = 0i64;  v9 = 0;  occupied_length = 0;  padding = 0;  a1[1] = 0i64;  while ( 1 )  {                                             // 創(chuàng)建一個(gè)索引放入ealist成員,后續(xù)循環(huán)取值    GetEaInfo = (FILE_GET_EA_INFORMATION *)((char *)UserEaList + v9);    *(_QWORD *)&DesEaName.Length = 0i64;    DesEaName.Buffer = 0i64;    *(_QWORD *)&SourceString.Length = 0i64;    SourceString.Buffer = 0i64;    DesEaName.Length = GetEaInfo->EaNameLength;    DesEaName.MaximumLength = DesEaName.Length;    DesEaName.Buffer = GetEaInfo->EaName;    RtlUpperString(&DesEaName, &DesEaName);    if ( !(unsigned __int8)NtfsIsEaNameValid(&DesEaName) )      break;    NextEntryOffset = GetEaInfo->NextEntryOffset;    EaNameLength = GetEaInfo->EaNameLength;    v22 = GetEaInfo->NextEntryOffset + v9;    for ( i = UserEaList; ; i = (FILE_GET_EA_INFORMATION *)((char *)i + i->NextEntryOffset) )    {      if ( i == GetEaInfo )      {        v15 = occupied_length;        out_buf_pos = (_DWORD *)(User_Buffer + padding + occupied_length);//   // 分配的內(nèi)核池        if ( (unsigned __int8)NtfsLocateEaByName(// 通過名字查找EA信息                                eas_blocks_for_file,                                *(unsigned int *)(a3 + 4),                                &DesEaName,                                &ea_block_pos) )        {          ea_block = (FILE_FULL_EA_INFORMATION *)((char *)eas_blocks_for_file + ea_block_pos);          ea_block_size = ea_block->EaValueLength + ea_block->EaNameLength + 9;          if ( ea_block_size <= User_Buffer_Length - padding )// 此處其實(shí)有個(gè)防止溢出的大小的檢查          {            memmove(out_buf_pos, ea_block, ea_block_size);// 緩沖區(qū)溢出的漏洞點(diǎn)            *out_buf_pos = 0;            goto LABEL_8;          }        }        else        {          ea_block_size = GetEaInfo->EaNameLength + 9;// 通過名字沒查到EA信息走的分支          if ( ea_block_size + padding <= User_Buffer_Length )          {            *out_buf_pos = 0;            *((_BYTE *)out_buf_pos + 4) = 0;            *((_BYTE *)out_buf_pos + 5) = GetEaInfo->EaNameLength;            *((_WORD *)out_buf_pos + 3) = 0;            memmove(out_buf_pos + 2, GetEaInfo->EaName, GetEaInfo->EaNameLength);            SourceString.Length = DesEaName.Length;            SourceString.MaximumLength = DesEaName.Length;            SourceString.Buffer = (PCHAR)(out_buf_pos + 2);            RtlUpperString(&SourceString, &SourceString);            v15 = occupied_length;            *((_BYTE *)out_buf_pos + GetEaInfo->EaNameLength + 8) = 0;LABEL_8:            v18 = ea_block_size + padding + v15;            occupied_length = v18;            if ( !a7 )            {              if ( v24 )                *v24 = (_DWORD)out_buf_pos - (_DWORD)v24;              if ( GetEaInfo->NextEntryOffset )              {                v24 = out_buf_pos;                User_Buffer_Length -= ea_block_size + padding;                padding = ((ea_block_size + 3) & 0xFFFFFFFC) - ea_block_size;// padding對(duì)齊的計(jì)算                goto LABEL_26;              }            }LABEL_12:            a1[1] = v18;LABEL_13:            *(_DWORD *)a1 = v8;            return a1;          }        }        v21 = NtfsStatusDebugFlags;        a1[1] = 0i64;        if ( v21 )          NtfsStatusTraceAndDebugInternal(0i64, 2147483653i64, 919406i64);        v8 = -2147483643;        goto LABEL_13;      }      if ( EaNameLength == i->EaNameLength && !memcmp(GetEaInfo->EaName, i->EaName, EaNameLength) )        break;    }    if ( !NextEntryOffset )    {      v18 = occupied_length;      goto LABEL_12;    }LABEL_26:    v9 = v22;  }  a1[1] = v9;  if ( NtfsStatusDebugFlags )    NtfsStatusTraceAndDebugInternal(0i64, 2147483667i64, 919230i64);  *(_DWORD *)a1 = -2147483629;  return a1;}

那么三個(gè)參數(shù)是如何來的丸升,哪一個(gè)是用戶態(tài)可控的,因?yàn)槿绻鹐a_block_size可控且User_Buffer_Length可控為0就可以輕松繞過檢查牺氨,ea_block_size還可以正好導(dǎo)致溢出發(fā)生发钝。

NtfsQueryEaUserEaList函數(shù)大致會(huì)做循環(huán)遍歷文件的每個(gè)NTFS擴(kuò)展屬性(Ea-Extended the attribute index)然后從ea_block復(fù)制出來這些buffer到緩沖區(qū) (ea_block_size的值)

ea_block_size的值又是由ea_block決定的(ea_block->EaValueLength + ea_block->EaNameLength + 9)其實(shí)最后就是繞過這個(gè)檢查 具體繞過思考

參考與ncc的計(jì)算方法顿涣,用數(shù)學(xué)公式表達(dá)一下方便(注:以下是根據(jù)代碼轉(zhuǎn)換成數(shù)學(xué)公式,只是個(gè)人覺得這么理解第一次比較好理解哈)ea_block_size <= User_Buffer_Length - padding 上邊說過是繞過這個(gè)條件判斷的檢查首先假設(shè)幾個(gè)值EaNameLength = x ,EaValueLength = y ,ea_block_size = z ,padding就是padding本身,User_Buffer_Length = f那么首先能根據(jù)代碼確定幾個(gè)式子 z = x + y + 9 , 判斷條件為 z <= f - padding首先開始第一次循環(huán)從數(shù)組里取值假設(shè)x = 5 ,y = 4 , 所以z = 5 + 4 + 9 = 18 ,padding = 0此時(shí)如果 設(shè)其值為30(User_Buffer_Length -= ea_block_size + padding)那么f = 30 - z + 0 = 12然后計(jì)算padding?=?((z?+?3)&?0xFFFFFFFC)?-?z?=?2第二次從擴(kuò)展屬性取值,依舊 x = 5, y =4 ,z = 5 + 4 + 9=18此時(shí)padding為2?f?=?12那么?18?<=?12?-?2?這個(gè)條件不成立,這是正常的想進(jìn)行溢出的流程這是假設(shè)其值為30的情況也就是f稍大于z的情況,那么我們假設(shè)的值不是30是18呢再來一遍第一次循環(huán)取值 x = 5,y = 4 , z = 5 + 4 + 9 =18 不變,padding 依舊是018 <= 18 - 0這時(shí)候此時(shí)條件是滿足,接著往下進(jìn)行設(shè)其值為18 (User_Buffer_Length -= ea_block_size + padding)那么f = 18 - 18 + 0 =0 ,padding計(jì)算不變 因?yàn)橛X得padding的值 z 并沒有變化 padding = ((z + 3)& 0xFFFFFFFC) - z = 2我們第二次擴(kuò)展 x = 5 , y = 99 , z = 5 + 99 + 9 = 113z <= f - padding 也就是 113 <= 0 - 2 ,因?yàn)槭菬o符號(hào)整數(shù),最后-2就會(huì)導(dǎo)致整數(shù)溢出從而繞過了這個(gè)條件那么超出的大小就會(huì)被覆蓋到相鄰的內(nèi)存導(dǎo)致溢出

代碼中其實(shí)可以看見其會(huì)不斷遍歷ea_block數(shù)組里邊的值,然后再根據(jù)FILE_GET_EA_INFORMATION 獲取到文件里的EA信息酝豪,通過上述的分析我們已經(jīng)知道如何過掉溢出的檢查了

分配的池空間PoolWithTag 到 NtfsQueryEaUserEaList -->User_Buffer --> out_buf_pos 最后memmove觸發(fā)

漏洞觸發(fā)利用

了解了漏洞觸發(fā)點(diǎn)之后涛碑,下一步就是驗(yàn)證。
首先需要?jiǎng)?chuàng)建一個(gè)文件然后添加EA拓展屬性=>NtSetEaFile該函數(shù)的第3個(gè)參數(shù)是一個(gè)FILE_FULL_EA_INFORM-ATION結(jié)構(gòu)的緩沖區(qū)孵淘,用來指定Ea屬性的值蒲障。所以我們可以利用EA屬性來構(gòu)造PAYLOAD, 然后使用NtQueryEaFile函數(shù)來觸發(fā)NtQueryEaFile

查詢一下能對(duì)EA 擴(kuò)展屬性進(jìn)行操作的api記一下這兩個(gè)ZwQueryEaFile , ZwSetEaFile 分別對(duì)應(yīng)NtSetEaFile , NtQueryEaFile

NTSTATUS ZwQueryEaFile(    [in]           HANDLE           FileHandle, //文件句柄    [out]          PIO_STATUS_BLOCK IoStatusBlock,    [out]          PVOID            Buffer, //擴(kuò)展屬性緩沖區(qū)(FILE_FULL_EA_INFORMATION結(jié)構(gòu))    [in]           ULONG            Length, //緩沖區(qū)大小    [in]           BOOLEAN          ReturnSingleEntry,    [in, optional] PVOID            EaList, //指定需要查詢的擴(kuò)展屬性    [in]           ULONG            EaListLength,    [in, optional] PULONG           EaIndex, //指定需要查詢的起始索引    [in]           BOOLEAN          RestartScan);NTSTATUS ZwSetEaFile(    [in] HANDLE FileHandle,    [out] PIO_STATUS_BLOCK IoStatusBlock,    [in] PVOID Buffer,    [in] ULONG Length,    );

WNF是一個(gè)通知系統(tǒng)在整個(gè)系統(tǒng)中的主要任務(wù)就是用于通知瘫证,相當(dāng)于通知中心揉阎。它可以在內(nèi)核模式中使用,也可以在用戶態(tài)被調(diào)用WNF背捌。我們要明白上述的輸出緩沖區(qū)buffer是從用戶空間傳入的毙籽,同時(shí)傳入的還有這個(gè)緩沖區(qū)的長度。

這意味著我們最終會(huì)根據(jù)緩沖區(qū)的大小控制內(nèi)核空間的大小分配毡庆,觸發(fā)漏洞的話還需要觸發(fā)如上所述的溢出坑赡。我們需要進(jìn)行堆噴在內(nèi)核進(jìn)行我們想要的堆布局。

利用手法是WNF

WNF_STATE_DATA  //用戶可以定義的NtCreateWnfStateName  //創(chuàng)建WNF對(duì)象實(shí)例=>WNF_NAME_INSTANCENtUpdateWnfStateData  //寫入數(shù)據(jù)存放在WNF_STATE_DATANtQueryWnfStateData   //讀取寫入的數(shù)據(jù)NtDeleteWnfStateData  //釋放Create創(chuàng)建的對(duì)象

有限的地址讀寫
所以首先要通過NtCreateWnfStateName創(chuàng)建一個(gè)WNF對(duì)象實(shí)例要利用漏洞溢出點(diǎn)Ntfs噴出來的堆塊去覆蓋WNF_STATE_DATA中的DataSize成員和AllocateSize成員么抗。

然后可以利用NtQueryWnfStateData去進(jìn)行讀取毅否,NtUpdateWnfStateData 去進(jìn)行修改相鄰WNF_NAME_INSTANCE數(shù)據(jù),但是此時(shí)這里完成的有限的地址讀寫蝇刀。

任意地址讀寫
利用相對(duì)內(nèi)存寫修改鄰近的 WNF_NAME_INSTANCE結(jié)構(gòu)的 StateData指針為任意內(nèi)存地址螟加,然后就可以通過NtQueryWnfStateData,NtUpdateWnfStateData來實(shí)現(xiàn)任意地址讀寫了。最后可以通過NtDeleteWnfStateData可以釋放掉這個(gè)對(duì)象吞琐。

歡迎關(guān)注長白山攻防實(shí)驗(yàn)室微信公眾號(hào)定期更新優(yōu)質(zhì)文章分享

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末捆探,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子站粟,更是在濱河造成了極大的恐慌徐许,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,884評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件卒蘸,死亡現(xiàn)場(chǎng)離奇詭異雌隅,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)缸沃,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,347評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門恰起,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人趾牧,你說我怎么就攤上這事检盼。” “怎么了翘单?”我有些...
    開封第一講書人閱讀 157,435評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵吨枉,是天一觀的道長蹦渣。 經(jīng)常有香客問我,道長貌亭,這世上最難降的妖魔是什么柬唯? 我笑而不...
    開封第一講書人閱讀 56,509評(píng)論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮圃庭,結(jié)果婚禮上锄奢,老公的妹妹穿的比我還像新娘。我一直安慰自己剧腻,他們只是感情好拘央,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,611評(píng)論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著书在,像睡著了一般灰伟。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上儒旬,一...
    開封第一講書人閱讀 49,837評(píng)論 1 290
  • 那天栏账,我揣著相機(jī)與錄音,去河邊找鬼义矛。 笑死,一個(gè)胖子當(dāng)著我的面吹牛盟萨,可吹牛的內(nèi)容都是我干的凉翻。 我是一名探鬼主播,決...
    沈念sama閱讀 38,987評(píng)論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼捻激,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼制轰!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起胞谭,我...
    開封第一講書人閱讀 37,730評(píng)論 0 267
  • 序言:老撾萬榮一對(duì)情侶失蹤垃杖,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后丈屹,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體调俘,經(jīng)...
    沈念sama閱讀 44,194評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,525評(píng)論 2 327
  • 正文 我和宋清朗相戀三年旺垒,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了彩库。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,664評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡先蒋,死狀恐怖骇钦,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情竞漾,我是刑警寧澤眯搭,帶...
    沈念sama閱讀 34,334評(píng)論 4 330
  • 正文 年R本政府宣布窥翩,位于F島的核電站,受9級(jí)特大地震影響鳞仙,放射性物質(zhì)發(fā)生泄漏寇蚊。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,944評(píng)論 3 313
  • 文/蒙蒙 一繁扎、第九天 我趴在偏房一處隱蔽的房頂上張望幔荒。 院中可真熱鬧,春花似錦梳玫、人聲如沸爹梁。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,764評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽姚垃。三九已至,卻和暖如春盼忌,著一層夾襖步出監(jiān)牢的瞬間积糯,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,997評(píng)論 1 266
  • 我被黑心中介騙來泰國打工谦纱, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留看成,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,389評(píng)論 2 360
  • 正文 我出身青樓跨嘉,卻偏偏與公主長得像川慌,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子祠乃,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,554評(píng)論 2 349

推薦閱讀更多精彩內(nèi)容