HackSys Extreme Vulnerable Driver — Arbitrary Write NULL (New Solution)
A simple (not stealth) method utilizing
NtQuerySystemInformation
forArbitrary 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 possibilities (but unreliable) techniques and methodologies on how to exploit a specify vulnerability typed Arbitrary Write NULL
in most of vulnerable x86
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, and all content from this write-up lies behind what should be done to work around with a view to get a LPE
.Hope everyone enjoy! =)
Introduction
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.
[HEVD]-TriggerWriteNull
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?
According to https://networkencyclopedia.com/discretionary-access-control-list-dacl/
What is DACL (Discretionary Access Control List)?
A
DACL
stands for DiscretionaryAccess Control List
, in Microsoft Windows family, is an internal list attached to an object inActive Directory
that specifies whichusers
andgroups
can access the object and what kinds of operations they can perform on the object. InWindows 2000
andWindows NT
, an internal list attached to a file or folder on a volume formatted using theNTFS
that has a similar function.How DACL works?
In
Windows
, each object inActive Directory
or a localNTFS
volume has an attribute calledSecurity Descriptor
that stores information aboutThe 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 BSOD'ed
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
method 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.
according to: https://stackoverflow.com/questions/902967/what-is-a-windows-handle
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 I/O
, apis
, pipes
, etc), 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 not 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
shell, nothing was crashed and processes work as well without any 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 (on 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:
https://github.com/daem0nc0re/HEVD-CSharpKernelPwn/blob/master/HEVD_Win7x86/WriteNull/Program.cs http://bprint.rewolf.pl/bprint/?p=1683
https://github.com/ZecOps/CVE-2020–0796-LPE-POC/blob/master/poc.py