首页
社区
课程
招聘
[原创]HTTP.sys UAF 漏洞 CVE-2021-31166 简要分析
发表于: 2023-1-3 18:38 10593

[原创]HTTP.sys UAF 漏洞 CVE-2021-31166 简要分析

2023-1-3 18:38
10593

最近在看http.sys,必然需要分析旧的漏洞的,使用网上的poc触发了漏洞后,有了个具体概念,就写一篇简要分析,帮助大家理解

通过 POC(链接地址) 可知,漏洞是一个UAF漏洞,栈回溯也很清晰:

从栈回溯可以知道, 它是一个独立线程, 崩溃在解析Header里. 而poc也很简单"Accept-Encoding: doar-e, ftw, imo, ,", 发送一个带"Accept-Encoding"字段的请求, 包含至少一个正常值, 最后带个空值.

以下是函数UlpParseAcceptEncoding的伪代码. 分析参见注释和序号.

以下是UlFreeUnknownCodingList函数:

通过上述注释, 可以明显看到问题出在v29的链表管理上.

下面是释放前后的关系:
释放前的链表关系:
图片描述

步骤7时的链表关系:
图片描述

知道了根本原因, 修补也很简单, 在步骤3和步骤4之间, 把v29的指针重置一下即可. 而官方补丁也是这样做的.

根据测试来看, 在本来win10 2004已经补了CVE-2021-31166, 它的继任者 win 10 21H1, 又重新引入了这个bug, 看起来在6月(或者5月?)的补丁里又补了它, 导致有的人测试还以为CVE-2022-21907就是CVE-2021-31166, 因此github上也有很多拿CVE-2021-31166的poc当作CVE-2022-21907的poc的情况.
事实上CVE-2022-21907属于另一个未初始化的问题. 参考此处链接
问题出在UlFastSendHttpResponse函数内, 某种条件下, 让第一次UlpAllocateFastTracker申请就被释放, 然后走第二次申请,让申请的内存没有初始化重要的+80字段的内容, 然后在后续的UlGenerateFixedHeaders中失败, 直接跳转到MmUnmapLockedPages去调用未初始化的数据, 导致崩溃. 具体成因比较复杂, 就不深入分析了.

可以看到, 问题主要还是在于v29在链接了其它链表时, 没有清理自身的链表, 导致有可能UAF, 如果及时清理了, 其实也就不会造成安全问题. 所以我们分析软件安全问题时, 也要多注意这种类似点, 提高敏感度

# Child-SP          RetAddr               Call Site
00 fffff90e`a867e368 fffff804`19525382     nt!DbgBreakPointWithStatus
01 fffff90e`a867e370 fffff804`19524966     nt!KiBugCheckDebugBreak+0x12
02 fffff90e`a867e3d0 fffff804`19408eb7     nt!KeBugCheck2+0x946
03 fffff90e`a867eae0 fffff804`1941ad69     nt!KeBugCheckEx+0x107
04 fffff90e`a867eb20 fffff804`1941b190     nt!KiBugCheckDispatch+0x69
05 fffff90e`a867ec60 fffff804`19419523     nt!KiFastFailDispatch+0xd0
06 fffff90e`a867ee40 fffff804`1db3f677     nt!KiRaiseSecurityCheckFailure+0x323
07 fffff90e`a867efd0 fffff804`1daf6c05     HTTP!UlFreeUnknownCodingList+0x63
08 fffff90e`a867f000 fffff804`1dacd201     HTTP!UlpParseAcceptEncoding+0x299c5
09 fffff90e`a867f0f0 fffff804`1daa93d8     HTTP!UlAcceptEncodingHeaderHandler+0x51
0a fffff90e`a867f140 fffff804`1daa8ab7     HTTP!UlParseHeader+0x218
0b fffff90e`a867f240 fffff804`1da04c5f     HTTP!UlParseHttp+0xac7
0c fffff90e`a867f3a0 fffff804`1da0490a     HTTP!UlpParseNextRequest+0x1ff
0d fffff90e`a867f4a0 fffff804`1daa48c2     HTTP!UlpHandleRequest+0x1aa
0e fffff90e`a867f540 fffff804`1932ae85     HTTP!UlpThreadPoolWorker+0x112
0f fffff90e`a867f5d0 fffff804`19410408     nt!PspSystemThreadStartup+0x55
10 fffff90e`a867f620 00000000`00000000     nt!KiStartSystemThread+0x28
# Child-SP          RetAddr               Call Site
00 fffff90e`a867e368 fffff804`19525382     nt!DbgBreakPointWithStatus
01 fffff90e`a867e370 fffff804`19524966     nt!KiBugCheckDebugBreak+0x12
02 fffff90e`a867e3d0 fffff804`19408eb7     nt!KeBugCheck2+0x946
03 fffff90e`a867eae0 fffff804`1941ad69     nt!KeBugCheckEx+0x107
04 fffff90e`a867eb20 fffff804`1941b190     nt!KiBugCheckDispatch+0x69
05 fffff90e`a867ec60 fffff804`19419523     nt!KiFastFailDispatch+0xd0
06 fffff90e`a867ee40 fffff804`1db3f677     nt!KiRaiseSecurityCheckFailure+0x323
07 fffff90e`a867efd0 fffff804`1daf6c05     HTTP!UlFreeUnknownCodingList+0x63
08 fffff90e`a867f000 fffff804`1dacd201     HTTP!UlpParseAcceptEncoding+0x299c5
09 fffff90e`a867f0f0 fffff804`1daa93d8     HTTP!UlAcceptEncodingHeaderHandler+0x51
0a fffff90e`a867f140 fffff804`1daa8ab7     HTTP!UlParseHeader+0x218
0b fffff90e`a867f240 fffff804`1da04c5f     HTTP!UlParseHttp+0xac7
0c fffff90e`a867f3a0 fffff804`1da0490a     HTTP!UlpParseNextRequest+0x1ff
0d fffff90e`a867f4a0 fffff804`1daa48c2     HTTP!UlpHandleRequest+0x1aa
0e fffff90e`a867f540 fffff804`1932ae85     HTTP!UlpThreadPoolWorker+0x112
0f fffff90e`a867f5d0 fffff804`19410408     nt!PspSystemThreadStartup+0x55
10 fffff90e`a867f620 00000000`00000000     nt!KiStartSystemThread+0x28
    while ( 1 )
    {
      v26[0] = 1000;
      ret = UlpParseContentCoding(// ","为分割, 解析Accept-Encoding字段的值.
              v7,
              v4,
              (unsigned int)&v27,
              (unsigned int)&v32,
              (__int64)&v30,
              (__int64)v26,
              (__int64)&v31);// v31指向解析的字符末尾, 比如 传入"a,b,c\r\n", 一轮下来, v31指向"b,c\r\n".
      if ( ret < 0 )
      {
        if ( ret != 0xC0000225 )                // 1. 当最后一次的长度为0时, UlpParseContentCoding会返回 0xC0000225
          goto LABEL_46;
        if ( v31 == end && !v9 )
        {
          ret = 0;
          goto LABEL_25;
        }
      }
       ....
      if ( v31 >= end )                         // 2. 当刚好是末尾的时候, v31就是消息的末尾. 所以条件成立, 跳出while循环
        break;
      v7 = v31;
      LODWORD(v4) = end - v31;
    }
    Flink = v29.Flink;
    if ( v29.Flink != &v29 )// 3. 当本地链表v29里有链接的时候, 链接到a3变量的结构体里.
    {
      v20 = v29.Blink;
      if ( v29.Flink->Blink != &v29
        || v29.Blink->Flink != &v29
        || (v29.Blink->Flink = v29.Flink,
            p_encoding_link_off10h_990h = &a3->encoding_link_off10h_990h,
            Flink->Blink = v20,
            v22 = a3->encoding_link_off10h_990h.Blink,
            a3->encoding_link_off10h_990h.Flink->Blink != &a3->encoding_link_off10h_990h)
        || v22->Flink != p_encoding_link_off10h_990h
        || Flink->Flink->Blink != Flink
        || v20->Flink != Flink )
      {
LABEL_47:
        __fastfail(3u);
      }
      v22->Flink = Flink;
      a3->encoding_link_off10h_990h.Blink = Flink->Blink;
      Flink->Blink->Flink = p_encoding_link_off10h_990h;
      v23 = v28;
      Flink->Blink = v22;
      *(_WORD *)&a3->gap88C[254] = v23;         // 4. 链接完成后, 没有清理v29链表的前项和后项;
LABEL_46:
      Flink = v29.Flink;
    }
    if ( ret < 0 )                              // 5. 因为ret=0xC0000225, 条件成立, 跳转LABEL_33
      goto LABEL_33;
 
LABEL_33:
    if ( Flink != &v29 )
        UlFreeUnknownCodingList(&v29);
    while ( 1 )
    {
      v26[0] = 1000;
      ret = UlpParseContentCoding(// ","为分割, 解析Accept-Encoding字段的值.
              v7,
              v4,
              (unsigned int)&v27,
              (unsigned int)&v32,
              (__int64)&v30,
              (__int64)v26,
              (__int64)&v31);// v31指向解析的字符末尾, 比如 传入"a,b,c\r\n", 一轮下来, v31指向"b,c\r\n".
      if ( ret < 0 )
      {
        if ( ret != 0xC0000225 )                // 1. 当最后一次的长度为0时, UlpParseContentCoding会返回 0xC0000225
          goto LABEL_46;
        if ( v31 == end && !v9 )
        {
          ret = 0;
          goto LABEL_25;
        }
      }
       ....
      if ( v31 >= end )                         // 2. 当刚好是末尾的时候, v31就是消息的末尾. 所以条件成立, 跳出while循环
        break;

[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

最后于 2023-1-4 17:35 被音货得福编辑 ,原因: 更新内容
收藏
免费 0
支持
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回
//