CVE-2023-38148 Windows Ics Rce分析

基本信息

依赖于ICS服务,Internet Connect Sharing,对应注册表,依赖ipnathlp.dll

HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\SharedAccess

漏洞存在于处理DHCP请求时,由于没有检查边界,导致在使用memset时使用的长度参数来源于数据包内,可以导致栈溢出。 服务调试参考第二个参考链接。

影响版本

环境搭建

参考 https://github.com/ruijanlee/h3cc/blob/master/h3cc_ruijanlee/doc/c8.md ,同时加一个Linux,网卡使用第二个网卡,使得Linux发出的DHCP包能够被Windows接收到。

技术分析&调试

静态分析

对比补丁修复前后的逻辑,有两个明显的不同点,有两种产生漏洞的可能的地方。

  1. 在修复版本中在进行 if ( *((_BYTE *)a2 + 230) > 0x20u )判断之前先调用了 DumpDhcpHeaderInfo,在漏洞代码中先进行判断在调用DumpDhcpHeaderInfo
  2. 在修复版本中如果满足 if ( *((_BYTE *)a2 + 230) > 0x20u ) 则进入if内,在结束if语句时会通过跳转略过一部分处理逻辑,而在未修复版本内则还会继续处理。

可以看出 a2 + 230_NH_BUFFER 结构体内的某个长度字段,该处为判断这个长度字段存储的长度,该漏洞应该是溢出漏洞,并且在产生漏洞的地方需要读取该字段。

所以漏洞应该是第二点所说的,产生在略过的逻辑中。

// 未修复代码
void __fastcall DhcpProcessMessage(struct _DHCP_INTERFACE *a1, struct _NH_BUFFER *a2)
{
 ......
  memset_0(&v12, 0, 0x40ui64);
  if ( *((_BYTE *)a2 + 230) > 0x20u )
  {
    if ( v4 != (CInterfaceMonitor *)&WPP_GLOBAL_Control && (*((_BYTE *)v4 + 28) & 2) != 0 && *((_BYTE *)v4 + 25) >= 4u )
      WPP_SF_dD(
        *((_QWORD *)v4 + 2),
        97i64,
        &WPP_2a3aeb8dd77c3a1919c551579bb6cf5d_Traceguids,
        *((unsigned __int8 *)a2 + 230),
        32);
    _InterlockedIncrement((volatile signed __int32 *)&DhcpStatistics);
  }
  DumpDhcpHeaderInfo(a2);


// 修复代码
void __fastcall DhcpProcessMessage(struct _DHCP_INTERFACE *a1, struct _NH_BUFFER *a2)
{
  ......
  memset_0(&v11, 0, 0x40ui64);
  DumpDhcpHeaderInfo(a2);
  if ( *((_BYTE *)a2 + 230) > 0x20u )
  {
    if ( WPP_GLOBAL_Control != (CInterfaceMonitor *)&WPP_GLOBAL_Control
      && (*((_BYTE *)WPP_GLOBAL_Control + 28) & 2) != 0
      && *((_BYTE *)WPP_GLOBAL_Control + 25) >= 4u )
    {
      WPP_SF_dd(
        *((_QWORD *)WPP_GLOBAL_Control + 2),
        97i64,
        &WPP_df007ca3347434f5610fc5a17e95e0a3_Traceguids,
        *((unsigned __int8 *)a2 + 230),
        32);
    }
    goto LABEL_10;
  }

LABEL_10:
    _InterlockedIncrement((volatile signed __int32 *)&DhcpStatistics);// 这里多了调用
    goto LABEL_11;
    ......
LABEL_11:
  EnterCriticalSection(&DhcpInterfaceLock);
  if ( *((int *)a1 + 19) < 0 )
  {
    LeaveCriticalSection(&DhcpInterfaceLock);

略过的代码中,读取了a2参数的代码如下:

  if ( DhcpExtractOptionsFromMessage((struct _NH_BUFFER *)((char *)a2 + 228), *((_DWORD *)a2 + 55), &v11) )
  .....
  if ( !v12 )
  {
    .....
    DhcpProcessBootpMessage(a1, a2, &v11);
    goto LABEL_11;
  }
  .....
  if ( DhcpIsLocalHardwareAddress((unsigned __int8 *)a2 + 256, *((unsigned __int8 *)a2 + 230)) )
  {
    ....
  }
  v7 = *(unsigned __int8 *)(v12 + 2);
  if ( v7 == 1 )
  {
    .....
    DhcpProcessDiscoverMessage(a1, a2, &v11);
  }
  else if ( *(_BYTE *)(v12 + 2) == 3 )
  {
    ......
    DhcpProcessRequestMessage(a1, a2, &v11);
  }
  ......
    }
    if ( !DhcpArpForDad )
    {
      v10 = *(_DWORD *)(v13 + 2);
      DhcpRemoveArpEntry(v10);
      DhcpCancelLease(v10, (unsigned __int8 *)a2 + 256, *((unsigned __int8 *)a2 + 230));
   .....
  }
  else
  {
    if ( *(_BYTE *)(v12 + 2) != 7 )
    {
      if ( *(_BYTE *)(v12 + 2) == 8 )
      {
        ......
        DhcpProcessInformMessage(a1, a2, &v11);
      }
      else
      {
        ......
      }
      goto LABEL_11;
    }
    if ( !DhcpArpForDad )
    {
      DhcpRemoveArpEntry(*((_DWORD *)a2 + 60));
      DhcpCancelLease(*((_DWORD *)a2 + 60), (unsigned __int8 *)a2 + 256, *((unsigned __int8 *)a2 + 230));
    }
   ......
  }

查看这些函数代码,在 DhcpProcessBootpMessage函数中有如下逻辑

void __fastcall DhcpProcessBootpMessage(
  v3 = a2;
  v5 = (char *)v3 + 228;
  ......
      else
      {
        if ( !DhcpSendUnicastMessagesEnabled
          || v5[10] < 0
          || DhcpAddArpEntry(v6, (unsigned __int8 *)v5 + 28, (unsigned __int8)v5[2], v23) )// 这个函数触发了漏洞
        {
// movzx   r8d, byte ptr [r15+2]           ; Size
        ...

前面知道 a2 + 230是长度字段,v5=v2+228,传入 DhcpAddArpEntry的size参数为v5+2,也就是a2 + 230DhcpAddArpEntry函数中,Row为栈内结构体,memcpy传入的长度参数为a2 + 230,也就是补丁中判断的长度参数。 MIB_IPNET_ROW2结构体定义可以在 这找到,其大小为0x58

__int64 __fastcall DhcpAddArpEntry(DWORD a1, unsigned __int8 *Src, size_t Size, struct _DHCP_INTERFACE *a4)
{
  MIB_IPNET_ROW2 Row;
  ......
  v4 = (unsigned int)Size;
  .....
    memset_0(&Row, 0, sizeof(Row));
    Row.InterfaceIndex = DhcpAdapterIndex;
    Row.Address.Ipv4.sin_family = 2;
    Row.Address.Ipv4.sin_addr.S_un.S_addr = a1;
    Row.PhysicalAddressLength = v4;
    memcpy_0(Row.PhysicalAddress, Src, v4);
    ......
    return v11;
  }
}

所以漏洞触发路径为 DhcpProcessMessage->DhcpProcessBootpMessage->DhcpAddArpEntry->memcpy_0,当长度参数过长时可以利用memcpy触发栈溢出。

动态调试

使用windbg附加到svchost进程,在 ipnathlp!DhcpProcessMessage断点,而后触发DHCP请求,windbg在 ipnathlp!DhcpProcessMessage断下 由于不知道 DhcpProcessMessage的a2结构体定义,此处构造正常的DHCP请求,并在调试器中查看这个结构体成员信息。 单步运行到判断长度的地方,此时rsi指向传入的 _NH_BUFFER结构体,

0:004> u
ipnathlp!DhcpProcessMessage+0x7f:
00007ff9`c00176f3 488dbee4000000  lea     rdi,[rsi+0E4h]
00007ff9`c00176fa 41b604          mov     r14b,4
00007ff9`c00176fd 807f0220        cmp     byte ptr [rdi+2],20h
00007ff9`c0017701 7636            jbe     ipnathlp!DhcpProcessMessage+0xc5 (00007ff9`c0017739)
00007ff9`c0017703 493bdc          cmp     rbx,r12
00007ff9`c0017706 742a            je      ipnathlp!DhcpProcessMessage+0xbe (00007ff9`c0017732)
00007ff9`c0017708 44847b1c        test    byte ptr [rbx+1Ch],r15b
00007ff9`c001770c 7424            je      ipnathlp!DhcpProcessMessage+0xbe (00007ff9`c0017732)

可以在调试器内看到 (_BYTE *)a2 + 230)值为6

0:004> db rdi+2
00000203`faa1fdb6  06 00 1a cc 8a 61 00 00-80 00 00 00 00 00 00 00  .....a..........
00000203`faa1fdc6  00 00 00 00 00 00 00 00-00 00 00 0c 29 c2 3a 42  ............).:B
00000203`faa1fdd6  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
00000203`faa1fde6  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
00000203`faa1fdf6  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
00000203`faa1fe06  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
00000203`faa1fe16  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
00000203`faa1fe26  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................

而该处数据来源于DHCP客户端发送的DHCP请求,在wireshark中可以看到数据包中刚好有长度字段值为6,说明(_BYTE *)a2 + 230)处有可能是数据包内的Hardware address length

此时调用栈:

0:004> k
Child-SP          RetAddr               Call Site
0000000e`3487f480 00007ff9`c00143a4     ipnathlp!DhcpProcessMessage+0x86
0000000e`3487f540 00007ff9`c0006ecf     ipnathlp!DhcpReadCompletionRoutine+0x644
0000000e`3487f5a0 00007ff9`eebe32ea     ipnathlp!NhpIoCompletionRoutine+0x6f
0000000e`3487f5d0 00007ff9`eeb22f86     ntdll!RtlpTpIoCallback+0xca
0000000e`3487f610 00007ff9`ee0a7614     ntdll!TppWorkerThread+0x456
0000000e`3487f910 00007ff9`eeb226b1     KERNEL32!BaseThreadInitThunk+0x14
0000000e`3487f940 00000000`00000000     ntdll!RtlUserThreadStart+0x21

此时尝试手动将(_BYTE *)a2 + 230)修改为0xfe,继续运行,但没有触发异常。

0:004> db rdi+2
00000203`faa1fdb6  06 00 1a cc 8a 61 00 00-80 00 00 00 00 00 00 00  .....a..........
00000203`faa1fdc6  00 00 00 00 00 00 00 00-00 00 00 0c 29 c2 3a 42  ............).:B
00000203`faa1fdd6  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
00000203`faa1fde6  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
00000203`faa1fdf6  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
00000203`faa1fe06  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
00000203`faa1fe16  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
00000203`faa1fe26  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
0:004> eb rdi+2 fe
0:004> db rdi+2
00000203`faa1fdb6  fe 00 1a cc 8a 61 00 00-80 00 00 00 00 00 00 00  .....a..........
00000203`faa1fdc6  00 00 00 00 00 00 00 00-00 00 00 0c 29 c2 3a 42  ............).:B
00000203`faa1fdd6  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
00000203`faa1fde6  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
00000203`faa1fdf6  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
00000203`faa1fe06  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
00000203`faa1fe16  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
00000203`faa1fe26  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
0:002> g
Breakpoint 0 hit
ipnathlp!DhcpProcessMessage:
00007ff9`c0017674 48895c2418      mov     qword ptr [rsp+18h],rbx ss:0000000e`346ff630=00000203faa7e350

改为单步调试,再次发起DHCP请求,发现没有进入到漏洞函数 DhcpProcessBootpMessage中,原因是v13不为0,条件不成立,不会调用 DhcpProcessBootpMessage

if ( !v13 )
  {
    if ( WPP_GLOBAL_Control != (CInterfaceMonitor *)&WPP_GLOBAL_Control
      && (*((_BYTE *)WPP_GLOBAL_Control + 28) & 2) != 0
      && *((_BYTE *)WPP_GLOBAL_Control + 25) >= 4u )
    {
      WPP_SF_(*((_QWORD *)WPP_GLOBAL_Control + 2), 98i64, &WPP_2a3aeb8dd77c3a1919c551579bb6cf5d_Traceguids);
    }
    DhcpProcessBootpMessage(a1, a2, &v12);      // 这里触发漏洞
    goto LABEL_25;
  }

对v13下写断点

0:004> ba w1 rsp+0x38
0:004> g
Breakpoint 6 hit
msvcrt!memset+0x35:
00007ff9`ed1046b5 4983e908        sub     r9,8

触发断点,此时调用栈如下,对应代码为 memset_0(a3, 0, 0x40ui64);

0:004> k
Child-SP          RetAddr               Call Site
0000000e`3487f418 00007ff9`c0015b63     msvcrt!memset+0x35
0000000e`3487f420 00007ff9`c0017754     ipnathlp!DhcpExtractOptionsFromMessage+0x7b
0000000e`3487f480 00007ff9`c00143a4     ipnathlp!DhcpProcessMessage+0xe0
0000000e`3487f540 00007ff9`c0006ecf     ipnathlp!DhcpReadCompletionRoutine+0x644
0000000e`3487f5a0 00007ff9`eebe32ea     ipnathlp!NhpIoCompletionRoutine+0x6f
0000000e`3487f5d0 00007ff9`eeb22f86     ntdll!RtlpTpIoCallback+0xca
0000000e`3487f610 00007ff9`ee0a7614     ntdll!TppWorkerThread+0x456
0000000e`3487f910 00007ff9`eeb226b1     KERNEL32!BaseThreadInitThunk+0x14
0000000e`3487f940 00000000`00000000     ntdll!RtlUserThreadStart+0x21

此处将目标内存清零,不符合前面说的条件,继续运行,再次触发写断,调用栈为

0:004> k
Child-SP          RetAddr               Call Site
0000000e`3487f420 00007ff9`c0017754     ipnathlp!DhcpExtractOptionsFromMessage+0x428
0000000e`3487f480 00007ff9`c00143a4     ipnathlp!DhcpProcessMessage+0xe0
0000000e`3487f540 00007ff9`c0006ecf     ipnathlp!DhcpReadCompletionRoutine+0x644
0000000e`3487f5a0 00007ff9`eebe32ea     ipnathlp!NhpIoCompletionRoutine+0x6f
0000000e`3487f5d0 00007ff9`eeb22f86     ntdll!RtlpTpIoCallback+0xca
0000000e`3487f610 00007ff9`ee0a7614     ntdll!TppWorkerThread+0x456
0000000e`3487f910 00007ff9`eeb226b1     KERNEL32!BaseThreadInitThunk+0x14
0000000e`3487f940 00000000`00000000     ntdll!RtlUserThreadStart+0x21

对应在DhcpExtractOptionsFromMessage的代码如下,当OptionID为0x35时进入case语句内

v9 = (struct _DHCP_OPTION *)((char *)a1 + 240);
OptionID = v9->OptionID;
    if ( OptionID )
    {
      switch ( OptionID )
        case 0x35u:
          if ( v6 != (CInterfaceMonitor *)&WPP_GLOBAL_Control
            && (*((_BYTE *)v6 + 28) & 2) != 0
            && *((_BYTE *)v6 + 25) >= 4u )
          {
            WPP_SF_(*((_QWORD *)v6 + 2), 44i64, &WPP_2a3aeb8dd77c3a1919c551579bb6cf5d_Traceguids);
            v6 = WPP_GLOBAL_Control;
          }
          if ( BYTE1(v9->OptionID) )
          {
            a3[1] = v9;
          }

_DHCP_OPTION结构体定义如下,对应于DHCP请求内的option

typedef DWORD DHCP_OPTION_ID;
struct _DHCP_OPTION
{
  DHCP_OPTION_ID OptionID;
  LPWSTR OptionName;
  LPWSTR OptionComment;
  DHCP_OPTION_DATA DefaultValue;
  DHCP_OPTION_TYPE OptionType;
};
0:002> db rdi
00000203`faa1fea4  35 01 03 3d 07 01 00 0c-29 c2 3a 42 32 04 c0 a8  5..=....).:B2...
00000203`faa1feb4  89 cd 0c 0f 44 45 53 4b-54 4f 50 2d 54 35 50 37  ....DESKTOP-T5P7
00000203`faa1fec4  34 45 53 51 12 00 00 00-44 45 53 4b 54 4f 50 2d  4ESQ....DESKTOP-
00000203`faa1fed4  54 35 50 37 34 45 53 3c-08 4d 53 46 54 20 35 2e  T5P74ES<.MSFT 5.
00000203`faa1fee4  30 37 0e 01 03 06 0f 1f-21 2b 2c 2e 2f 77 79 f9  07......!+,./wy.

根据RFC rfc2132 option 53为传递DHCP消息类型,第一个字节是操作编号,第二个字节恒为1,第三个字节是消息类型,范围是1-9

根据代码,当DHCP中含有option 53一定会进入 DhcpExtractOptionsFromMessageif ( BYTE1(v9->OptionID) ),把a3[1]赋值为不为零的值。 回到DhcpProcessMessage内,v13就不为0,不能进入触发漏洞的逻辑

重新构造DHCP数据包,,删除option53并将 Hardware address length改为100,单步调试,成功进入到 DhcpAddArpEntry函数内。

0:004> k
Child-SP          RetAddr               Call Site
0000000e`3487f2a0 00007ff9`c0016766     ipnathlp!DhcpAddArpEntry+0x14a
0000000e`3487f380 00007ff9`c0017797     ipnathlp!DhcpProcessBootpMessage+0x5ea
0000000e`3487f480 00007ff9`c00143a4     ipnathlp!DhcpProcessMessage+0x123
0000000e`3487f540 00007ff9`c0006ecf     ipnathlp!DhcpReadCompletionRoutine+0x644
0000000e`3487f5a0 00007ff9`eebe32ea     ipnathlp!NhpIoCompletionRoutine+0x6f
0000000e`3487f5d0 00007ff9`eeb22f86     ntdll!RtlpTpIoCallback+0xca
0000000e`3487f610 00007ff9`ee0a7614     ntdll!TppWorkerThread+0x456
0000000e`3487f910 00007ff9`eeb226b1     KERNEL32!BaseThreadInitThunk+0x14
0000000e`3487f940 00000000`00000000     ntdll!RtlUserThreadStart+0x21

在调试器中可以看到,执行memcpy时长度参数为0x64,继续运行则触发了栈溢出,进程异常退出。

0:005> g
Breakpoint 9 hit
ipnathlp!DhcpAddArpEntry+0x184:
00007ff9`c0012570 e83db80600      call    ipnathlp!memcpy (00007ff9`c007ddb2)
0:005> rr8
r8=0000000000000064
0:005> g

STATUS_STACK_BUFFER_OVERRUN encountered
(1858.3b4): Break instruction exception - code 80000003 (first chance)
KERNELBASE!UnhandledExceptionFilter+0x7c:
00007ff9`ec55dd3c cc              int     3
0:005> k
Child-SP          RetAddr               Call Site
0000000e`34b7efa0 00007ff9`c007d096     KERNELBASE!UnhandledExceptionFilter+0x7c
0000000e`34b7f0c0 00007ff9`c007d229     ipnathlp!_raise_securityfailure+0x1a
0000000e`34b7f0f0 00007ff9`c0012600     ipnathlp!_report_gsfailure+0x169
0000000e`34b7f180 00007ff9`c0016766     ipnathlp!DhcpAddArpEntry+0x214
0000000e`34b7f260 00007ff9`c0017797     ipnathlp!DhcpProcessBootpMessage+0x5ea
0000000e`34b7f360 00007ff9`c00143a4     ipnathlp!DhcpProcessMessage+0x123
0000000e`34b7f420 00007ff9`c0006ecf     ipnathlp!DhcpReadCompletionRoutine+0x644
0000000e`34b7f480 00007ff9`eebe32ea     ipnathlp!NhpIoCompletionRoutine+0x6f
0000000e`34b7f4b0 00007ff9`eeb22f86     ntdll!RtlpTpIoCallback+0xca
0000000e`34b7f4f0 00007ff9`ee0a7614     ntdll!TppWorkerThread+0x456
0000000e`34b7f7f0 00007ff9`eeb226b1     KERNEL32!BaseThreadInitThunk+0x14
0000000e`34b7f820 00000000`00000000     ntdll!RtlUserThreadStart+0x21
0:005> g
ntdll!NtWaitForWorkViaWorkerFactory+0x14:
00007ff9`eeb70aa4 c3              ret

wireshrk中可以看到数据包协议为Bootp。

PoC参考 简单实现的DHCP Client并将option 53注释,将 Hardware address length改为0x100。 这个栈溢出长度和内容均为内容可控

小结

这个漏洞起源于memcpy时src和len参数均来源于数据包内,为用户可控,导致攻击者可以通过设置过长长度触发memcpy越界写入,触发时的漏洞函数为处理BOOTP协议,这个协议是DHCP协议前身,DHCP兼容这个协议,在处理Bootp消息时,没有检查长度导致在复制mac时出错。

参考链接

https://bbs.kanxue.com/thread-278835.htm

https://learn.microsoft.com/en-us/windows-hardware/drivers/debugger/preparing-to-debug-the-service-application#-enabling-the-debugging-of-the-initialization-code

Created at 2023-09-18T16:18:40+08:00

创建于:Monday, September 18,2023
最后修改于: Tuesday, January 2,2024