Srv.sys的SMB协议漏洞

勒索病毒 利用了Srv.sys的SMB协议漏洞,远程代码运行。

SrvOs2GeaListSizeToNt存在一个与SrvOs2FeaListSizeToNt完全一样的漏洞,但前者必须用身份认证才能到达,后者只需要空会话即可。

SrvSmbQueryQuota,RestartWriteNamePipe,srv.sys处理畸形\MAILSLOT型Transaction(0x25)报文时存在远程内核态缓冲区溢出。

Windows SMBv1缓冲区溢出漏洞在Large非分页kernel pool内存,使用File 扩展属性(FEAs)。

函数srv!SrvOs2FeaListToNt会调用srv!SrvOs2FeaListSizeToNt 计算接收到的FEA list大小。计算的FEA大小大于从前值因为一个错误的WORD转换。当FEA List迭代到转换NTFEA LIST, 会发生一个overflow在非分页池因为从前的总大小被错误计算。

漏洞代码可被srv!SrvSmbOpen2触发。

Typedef struct _FEA {
BYTE fEA; /*flags*/
BYTE cbName;
USHORT cbValue;
} FEA;

EternalBlue首先发送一个SRV缓冲区不包括最后的包,因为 Large NON-PAGED POOL 缓冲区会被创建当传输的最后数据到server。SMB服务器将积累一个输入缓冲的数据,直到所有数据已到。SMB服务器将处理数据,数据分发到SrvOpen2函数用于读数据通过CFIS(Common internet file system)。
unsigned int __fastcall SrvOs2FeaListSizeToNt(int pOs2Fea)
{
unsigned int v1; // edi@1
int Length; // ebx@1
int pBody; // esi@1
unsigned int v4; // ebx@1
int v5; // ecx@3
int v8; // [sp+10h] [bp-8h]@3
unsigned int v9; // [sp+14h] [bp-4h]@1
v1 = 0;
Length = *(_DWORD *)pOs2Fea;
pBody = pOs2Fea + 4;
v9 = 0;
v4 = pOs2Fea + Length;
while ( pBody < v4 )
{
if ( pBody + 4 >= v4
|| (v5 = *(_BYTE *)(pBody + 1) + *(_WORD *)(pBody + 2),
v8 = *(_BYTE *)(pBody + 1) + *(_WORD *)(pBody + 2),
v5 + pBody + 5 > v4) )
{
//
// 注意这里修改了Os2Fea的Length,自动适应大小
// 初始值是0x10000,最终变成了0x1ff5d
//
*(_WORD *)pOs2Fea = pBody – pOs2Fea;
return v1;
}
if ( RtlULongAdd(v1, (v5 + 0xC) & 0xFFFFFFFC, &v9) < 0 )
return 0;
v1 = v9;
pBody += v8 + 5;
}
return v1;
}
unsigned int __fastcall SrvOs2FeaListToNt(int pOs2Fea, int *pArgNtFea, int *a3, _WORD *a4)
{
__int16 v5; // bx@1
unsigned int Size; // eax@1
NTFEA *pNtFea; // ecx@3
int pOs2FeaBody; // esi@9
int v10; // edx@9
unsigned int v11; // esi@14
int v12; // [sp+Ch] [bp-Ch]@11
unsigned int v14; // [sp+20h] [bp+8h]@9
v5 = 0;
Size = SrvOs2FeaListSizeToNt(pOs2Fea);
*a3 = Size;
if ( !Size )
{
*a4 = 0;
return 0xC098F0FF;
}
pNtFea = (NTFEA *)SrvAllocateNonPagedPool(Size, 0x15);
*pArgNtFea = (int)pNtFea;
if ( pNtFea )
{
pOs2FeaBody = pOs2Fea + 4;
v10 = (int)pNtFea;
v14 = pOs2Fea + *(_DWORD *)pOs2Fea - 5;
if ( pOs2Fea + 4 > v14 )
{
LABEL_13:
if ( pOs2FeaBody == pOs2Fea + *(_DWORD *)pOs2Fea )
{
*(_DWORD *)v10 = 0;
return 0;
}
v11 = 0xC0000001;
*a4 = v5 – pOs2Fea;
}
else
{
while ( !(*(_BYTE *)pOs2FeaBody & 0x7F) )
{
v12 = (int)pNtFea;
v5 = pOs2FeaBody;
pNtFea = (NTFEA *)SrvOs2FeaToNt(pNtFea, pOs2FeaBody);
pOs2FeaBody += *(_BYTE *)(pOs2FeaBody + 1) + *(_WORD *)(pOs2FeaBody + 2) + 5;
//
// 由于SrvOs2FeaListSizeToNt将pOs2Fea的Length改大了。
// 而且变得大了不少,所以这里的判读就没有什么意义了。最终导致越界的产生。
//
if ( pOs2FeaBody > v14 )
{
v10 = v12;
goto LABEL_13;
}
}
*a4 = pOs2FeaBody – pOs2Fea;
v11 = 0xC000000D;
}
SrvFreeNonPagedPool(*pArgNtFea);
return v11;
}
if ( BYTE1(WPP_GLOBAL_Control->Flags) >= 2u && WPP_GLOBAL_Control->Characteristics & 1 && KeGetCurrentIrql() < 2u )
{
_DbgPrint("SrvOs2FeaListToNt: Unable to allocate %d bytes from nonpaged pool.", *a3, 0);
_DbgPrint("n");
}
return 0xC0000205;
}
首先SrvOs2FeaListToNt首先调用SrvOs2FeaListSizeToNt计算pNtFea的大小。这里注意了SrvOs2FeaListSizeToNt函数会修改原始的pOs2Fea中的Length大小,然后以计算出来的Length来分配pNtFea.最后调用SrvOs2FeaToNt来实现转换。SrvOs2FeaToNt后面的判断就有问题了。这里还不止一个问题。
1. 转换完成后,增加pOs2FeaBody然后比较。正确的逻辑难道不应该是先判断再转换吗?
2. 由于SrvOs2FeaListSizeToNt中改变了pOs2Fea的length的值,这里使用变大后的值做比较,肯定会越界。

为了方便同学们调试,我把代码扣出来了。大家可以在环3围观下这段代码。

#include
signed int RtlULongAdd(unsigned int a1, int a2, unsigned int *a3)
{
unsigned int v3; // edx@1
signed int result; // eax@2
v3 = a1 + a2;
if (v3 < a1)
{
*a3 = -1;
result = -1073741675;
} else
{
*a3 = v3;
result = 0;
}
return result;
}
unsigned int SrvOs2FeaListSizeToNt(PUCHAR pOs2Fea)
{
unsigned int v1; // edi@1
int Length; // ebx@1
PUCHAR pBody; // esi@1
PUCHAR v4; // ebx@1
int v5; // ecx@3
int v8; // [sp+10h] [bp-8h]@3
unsigned int v9; // [sp+14h] [bp-4h]@1
v1 = 0;
Length = *(DWORD*)pOs2Fea;
pBody = pOs2Fea + 4;
v9 = 0;
v4 = pOs2Fea + Length;
while (pBody < v4)
{
if (pBody + 4 >= v4
|| (v5 = *(BYTE *)(pBody + 1) + *(WORD *)(pBody + 2),
v8 = *(BYTE *)(pBody + 1) + *(WORD *)(pBody + 2),
v5 + pBody + 5 > v4))
{
*(WORD *)pOs2Fea = pBody – pOs2Fea;
return v1;
}
if (RtlULongAdd(v1, (v5 + 0xC) & 0xFFFFFFFC, &v9) < 0)
return 0;
v1 = v9;
pBody += v8 + 5;
}
return v1;
}
PUCHAR gpBuffer = NULL;
ULONG guSize = 0;
PUCHAR SrvOs2FeaToNt(PUCHAR pNtFea, PUCHAR pOs2FeaBody)
{
PUCHAR pBody; // edi@1
BYTE *pNtBodyStart; // edi@1
PUCHAR result; // eax@1
pBody = pNtFea + 8;
*(BYTE *)(pNtFea + 4) = *(BYTE *)pOs2FeaBody;
*(BYTE *)(pNtFea + 5) = *(BYTE *)(pOs2FeaBody + 1);
*(WORD *)(pNtFea + 6) = *(WORD *)(pOs2FeaBody + 2);
memcpy((void *)(pNtFea + 8), (const void *)(pOs2FeaBody + 4), *(BYTE *)(pOs2FeaBody + 1));
pNtBodyStart = (BYTE *)(*(BYTE *)(pNtFea + 5) + pBody);
*pNtBodyStart++ = 0;
if ((pNtBodyStart + *(WORD *)(pNtFea + 6)) > (gpBuffer + guSize)){
__debugbreak();
}
memcpy(pNtBodyStart, (const void *)(pOs2FeaBody + 5 + *(BYTE *)(pNtFea + 5)), *(WORD *)(pNtFea + 6));
result = (PUCHAR)((ULONG_PTR)&pNtBodyStart[*(WORD *)(pNtFea + 6) + 3] & 0xFFFFFFFC);
*(DWORD *)pNtFea = result – pNtFea;
static int j = 0;
printf(“j=%dn”, j++);
return result;
}
int main()
{
FILE* pFile = fopen(“1.bin”, “r+b”);
fseek(pFile, 0, SEEK_END);
ULONG uSize = (ULONG)ftell(pFile);
fseek(pFile, 0, SEEK_SET);
PUCHAR pOs2Fea = (PUCHAR)malloc(uSize);
fread(pOs2Fea, 1, uSize, pFile);
fclose(pFile);
ULONG uFixSize = SrvOs2FeaListSizeToNt(pOs2Fea);
PUCHAR pOs2FeaBody;
PUCHAR pNtFea = (PUCHAR)malloc(uFixSize);
PUCHAR v10;
PUCHAR v14;
PUCHAR v12;
PUCHAR v5;
LONG v11;
PUCHAR pNtFeaEnd = pNtFea + uFixSize;
gpBuffer = pNtFea;
guSize = uFixSize;
if (pNtFea)
{
pOs2FeaBody = pOs2Fea + 4;
v10 = pNtFea;
v14 = pOs2Fea + *(DWORD *)pOs2Fea – 5;
if (pOs2Fea + 4 > v14)
{
LABEL_13:
if (pOs2FeaBody == pOs2Fea + *(DWORD *)pOs2Fea)
{
*(DWORD *)v10 = 0;
return 0;
}
v11 = 0xC0000001;
//*a4 = v5 – pOs2Fea;
} else{
while (!(*(BYTE *)pOs2FeaBody & 0x7F))
{
v12 = pNtFea;
v5 = pOs2FeaBody;
pNtFea = SrvOs2FeaToNt(pNtFea, pOs2FeaBody);
pOs2FeaBody += *(BYTE *)(pOs2FeaBody + 1) + *(WORD *)(pOs2FeaBody + 2) + 5;
if (pOs2FeaBody > v14)
{
v10 = v12;
goto LABEL_13;
}
}
//*a4 = pOs2FeaBody – pOs2Fea;
v11 = 0xC000000D;
}
return v11;
}
return 0;
}

发表评论