Malware development: APC Injection with C++
·6 mins
Table of Contents
Introduction #
- In our previous blog post, we looked into classic process injection, going into the various techniques such as finding target processes to inject to using Windows APIs, as well as injecting into the said processes.
- There are several methods to perform process injection, we will dive into APC Injection, a more advanced technique, that offers more advantages to the standard method.
Why? #
- This method is harder to detect than the standard process injection. Despite implementing some common APIs used in malware development such as
VirtualAllocEx, WriteProcessMemory and OpenProcess
, the major difference is in how shellcode is executed. - Traditional process injection executes shellcode using
CreateRemoteThread
. This API is overtly suspicious and will get flagged by the AV. APC injection uses an API calledQueueUserAPC
, which is less suspicious since it is used in normal OS operations such as scheduling work for a thread when it becomes idle. -Let’s dive into what some of these technical terms mean.
Program Execution in Modern Operating Systems #
- When programs are executed in Windows, the operating system allocates necessary resources to the program to start the execution.
- During the execution, multiple threads are usually assigned to a program. A
thread
in this case represents a sequence of instructions in the program that can be scheduled by the OS to run. These threads could be performing tasks such as accessing OS resources. - If a program needs to perform I/O operations such as reading data from files, it uses
synchronous calls
, which halts the execution of the thread to allow the I/O operation to take place. - To address this inefficiency issue, modern Operating Systems will provide support for
asynchronous calls
. This allows the thread to continue execution after handing over the I/O operation to the OS.
Asynchronous Procedure Calls #
- When an asynchronous I/O operation is completed, the operating system can
queue an APC associated with that I/O operation
. - The APC can contain some code or a function that is
executed in response to the completion of the I/O event
. This requires a thread to be in analertable state
, which is when a thread is idle and ready to receive Asynchronous Procedure Calls. This allows the OS to deliver the APC to the thread hence executing the code. - In our case, we will be creating an APC routine that points to our shellcode so that when the APC fires, our shellcode executes!
QueueUserAPC() #
- This Windows API allows an application to queue an APC to a specified thread. Its implementation is as follows:
pfnAPC
: This is a pointer to the function that you want to be executed asynchronously. This function will be invoked when the thread is in astate where it can process APCs
(also known as analertable state
).hThread
: This is ahandle
to the thread to which you want to queue the APC. A handle can be thought of as a unique identifier used to interact with a resource, in our case this will be a specific thread.dwData
: This is the data that you want topass to the APC function
. It’s a single number (an integer, technically aULONG_PTR
) that can be used for whatever you want. It is up to you to decide what you want for this value (error code, status code, commands)
Implementing the QueueUserAPC() API #
- I will demonstrate a simple example of how we queue an APC in C++. We will create a thread and execute it. After its execution, we will then queue our APC and see how it gets executed.
- Starting with an empty C++ project. Import the Windows.h header and create a simple thread as shown.
ThreadProc
is a callback function that will be executed by CreateThread() as a thread.
- We can use print statements to show that the thread is being executed.
- We use
wprintf
to print out our messages. It is similar toprintf
but is used to print wide-character strings. It takes in wide character literals indicated by the prefixL
GetLastError
is used to grab the last error code value
When
CreateThread
is called, it may return before ThreadProc finishes executing. That is why we use the sleep function
inside the main() function to allow ThreadProc to finish executing.- We finally queue in our APC using the
QueueUserAPC
function. Remember that we mentioned we can only queue in threads that have been put in analertable state
. So how do you do this?
- msdn docs describes this as shown in the screenshot below.
- TLDR; We can use the
SleepEx
function to make our threadalertable before queueing in the APC
. We adjust our code by adding the API and a callback function that will be queued in after the thread has been executed. - The callback function has a parameter called
Parameter
, which contains the data that is passed in thedwData
parameter in the QueueUserAPC() function, which was123
. We can print it out as well to confirm that as shown in line 22
- It executes as shown:
Note that SleepEx returned 192. In the msdn docs, the return value of sleepex is WAIT_IO_COMPLETION if the callback is completed. WAIT_IO_COMPLETION is a return code whose value is 0xC0 which is 192 in decimal
Implementing QueueUserAPC() in our implant. #
High-Level Overview #
- We now have some understanding of how QueueUserAPC works. A high-level breakdown of how the implementation works is as follows.
- Create the target process in a suspended state.
- Allocate memory using
VirtualAllocEx
in the suspended process. - Define the
APC callback routine
, it is going to point to our shellcode - Write shellcode into the allocated memory within the target process using
WriteProcessMemory
- Queue the APC to the main thread using
QueueUserAPC
. - Once the thread is resumed the shellcode is executed.
Key Points
- In line 38, we define our APC routine using
PTHREAD_START_ROUTINE
which declares the APC callback as a pointer to a variable. In this case, our variable would be theshellcode
defined in line 6. - In lines 45 & 46 we define 2 structures necessary in implementation of the
CreateProcessA
API. We use these structures to access information about the process that we are creating. You can see this in line 52 where we obtain the process id and thread id of notepad.
Final demo #
- Using processhacker, we can further verify our process spawned in the context of notepad as shown.
Conclusion #
- I hope you enjoyed diving deep into how this technique works. You can play around with the code and see if you can implement some form of encryption for the shellcode. Happy hacking !!
Project Files #
- You can find the project files for process injection here