zl程序教程

您现在的位置是:首页 >  后端

当前栏目

映射dll到r0的高维注入

2023-09-11 14:13:58 时间

背景

之前写过一篇把r3代码放r0并且跑起来的例子,当时win10会出现1a错误,原因是我没关KPTI,导致页面异常,最近又看到了类似的注入,于是写了点代码映射dll在r0,并注入到目标进程,代码:GitHub

相关知识介绍

首先呢,大家都知道不同进程之间的虚拟地址是分开的,你改你的地址数据不会影响另外的进程。但是对于系统dll而已,不会经常修改,所以一般情况下只会映射一份,系统的ntdll和kernel32这类的dll的物理内存都是同一份。顺便说一下什么是COW,CORY-ON-WRITE写时拷贝,当用户尝试对ntdll或者kernel32的函数进行修改的时候,会拷贝到另外一块物理内存进行修改,这里分别附加到不同进程,观察他们的物理地址。不同进程之间的Kernel32!TlsGetValue对应的pte是相同的一份,但是没有写权限只有读和执行,如果修改会重新映射一个pte。

 好了了解完这个之后,我们怎么突破这个限制了,让每一个进程的TlsGetValue都被修改。

1、自己把TlsGetValue的物理页挂到其他地址去,并且修改它的页属性

2、使用windows的mdl函数

这里选择第二种,因为自己挂比较麻烦,而且页不安全,稍微写成就容易蓝,mdl会把对应的pte映射到内核的虚拟地址,然后就可以进行修改,强调一下mdl需要try起来。

NTSTATUS MapSC(PUCHAR shellcode, PVOID address, ULONG len)
{
	BOOLEAN isLock = FALSE;
	NTSTATUS status = STATUS_UNSUCCESSFUL;

	PMDL mdl = IoAllocateMdl(address, PAGE_SIZE, FALSE, FALSE, NULL);
	__try
	{
		MmProbeAndLockPages(mdl, UserMode, IoReadAccess);
		isLock = TRUE;

		PVOID virtualAddress = MmMapLockedPagesSpecifyCache(mdl, KernelMode, MmCached, NULL, FALSE, NormalPagePriority);
		if (virtualAddress)
		{
			memcpy(virtualAddress, shellcode, len);
			status = STATUS_SUCCESS;
			MmUnmapLockedPages(virtualAddress, mdl);
			if (isLock)
			{
				MmUnlockPages(mdl);
			}
			IoFreeMdl(mdl);
			mdl = NULL;
		}
	}
	__except (EXCEPTION_EXECUTE_HANDLER)
	{
		if (isLock)
		{
			MmUnlockPages(mdl);
		}
		IoFreeMdl(mdl);
		mdl = NULL;
	}
	return status;
}

接着现在能够修改所有进程的TlsGetValue,然后让他跳到我们内核申请的地址

					UCHAR checkPid[] =
					{
					  0x65, 0x48, 0x8B, 0x04, 0x25, 0x30, 0x00, 0x00, 0x00,        // mov rax, gs:[0x30]
					  0x8B, 0x40, 0x40,                                            // mov eax,[rax+0x40] ; pid
					  0x3D, 0x00, 0x00, 0x00, 0x00,                                // cmp eax, TargetPid
					  0x0F, 0x85, 0x00, 0x00, 0x00, 0x00,                          // jne 0xAABBCC
					  0x48, 0xB8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // mov rax, KernelMemory
					  0xFF, 0xE0                                                   // jmp rax
					};

但是正常人都知道用户层无法执行内核代码,其实也很简单,只需要 给对应的页面加上用户层可访问就行了,这里或上4就是对user置位,最新代码简单处理了2M和1GB的大页。

VOID Modify2UserMem()
{
	if (g_shellCode && g_allocateSize)
	{
		PULONG64 PteBase = (PULONG64)GetPTEBase();
		if (PteBase)
		{
			for (ULONG i = 0; i < g_allocateSize / PAGE_SIZE; i++)
			{
				PULONG64 Pte = (PULONG64)MiGetXXXAddress((ULONG64)g_shellCode + (ULONG64)i * PAGE_SIZE, PteBase);
				PULONG64 Pde = (PULONG64)MiGetXXXAddress((ULONG64)Pte, PteBase);
				PULONG64 Ppe = (PULONG64)MiGetXXXAddress((ULONG64)Pde, PteBase);
				PULONG64 Pxe = (PULONG64)MiGetXXXAddress((ULONG64)Ppe, PteBase);
				if (MmIsAddressValid(Pte) && MmIsAddressValid(Pde) && MmIsAddressValid(Ppe) && MmIsAddressValid(Pxe))
				{
					*Pte |= 4;
					*Pde |= 4;
					*Ppe |= 4;
					*Pxe |= 4;
				}
			}
		}
	}
}

好了,代码写好了测试一下,发现1a蓝屏,其实是因为KPTI,简单说一下怎么判断是否开启KPTI

 cr4的第17位表示是否支持pci,当KPTI开启时,这个位会被置位。

关闭方式,管理员

reg add "HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Memory Management" /v FeatureSettingsOverride /t REG_DWORD /d 3 /f
reg add "HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Memory Management" /v FeatureSettingsOverrideMask /t REG_DWORD /d 3 /f

下面是没有开启的,cr4第17位为0,并且用户层地址的pxe ppe pde pte都是有执行权限的

 开启了的,pxe没有执行权限就不会继续往下了,并且cr4的第17位是1

 最后,映射dll到内存,修改页属性,指向oep,最后修复cow hook就完成了。

当然这里映射还有一个坑

正常的修复IAT是需要dll的导出函数,但是像kernel32这种是有坑的,这个前面带a的,不是自己实现,指向的是字符串

	if ((pImageThunkData->u1.Ordinal & IMAGE_ORDINAL_FLAG64) == 0)
				{
					PIMAGE_IMPORT_BY_NAME pImageImportName = (PIMAGE_IMPORT_BY_NAME)(pImageThunkData->u1.AddressOfData + (PUCHAR)g_shellCode);
					PVOID func = GetModuleExport(pModuleBase, pImageImportName->Name);
					if (func && func < pModuleBase)
					{
						if (memcmp((PUCHAR)pModuleBase + (ULONG64)func, "NTDLL", strlen("NTDLL")) == 0)//NTDLL.RtlEncodePointer
						{
							UNICODE_STRING Kernel32String = RTL_CONSTANT_STRING(L"Ntdll.dll");
							PVOID NtdllAddress = GetUserModule(EProcess, &Kernel32String, FALSE);
							func = GetModuleExport(NtdllAddress, (PCCHAR)pModuleBase + (ULONG64)func + strlen("NTDLL."));
						}
					}

					if (func)
						pImageThunkData->u1.Function = (ULONG64)func;
				}
				pImageThunkData++;

还有编译的时候选择mt不要整md,md编译出来的IAT长这样,拿什么去给你修复这个dll的IAT

 这里dll的起始不是oep而是第一个导出函数。

在编译的时候全选release,不要整debug,如果你选debug导致进程崩溃或者蓝屏的问题需要你自己去修复bug了,代码只是一个Demo,出问题自己改。