今天简要介绍一下进程注入之Dirty Vanity基本实现原理及实现步骤,希望能有所启发。
介绍
DirtyVanity是2022年blackhat大会所提出的一种新型的进程注入方式,进程注入一般都会遵循特定的注入三步走:分配内存--写入内存--执行。各类杀毒软件在进程注入的监控方式一般是监控特定的api序列调用,一旦api序列调用构成进程注入的注入步骤,就可以怀疑存在进程注入行为。然而,总是会存在一些未被发现的api调用构成了进程注入三步走,这就导致传统检测方法针对已知api检测方式无法有效察觉此类注入攻击。例如DirtyVanity攻击。
原理解析
个人认为,当时DirtyVanity无法被检测到的原因有两个:一是其使用Windows的fork函数进行执行,该fork函数并未位于监控列表中,从而导致被绕过;二是Windows的fork函数将会创建本进程的副本,这便导致可疑内存页的分配写入,与执行是跨进程的,传统的检测方法没有办法,完美构建进程注入链。
由于DirtyVanity是一种进程注入方法,因此需要使用到Windows中能够远程fork继承的函数RtlCreateProcessReflection,其函数原型如下:
RtlCreateProcessReflection(
HANDLE ProcessHandle,
ULONG Flags,
PVOID StartRoutine,
PVOID StartContext,
HANDLE EventHandle,
T_RTLP_PROCESS_REFLECTION_REFLECTION_INFORMATION* ReflectionInformation
);
RtlCreateProcessReflection
将会创建ProcessHandle
所指向的进程的副本,他将执行以下操作:
-
1.创建共享内存段
-
2.使用参数填充共享内存段
-
3.将共享内存段同时映射到当前进程和目标进程
-
4.使用
RtlpCreateUserThreadEx
在目标进程中创建新线程 -
5.新线程执行
RtlCloneUserProcess
并传递共享内存段参数,RtlCloneUserProcess
将会fork本进程至新的进程。 -
6.如果
RtlCreateProcessReflection
指定了StartRoutine
,则将会执行StartRoutine
从上面的RtlCreateProcessReflection
的执行流程,我们得知,关键点在于将StartRoutine
指定为我们shellcode写入的位置。
基本步骤
-
通过
VirtualAllocEx
和WriteProcessMemory
或者NtCreateSection
和NtMapViewOfSection
等常规内存写入API将shellcode写入进程之中。 -
在目标进程,执行远程fork函数,将目标程序创建一个副本,该副本将会包含原始程序所有的内容,同时也会包含上一步写入的shellcode。
-
使用
RtlCreateProcessReflection
指向克隆的shellcode,并将进程起始地址设置为克隆的shellcode。 -
最后使用
RtlCloneUserProcess
,执行shellcode即可。
值得注意的是,由于我们使用一种类似于fork的方式创建进程,而在windows中并非所有内存页都会被传递给子进程,例如VirwUnmap
类型的节将不会进行传递,详见MSDN。因此,我们shellcode不能够使用此类api,但是我们可以通过定位ntdll中的api地址,手动进行执行一些命令:
-
使用
RtlInitUnicodeString
、RtlAllocateHeap
、RtlCreateProcessParametersEx
创建参数 -
调用
NtCreateUserProcess
创建进程 -
进程:
C:WindowsSystem32cmd.exe
-
命令参数:
/k msg * “Hello from Dirty Vanity”
源码解析
首先,需要拿到注入目标进程的进程句柄;这里使用OpenProcess
打开句柄,权限需要设置为PROCESS_VM_OPERATION | PROCESS_VM_WRITE | PROCESS_CREATE_THREAD | PROCESS_DUP_HANDLE
。
// open process handle
hProcess = OpenProcess(PROCESS_VM_OPERATION | PROCESS_VM_WRITE | PROCESS_CREATE_THREAD | PROCESS_DUP_HANDLE, FALSE, dwPid);
if (hProcess == nullptr)
{
std::cout << std::format("[-] Error using OpenProcess on PID {}: ERROR {}", dwPid, GetLastError()) << std::endl;
return FALSE;
}
然后,进程注入的一般步骤,分配内存,写入shellcode。
// alloc readwrite and execute page
PVOID lpAddr = NULL;
SIZE_T page_size = 4096;
lpAddr = ::VirtualAllocEx(hProcess, nullptr, page_size, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
if (lpAddr == NULL)
{
VirtualFreeEx(hProcess, lpAddr, page_size, MEM_DECOMMIT);
CloseHandle(hProcess);
return FALSE;
}
if (FALSE == ::WriteProcessMemory(hProcess, lpAddr, shellcode, sizeof shellcode, nullptr))
{
VirtualFreeEx(hProcess, lpAddr, page_size, MEM_DECOMMIT);
CloseHandle(hProcess);
return FALSE;
}
最后,也是Dirty Vanity的核心步骤,获取RtlCreateProcessReflection
未导出函数,创建目标进程的Reflection镜像并将结果存储到info中。
status = fnRtlCreateProcessReflection(hProcess, RTL_CLONE_PROCESS_FLAGS_INHERIT_HANDLES | RTL_CLONE_PROCESS_FLAGS_NO_SYNCHRONIZE, lpAddr, nullptr, NULL, &info);
参考链接
Dirty Vanity: A New Approach to Code Injection & EDR Bypass
原文始发于微信公众号(Bits):进程注入之Dirty Vanity
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论