HackSys Extreme Vulnerable Driver — Arbitrary Write NULL (New Solution)
A simply (not stealth) method utilizing “NtQuerySystemInformation” for “Arbitrary Write NULL” vulnerabilities
Today, we’re going to have a deep looking through an interesting exploitation technique using “NtQuerySystemInformation” system call in order to achieve a “LPE - (Local Privilege Escalation)” through “HEVD - (Hacksys Extreme Vulnerable Driver)”. The follow content will only show about possibles and functional (but unreliable) techniques and methodologies on how to exploit this common vulnerability type “(Arbitrary Write NULL)” in most of vulnerable drivers (in case you doesn’t have certain tools in order to exploit them). Also, we’ll not cover how to install and configure kernel debugging or “HEVD IOCLT” communication, this write-up is about what i did and how i worked around to get a solution, despite knowledge of other people influenced me and finally the results of final script. Hope Everyone enjoy! =)
First of all, we need to talk about what is “Arbitrary Write NULL” vulnerability, and at kernel perspective, what should be possible to do with it in order to achieve “LPE” in our simple “cmd.exe” session from “ring3 (user-land)”.
In short, “Arbitrary Write NULL” is most like “Arbitrary Write” vulnerabilities, the difference behind them, is that the first one allows you to be able to “write->[0x00000000]” in whatever address/pointer you looking for, and the second one, allows you to define explicitly “Write-What-Where”, allowing things like “write->[0xdeadbeef]”, meaning that you have control over value of an address/pointer which will be overflowed with “0xdeadbeef”, instead only “0x00000000”. At below we are going to have a looking deep in to HEVD driver vulnerable function, dissect and understand what is happening.
As you can see here, “ifdef SECURE” (which is not), “probeForWrite()” function should verify and confirm that our user input buffer is located at “ring3”, otherwise our input buffer with be nullified without properly security checks.
As commented, “[edi]” register is been overflowed with “0x00000000” by “[ebx]” register as occurs when compiled code wasn’t defined with “#ifdef SECURE” bit set.
Since IOCTL drive connection is predefined, we can test it and see that our first 4 bytes from user-buffer “(shellcode_ptr)”, is about to be nullified with “0x00000000”.
After script run, and hit break-point, we can clearly notice that “[edi]” value contains our address to the pointer of user-buffer address “(shellcode_ptr -> 0x00500000)”.
As an example, the content of our “shellcode” is storing a piece of “x86 assembly” code to “LPE” our permissions. At your first “4 bytes”, you can see that have the initial parts of our “shellcode” start. Now ignoring the code located there, we’re only looking into “0xa16460cc” address.
The problem here is obvious, as a simple user “(ring3)”, if we send a whatever address, it will be nullified, no matter what or how, just it will stay “NULL”.
Said all that, we know from here that we actually only can write “NULL” bytes to our defined address/pointer, and do some magic to achieve LPE from there, but… how can we do that? Let’s talk about DACL & Security Description.
DACL & Security Description
First of all, what is “DACL” and “Security Description”? how them can be exploited using “Arbitrary Write NULL” vulnerabilities?
What is DACL (Discretionary Access Control List)?
A DACL stands for Discretionary Access Control List, in Microsoft Windows family, is an internal list attached to an object in Active Directory that specifies which users and groups can access the object and what kinds of operations they can perform on the object. In Windows 2000 and Windows NT, an internal list attached to a file or folder on a volume formatted using the NTFS that has a similar function.
How DACL works?
In Windows, each object in Active Directory or a local NTFS volume has an attribute called Security Descriptor that stores information about
The object’s owner (the security identifier or the owner) and the groups to which the owner belongs.
The discretionary access control list (DACL) of the object, which lists the security principals (users, groups, and computers) that have access to the object and their level of access.
The system access control list (SACL), which lists the security principals that should trigger audit events when accessing the list.
Basically, “DACL” is a list that contains features, one of them are called “Security Description” (we will take deep soon). This list are configured to handle calls and filter what object (files, processes, threads, etc), should be allowed or not for specific (user, groups, or computers). Hard to understand? Let me show up it as “Window UI”. =)
Maybe this image should be familiar to you right?. This area is one of various that you can manage “DACL & Security Descriptions” easily (without knowing that they actually exists).
As we can see, isn’t hard to understand what it is and why it was created, the thing is, what defines internally what permissions an user can have on it? What objects are configured in “DACL” to be filtered? let’s have a deep look into “Windows Internals” and his “structs”.
First of all, let’s take a look at “WinDBG” process list.
When we do list our windows processes in “WinDBG”, we can see that every process have the same patterns, only with different values or addresses ranges. In the image above, you can notice a marked address “0x856117c8”, this address represents a windows object, and this object have some important properties which defines: process name, permissions, process ID’s, handles, etc. (i’ll not extend this, so let it just as a simple recap).
A interesting thing that we can explore at moment, isn’t any else then “nt!_OBJECT_HEADER struct”. This struct have literally what tools we need to work and start our attack.
As an image above says, we simply dissect our process utilizing “nt!_OBJECT_HEADER struct”, which gave us information about what is located in our object. Also it’s important to notice that “nt!_OBJECT_HEADER” only look for addresses offsets before “nt!_EPROCESS”, which means that “nt!_EPROCESS” range, should stay after those offsets.
But what happens to SecurityDescription?
Another interesting thing is that our SecurityDescription “(0x856117b0+0x014)” is pointing to “0x8c005e1f” address, meaning that something is happening here, and this address have some interaction to “DACL & Security Description” implementations.
Now, let’s have a deep look in this specific address “0x8c005e1f”.
Utilizing previous target SecurityDescription address with WinDBG command “!sd”, with simple bit calculation, we now are able to understand much better how it’s implementation are configured on Windows Internals. So, those marked value, remember you something? Yes, that’s right, this marks are the users information stored in the process. At image below, we can compare these two DACL information.
As we can compared these two images, we notice that “SYSTEM” and “DAML” users, are related to another image about SecurityDescription “(Windows Internals)”. It’s a example (not legit), that how we can compare this two values.
Knowing that, and understanding the concepts that we actually can nullify any address from “ring0 (kernel)” only as an simple user, let’s try to make SecurityDescription address “(0x856117b0+0x014)” point to “NULL” “(0x00000000)”, and see what happens!
Ok! now “System.exe (PID:4)” process have SecurityDescriptor pointer nullified. Now let’s try to continue our “VM snapshot”.
Wait, what happened? we closed [System.exe] process manually? and without user permissions for it? using task manager?
Yes! only “nt authority/SYSTEM” should have permissions to close this process, but how could be possible a simple user “BSODed” whole system? There’s, the magic behind this exploitation is a well-know exploitation technique which nullify SecurityDescription behaviors from SYSTEM processes, it technique is very used for “LPE exploits” since it applies “no permissions” bit for those target processes. In short, from WinDBG we manually nullified the pointer which contains those permissions information in SecurityDescription meaning that “anyone” now can “write/read/execute” in this process. =)
But there’s a problem here, we now understand what we need to do in order to elaborate our exploit but i ask you, how we can identify “SYSTEM” process objects from a simple “ring3 (user-land)”?
In the image above, since we’re looking those addresses from WinDBG screen “ring0 (kernel mode)”, we clearly see the object there, but also we need to know that those values are not accessible (also unpredictable), to our simple user from “ring3 (user-land)”. The randomization of these addresses are implemented every time since “Windows 7” is rebooted “(Address Space Layout Randomization or ASLR)”, these mitigations deny every try to work with static address. Lastly, another important thing is that the randomization by self are unpredictable (in most of my tests), these objects only randomize through “0x85xxxxxx” to “0x87xxxxxx”, which means i actually don’t know if a bypass of this randomization (from ring3), actually exists.
So, what to do next?
NtQuerySystemInformation - Handle Leaking Attack
As mentioned before, isn’t possible to exploit from “ring3 (user-land)” due to a lot of permissions restrictions placed by our target “Operation System (windows 7)”, so what kind of things can we do to bypass these restrictions? the answer is “NtQuerySystemInformation WinAPI call”.
“NtQuerySystemInformation” by design is one of various security flaws that for “Microsoft” only represents a “feature” to an user perspective. The biggest problem about it “WinAPI call”, is that it’s configured by default to accept user calls (from undocumented behaviors), which should be parsed and queried together ring0 (kernel mode) information, as resulting in an leak of SYSTEM addresses/pointers. This “WinAPI Call”, is a well-know artifice for “memory leaking attacks”, since it’s allow an attacker to know exactly what pointers are important, and elaborate a better methodology for explore his target vulnerability (in our case WriteNULL).
But how could it be possible to leak information from ring3?
Before we start to looking deep into vulnerable (features) calls, we should look at first to the definition of “handles”, and why we need to get focus on it.
It’s an abstract reference value to a resource, often memory or an open file, or a pipe.
Properly, in Windows, (and generally in computing) a handle is an abstraction which hides a real memory address from the API user, allowing the system to reorganize physical memory transparently to the program. Resolving a handle into a pointer locks the memory, and releasing the handle invalidates the pointer. In this case think of it as an index into a table of pointers… you use the index for the system API calls, and the system can change the pointer in the table at will.
Alternatively a real pointer may be given as the handle when the API writer intends that the user of the API be insulated from the specifics of what the address returned points to; in this case it must be considered that what the handle points to may change at any time (from API version to version or even from call to call of the API that returns the handle) — the handle should therefore be treated as simply an opaque value meaningful only to the API.
In short, “handles” have properties to create and configure communications through objects (open files, apis, pipes), on “Operation System (OS)”. These handles are carrying a bunch of information about these objects, one of them are pointers. Basically, “handles” loads pointers, but do you know the best part of it? is the possibility to “leak” these pointer from “ring3 (user-land)”, and that’s what we need to deep our look.
Knowing that, “NtQuerySystemInformation” afford a lot interesting calls, on top of that, we have an undocumented call named “SystemExtendedHandleInformation”, which supports the follow structs.
These structs, should help us to leak “handle pointers”, and that’s how the magic starts.
Utilizing the designed flaw calls, let me fuzz some handle data from “ring3 (user-mode)” perspective and see what happens.
As you can see, from a simple user we actually can leak a lot of pointers and data. The best part of it, is that one of those pointers are correlated to our “PROCESS object”, did you remember?
This is it, that’s the trick! But there’s a problem. Assuming that many restrictions such as: “ASLR”, are configured by default, how we knows what addresses, PID’s and Handle values are correct in order to predict that one who contains our “PROCESS object” pointer?
The answer for this question is: “I don’t know, but there’s a method (really no stealth), which work as well!”. This method was discovered after tests assuming “ASLR randomization” and what processes (who contains useful pointers), should crash after been nullified through exploitation technique.
After some tests, it was noticed that if we define “lsass.exe PID”, as the only target to have his handles nullified, the “Operation System OS” doesn’t crashes (I assume that because “lsass.exe” isn’t a process that contains so many handles for “SYSTEM internals”, only to hold permissions and things related on this). After all, with “lsass.exe” handle pointers nullified, it’s clearly that not only 1 process will have “write/read/execute permission”, but also a lot. That’s why i don’t recommend this technique for a real world exploitation because isn’t safe (also not stealthy), nullifying “handles” could make the Operation System crash and reboot.
The things is, once “SYSTEM processes” do have access permissions for “Anyone”, the final part should be a “Shellcode Injection” in a SYSTEM target process, and that’s what we do in “winlogon.exe”. This process is running with SYSTEM permissions (and now after nullify attack “write/read/execute”)
So, putting all together, this is how it looks like. =D
After exploit runs, we finally got our “nt authority/SYSTEM cmd.exe shell”, nothing was crashed and processes work as well without issues.
The final consideration for this write-up, is that i didn’t found any reliable solution for WriteNULL challenge, only one which uses another driver vulnerability in order to leak pointer address (in references), meaning that this exploit should be the only one existing in internet utilizing this technique (really no one want to do this). =(
So, it was kind fun and hope everyone enjoyed this write-up. =P
Final Exploit link: