MS17-010 “永恒之藍”漏洞分析與復現
1、 漏洞簡述
漏洞名稱:“永恒之藍”漏洞
漏洞編號:MS17-010,CVE-2017-0143/0144/0145/0146/0147/0148
漏洞類型:緩沖區溢出漏洞
漏洞影響:信息泄露
CVSS評分:9.3(High)
利用難度:Medium
基礎權限:不需要
2、組件概述
SMB(Server Message Block)是一個協議名,它能被用于Web連接和客戶端與服務器之間的信息溝通。其目的是將DOS操作系統中的本地文件接口“中斷13”改造為網絡文件系統。
SMB1.0協議由于在文件共享傳輸過程中存在的傳輸效率低以及傳輸空間小等缺陷被人們所摒棄。為了更好的實現網絡中文件的共享過程,在SMB1.0的基礎上開發了新的網絡文件傳輸協議,并將其命名為SMB2.0。
該協議在實現了文件共享傳輸的基本功能的基礎上對文件傳輸的效率、文件緩存的空間以及文件并發傳輸等問題進行改進,使得在局域網或更高配置的網絡環境下,文件傳輸過程的速度和效率等得到了很大的提升。
3、 漏洞影響
Windows Vista SP2; Windows Server 2008 SP2 and R2 SP1; Windows 7 SP1; Windows 8.1; Windows Server 2012 Gold and R2; Windows RT 8.1; and Windows 10 Gold, 1511, and 1607; and Windows Server 2016
以上系統打開445號端口都容易受到影響。
4、解決方案
禁用 SMBv1
對于客戶端操作系統:
- 打開“控制面板”,單擊“程序”,然后單擊“打開或關閉 Windows 功能”。
- 在 Windows 功能窗口中,清除SMB1.0/CIFS 文件共享支持復選框,然后單擊確定關閉窗口。
- 重新啟動系統。
對于服務器操作系統:
- 打開服務器管理器,然后單擊管理菜單并選擇刪除角色和功能。
- 在功能窗口中,清除SMB1.0/CIFS 文件共享支持復選框,然后單擊確定關閉窗口。
- 重新啟動系統。
更新Windows系統補丁:官方文檔鏈接
漏洞復現
1、環境搭建
實驗中需要用到三個機器,分別是調試機,靶機(被調試機),攻擊機。
調試機環境:主機Windows 10x64專業版 1909
靶機(被調試機)環境:Windows 7x86 SP1
靶機(被調試機)配置:192.168.44.132:445
攻擊機環境:Windows XPx86 SP3
攻擊機配置:192.168.44.152
(1)雙機內核調試
首先需要用調試機調試靶機,具體實現雙機調試請看這里。
(2)安裝并配置fuzzbunch
另外需要在攻擊機安裝fuzzbunch實現永恒之藍漏洞:
- 安裝Python 2.6
- 安裝PyWin32,需要以管理員權限進行安裝
- 下載fuzzbunch
- 在shadowbroker-master的子目錄windows中新建listeningposts文件夾,同時修改FuzzyBunch.xml文件內容,設置相應的ResourcesDir和LogDir參數值,修改結果如下圖所示。(路徑根據實際情況而定)

(3)靶機環境配置
- 打開靶機445端口,具體打開方法點這里
- 關閉防火墻:控制面板-系統和安全-Windows防火墻-打開或關閉Windows防火墻-全部選擇關閉防火墻。
3、復現過程
在攻擊機中啟動命令行,進入fuzzbench的Windows文件夾,用python啟動fuzzbench:

啟動后設置靶機IP-設置擊機IP-重定向no-log地址確認(無誤直接回車)-0+回車(創建新的項目)-為項目命名-設置路徑(Yes)-:

使用永恒之藍建立后門:
use Eternalblue
之后一直回車到出現設置靶機系統版本,本次復現靶機為Win7,所以選擇1:


下一項模式選擇1,FB模式:

接下來一直回車即可,即可看到插件運行,并成功利用永恒之藍漏洞在靶機系統中留下后門:

漏洞分析
1、基本信息
漏洞文件:srv.sys
漏洞函數:srv!SrvOs2FeaListToNt
漏洞對象:為NtFeaList分配的緩沖區
2、 背景知識
(1) SrvOs2FeaListToNt()函數
SrvOs2FeaListToNt()函數用于將FEA list轉化為NTFEA list,需要分配在內核地址大非分頁池分配緩沖區來存放轉化后的NTFEA list,因此需要先計算轉化后的NTFEA list的大小,計算大小是通過srv!SrvOs2FeaListSizeToNt()函數進行的,這個函數被SrvOs2FeaListToNt()調用。
(2)0xffdff000系統預留地址空間
0xffdff000處地址是系統預留的地址空間,用于存放時鐘,版本,配置等信息,其分配的權限為可執行:
kd> !pte 0xffdff000
VA ffdff000
PDE at C0603FF0 PTE at C07FEFF8
contains 000000000018A063 contains 00000000001E3163
pfn 18a ---DA--KWEV pfn 1e3 -G-DA--KWEV
3、 詳細分析
(1)靜態分析、Ida分析
使用Ida打開srv.sys
首先按shift + F1,再按insert鍵導入兩個結構:
struct _FEA{
BYTE fEA; //標記位
BYTE cbNAME; //記錄名稱長度
USHORT cbValue; //記錄值的長度
}FEA;
struct _FEALIST{
ULONG cbList; //記錄Fea List總長度
_FEA list[1];
}FEALIST, *PFEALIST;
查看srv!SrvOs2FeaListSizeToNt()函數源碼,右鍵a1,使用_FEALIST結構覆蓋:
int __stdcall SrvOs2FeaListSizeToNt(_FEALIST *FeaList)
{
_FEALIST *v1; // eax
char *v2; // edi
_FEA *v3; // esi
int v4; // ebx
int v6; // [esp+Ch] [ebp-4h]
v1 = FeaList; // a1
v6 = 0; // NtFeaList的大小
v2 = (char *)FeaList + FeaList->cbList; // 獲取指向表結尾的指針v2
v3 = FeaList->list; // 指向表的開始
if ( FeaList->list < (_FEA *)v2 ) // 表的開始指針應該在結尾指針之前
{
while ( &v3[1] < (_FEA *)v2 ) // 從FeaList表的第一個元素開始,遍歷整個表
{
v4 = v3->cbValue + v3->cbNAME; // 獲取當前FEA的長度
if ( &v3[1].cbNAME + v4 > (BYTE *)v2 ) // 檢查下一個FEA是否有效
break;
if ( RtlSizeTAdd(v6, (v4 + 12) & 0xFFFFFFFC, &v6) < 0 )// 增加NtFeaList的大小,每次12字節
return 0;
v3 = (_FEA *)((char *)v3 + v4 + 5); // 下一個FEA,加5意思是每個FEA后面有5字節的NULL
if ( v3 >= (_FEA *)v2 )
return v6;
v1 = FeaList; // 重置v1
}
LOWORD(v1->cbList) = (_WORD)v3 - (_WORD)v1; //此處發生計算錯誤
}
return v6;
}
LOWORD(v1->cbList) = (_WORD)v3 - (_WORD)v1; 這句代碼中存在一處錯誤,cbList長度本為4字節,這里卻被強制轉換為2字節,導致賦值過程中無視高2字節的內容,只賦值了低2字節的內容。
(2)補丁Diff
原來的SrvOs2FeaListSizeToNt()函數中的C代碼:
LOWORD(v1->cbList) = (_WORD)v3 - (_WORD)v1;
修補后:
*(DWORD*)(v1->cbList) = v3 - (_DWORD)v1;
(v1->cbList)數據類型由WORD變為了DWORD
2、動態分析
將Windbg(需要配置好符號文件)連接靶機進行內核調試,連接成功Windbg會顯示:

運行前先設置幾個斷點來采集運行過程中涉及的關鍵數據:
bp srv!SrvSmbOpen2+0x79 ".printf \"feasize: %p indatasize: %p fealist addr: %p\\",edx,ecx,eax;g;" // 獲取Fea大小和indata大小 bp srv!SrvOs2FeaListToNt+0x10 ".printf \"feasize before: %p\\",poi(edi);r $t0 = @edi;g;" bp srv!SrvOs2FeaListToNt+0x15 ".printf \"NTFEA size: %p feasize after: %p\\",eax,poi(@$t0);g;" // FEA List大小的前后變化,以及NTFEA List大小 bp srv!SrvOs2FeaListToNt+0x99 ".printf \"NEXT: FEA: %p NTFEA: %p\\",esi,eax;g;" bp srv!SrvOs2FeaToNt+04d ".printf \"MOV2: dst: %p src: %p size: %p\\",ebx,eax,poi(esp+8);g;" // 查看被分配的池 bp srv!SrvOs2FeaListToNt+0xd5 // 查看服務器返回的值
設置好斷點后,在攻擊機運行腳本,開始攻擊。這時,Windbg會記錄下攻擊過程中的數據:

整理一下:
feasize: 00010000 indatasize: 000103d0 fealist addr: a1f050d8 feasize before: 00010000 NTFEA size: 00010fe8 feasize after: 0001ff5d
可以看到經過某處指令后feasize由00010000變為0001ff5d,這就是代碼LOWORD(v1->cbList) = (_WORD)v3 - (_WORD)v1;產生的錯誤計算,其原因請看下面對應的匯編指令:
此時esi和eax寄存器的值:
FeaEnd: esi = a4217035h
FeaStart: eax = a42070d8h
FeaSize: [eax] = 00010000
sub esi, eax mov word ptr[eax], si
經過sub指令后esi的值為ff5d,而在給[eax]賦值的時候使用WORD類型,導致了eax內容變成了0001ff5d。
繼續查看內存池的數據:
NEXT: FEA: a1f050dc NTFEA: 85da4008 MOV2: dst: 85da4011 src: a1f050e1 size: 00000000 NEXT: FEA: a1f050e1 NTFEA: 85da4014 MOV2: dst: 85da401d src: a1f050e6 size: 00000000 ...... NEXT: FEA: a1f05ca3 NTFEA: 85da5c4c MOV2: dst: 85da5c55 src: a1f05ca8 size: 00000000 NEXT: FEA: a1f05ca8 NTFEA: 85da5c58 MOV2: dst: 85da5c61 src: a1f05cad size: 00000000 NEXT: FEA: a1f05cad NTFEA: 85da5c64 MOV2: dst: 85da5c6d src: a1f05cb2 size: 0000f383 NEXT: FEA: a1f15035 NTFEA: 85db4ff0 MOV2: dst: 85db4ff9 src: a1f1503a size: 000000a8 srv!SrvOs2FeaListToNt+0xd5: 8949263a be0d0000c0 mov esi,0C000000Dh
分配的起始地址為85da4008,正常分配的結束地址為85da4008h + 00010fe8h = 85db4ff0h,到了Windbg中顯示的最后一塊池時,發生了錯誤,并返回STATUS_INVALID_PARAMETER(0xC000000D),查看85db4ff0h中的內容后發現這片地址屬于srvnet.sys分配的池。
而這個池中存在一個很關鍵的數據,這個數據是一個地址,當靶機系統再接受數據的時候會將數據復制到這個地址+0x80的地方。
這個關鍵的地址位于_MDL結構中的MappedSystemVa成員,這個結構在srvnet池分配開始的地址+3c,我們在Windbg中找到這個結構:
kd> dt 85db503C _MDL nt!_MDL +0x000 Next : 0xffffffff _MDL +0x004 Size : 0n96 +0x006 MdlFlags : 0n4100 +0x008 Process : (null) +0x00c MappedSystemVa : 0xffdfef80 Void +0x010 StartVa : (null) +0x014 ByteCount : 0xffd00010 +0x018 ByteOffset : 0xffffffff
可以看到此時的這個結構的MappedSystemVa成員已經被覆蓋為0xffdfef80,當靶機再次接受數據時,會向0xffdf80+0x80也就是在剛才介紹中提到的系統預留的一片可執行空間0xffdff000,此時攻擊機獲得一次寫入shellcode的機會。那么程序是如何執行這部分shellcode的呢?
當靶機接受完這部分數據后,還會調用srvnet!SrvNetWskReceiveComplete這個函數,巧了,剛好這個函數會調用到寫入的shellcode,就此,成功劫持了靶機的執行流。
查看0xffdff000地址空間內容如圖所示:

0xffdff1f1處為shellcode的開始。
緩解措施
1、關閉445端口
2、更新補丁