频道栏目
首页 > 安全 > 网络安全 > 正文

MS 17-010:NSA Eternalblue SMB 漏洞分析

2017-04-19 10:29:49      个评论      
收藏   我要投稿

MS 17-010:NSA Eternalblue SMB 漏洞分析。

环境

EXPLOIT:

Eternalblue-2.2.0.exe

TARGET:

win7 sp1 32bits

srv.sys 6.1.7601.17514

srvnet.sys 6.1.7601.17514

PATCH:

MS17-010

漏洞原理

srv.sys在处理SrvOs2FeaListSizeToNt的时候逻辑不正确导致越界拷贝。我们首先看下漏洞的触发点:

unsigned int __fastcall SrvOs2FeaToNt(int a1, int a2)

{

int v4; // edi@1

_BYTE *v5; // edi@1

unsigned int result; // eax@1

v4 = a1 + 8;

*(_BYTE *)(a1 + 4) = *(_BYTE *)a2;

*(_BYTE *)(a1 + 5) = *(_BYTE *)(a2 + 1);

*(_WORD *)(a1 + 6) = *(_WORD *)(a2 + 2);

_memmove((void *)(a1 + 8), (const void *)(a2 + 4), *(_BYTE *)(a2 + 1));

v5 = (_BYTE *)(*(_BYTE *)(a1 + 5) + v4);

*v5++ = 0;

_memmove(v5, (const void *)(a2 + 5 + *(_BYTE *)(a1 + 5)), *(_WORD *)(a1 + 6)); //这里产生的越界覆盖

result = (unsigned int)&v5[*(_WORD *)(a1 + 6) + 3] & 0xFFFFFFFC;

*(_DWORD *)a1 = result - a1;

return result;

}

发生越界的地方见上面第二个memmove。调试的时候可以这样下断点:

kd> u srv!SrvOs2FeaToNt+0x4d

srv!SrvOs2FeaToNt+0x4d:

9877b278 ff15e0a07698 call dword ptr [srv!_imp__memmove (9876a0e0)]

9877b27e 0fb74606 movzx eax,word ptr [esi+6]

9877b282 8d441803 lea eax,[eax+ebx+3]

9877b286 83e0fc and eax,0FFFFFFFCh

9877b289 83c418 add esp,18h

9877b28c 8bc8 mov ecx,eax

9877b28e 2bce sub ecx,esi

9877b290 5f pop edi

//最后一次越界的拷贝的长度是0xa8

ba e1 srv!SrvOs2FeaToNt+0x4d ".if(poi(esp+8) != a8){gc} .else {}"

这么设断点的原因是最后一次越界的拷贝的长度是0xa8,断下来后可以发现:

kd> dd esp

99803b38 88c8dff9 a3fc203a 000000a8 88c8dff8

99803b48 a3fc2039 00000000 a3fb20d8 a3fc2035

99803b58 a3fd2030 99803b7c 9877b603 88c8dff0

99803b68 a3fc2035 88307360 a3fb20b4 a3fb2008

99803b78 a3fc2035 99803bb4 98794602 88c8dff0

99803b88 99803bbc 99803ba8 99803bac 88307360

99803b98 a3fb2008 00000002 a3fb20b4 a3fb20d8

99803ba8 00010fe8 00000000 00000000 99803c00

kd> !pool 88c8dff9

Pool page 88c8dff9 region is Nonpaged pool

*88c7d000 : large page allocation, tag is LSdb, size is 0x11000 bytes

Pooltag LSdb : SMB1 data buffer, Binary : srv.sys

kd> !pool 88c8e009

Pool page 88c8e009 region is Nonpaged pool

88c8e000 size: 8 previous size: 0 (Free) ....

88c8e008 doesn't look like a valid small pool allocation, checking to see

if the entire page is actually part of a large page allocation...

*88c8e000 : large page allocation, tag is LSbf, size is 0x11000 bytes

Pooltag LSbf : SMB1 buffer descriptor or srvnet allocation, Binary : srvnet.sys

kd> ? 88c7d000 +11000

Evaluate expression: -2000101376 = 88c8e000

kd> ? 88c8dff9 +a8

Evaluate expression: -2000101215 = 88c8e0a1 //这里明显越界了。

我们可以从上面的调试记录看到明显的越写拷贝操作。可以看到被覆盖的是SMB1的buffer是有srvnet.sys分配的。这里exploit精心布局好的,是通过pool喷射的将两个pool连接在一起的。覆盖后面的这个pool有啥用后面会提到。

有同学会说”这只是现象,漏洞真正的成因在哪里呢?”。往下看:

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=%d\n", 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;

}

看到我加了个__debugbreak的地方,断在那里就说明溢出了

1.bin的内容最后我会给大家带上。

大家也可以自己抓1.bin的内容,方法如下:

kd> u SrvOs2FeaListToNt

srv!SrvOs2FeaListToNt:

9877b565 8bff mov edi,edi

9877b567 55 push ebp

9877b568 8bec mov ebp,esp

9877b56a 51 push ecx

9877b56b 8365fc00 and dword ptr [ebp-4],0

9877b56f 56 push esi

9877b570 57 push edi

9877b571 8b7d08 mov edi,dword ptr [ebp+8]

9877b574 57 push edi

9877b575 e82effffff call srv!SrvOs2FeaListSizeToNt (9877b4a8)

kd> ba e1 9877b575

kd> g

Breakpoint 0 hit

srv!SrvOs2FeaListToNt+0x10:

9877b575 e82effffff call srv!SrvOs2FeaListSizeToNt (9877b4a8)

kd> !pool edi

Pool page a3fd10d8 region is Paged pool

*a3fd1000 : large page allocation, tag is LStr, size is 0x11000 bytes

Pooltag LStr : SMB1 transaction, Binary : srv.sys

kd> .writemem 1.bin a3fd10d8 l0x11000-d8

上一篇:MySQL曝中间人攻击Riddle漏洞,可致用户名密码泄露
下一篇:OFFICE OLE2LINK(CVE-2017-0199)漏洞利用详解
相关文章
图文推荐

关于我们 | 联系我们 | 广告服务 | 投资合作 | 版权申明 | 在线帮助 | 网站地图 | 作品发布 | Vip技术培训 | 举报中心

版权所有: 红黑联盟--致力于做实用的IT技术学习网站