The purpose of this lab is to bypass KASLR and find the Virtual Address(VA) of Ring 0 module symbol.
Virtual Address of Symbol X in Ring 0
Now that we have derived arbitrary Ring 0 virtual memory read and write primitives, we need to find the VA(now randomized thanks to KASLR) of the Kernel-Mode module symbol that we want to read or modify. This is required for both:
1. Ring-0 code execution
2. Data/Data Corruption attacks
A symbol can be categorized into primarily two types:
1. Exported symbol
2. Non-exported symbol
The generic algorithm for finding the VA of a Ring 0 module symbol is:
Load the target module in the local process and retrieve the User-Mode base address of the module. This can be achieved using a Win32API function known as LoadLibraryExA with the flag DONT_RESOLVE_DLL_REFERENCES that tells the LDR subsystem to not call DllMain after load and also instructs the loader to not process the image's IAT and load dependent modules(as the name suggests).
Get the offset to the symbol from the start of the module. Remember that an offset is just a fixed distance between two addresses. In other words, the symbol offset will remain the same on a per-patch/per-build basis even though the base address should be randomized on a per-load basis thanks to ASLR.
Note that this is the step where the variable part of the algorithm lies. In other words, the method for obtaining the symbol offset varies on whether the symbol is exported or not(more on this later).
Leak the base Ring 0 VA of the kernel module using one of the many available information leak vulnerabilities to defeat KASLR. Some examples are using Kernel32!EnumDeviceDrivers, Ntdll!NtQuerySystemInformation etc.
KASLR is designed to prevent predicting addresses of some desired kernel memory by randomizing the base address of the kernel image, kernel modules and device drivers on a per-boot basis. It was first introduced in Windows Vista.
In this lab, we are going to be utilizing NtQuerySystemInformation with SystemModuleInformation class to obtain the base address of the Ring 0 module.
Note that we need to be in a Medium-IL context at least to be able to use this API to leak the module base address.
Remember that KASLR is only meant to protect against REMOTE kernel exploits and not LOCAL exploits. This is because it is trivial to retrieve the base address of a kernel module from a Medium-IL/High-IL process as compared to a Low-IL/sandboxed process ergo this cannot be termed as a "true" KASLR bypass.
In the final step, we can simply add the symbol offset to the module base VA that we retrieved in the previous step to get the symbol VA.
Here is a visualization of the whole process that might help make this clearer:
Overview of finding kernel module symbol VA
Finding VA of Symbol X in Ring 0
Now that we have discussed the generic algorithm for finding the VA of a kernel module symbol, let's look at the more specific picture both for when the symbol is exported and when it's not.
Here is the command in WinDbg for finding the VA of any kernel module symbol:
kd> x <modulename!symbolname>
Finding kernel module symbol VA with WinDbg
Note that similar results may also be achieved using another WinDbg command called .formats. Also, note that in case any of these commands are unsuccessful, please try reloading the symbols from Microsoft's symbol server using .reload command.
In case the symbol is exported, finding the symbol offset becomes quite easy for us as we can use a Win32API function named GetProcAddress to retrieve the User-Mode address of the function and then subtract the User-Mode base address of the module in the local process to get the symbol offset.
In case the symbol is non-exported, there are predominantly two routes that we may take to find the VA:
1. Hardcoding symbol offset
2. Pattern searching
The former option may seem easy to implement but it usually comes at the cost of increased maintenance efforts and low scalability among other issues. The reason being that offsets may change frequently from build to build and sometimes even with a software patch released by Microsoft as a fix for a vulnerability.
To overcome the above shortcomings, we are primarily interested in the latter approach i.e. instead of hardcoding the symbol offset, we embed a unique signature that we want to scan for in-memory within a given search range using a Ring 0 PM/VM read primitive and find the symbol offset dynamically. The sole advantage of this approach is based on the fact that we can embed a unique pattern at address that is less susceptible to changes between builds/patches and leverage it to find the offset at runtime.
To find a unique signature, first, we need to disassemble the target routine or dump memory around target VA. In WinDbg this can be achieved using:
kd> u <modulename!symbolname>
kd> dps <modulename!symbolname>
After dumping memory, now we need to select an appropriate memory pattern as our target signature. It should be long enough so as to be unique.
Finally, we can check if the signature is unique in our search range using:
kd> s -b <modulename> L0X1000000 <signature>
Here is a WinDbg screenshot that might help visualize the above procedure:
Finding target signature with WinDbg
Beware of embedding patterns consisting of instructions that have a symbol in them!
Now that we have a basic understanding of the process of finding Ring 0 module symbol VA, let's implement it.
The below piece of code assumes that we have already established an arbitrary physical read by exploiting a vulnerability.