Posted yesteryear Oliver Chang
Modern graphic drivers are complicated in addition to furnish a large promising assault surface for EoPs in addition to sandbox escapes from processes that receive got access to the GPU (e.g. the Chrome GPU process). In this spider web log post service we’ll convey a aspect at attacking the NVIDIA essence fashion Windows drivers, in addition to a few of the bugs that I found. I did this enquiry every bit component of a 20% projection amongst , during which a full of 16 vulnerabilities were discovered.
Kernel WDDM interfaces
The essence fashion element of a graphics driver is referred to every bit the display miniport driver. Microsoft’s documentation has a overnice diagram that summarises the human relationship betwixt the diverse components:
In the DriverEntry() for display miniport drivers, a DRIVER_INITIALIZATION_DATA construction is populated amongst callbacks to the vendor implementations of functions that truly interact amongst the hardware, which is passed to dxgkrnl.sys (DirectX subsystem) via DxgkInitialize(). These callbacks tin give the axe either last called yesteryear the DirectX essence subsystem, or inwards to a greater extent than or less cases larn called straight from user fashion code.
DxgkDdiEscape
A good known entry dot for potential vulnerabilities hither is the DxgkDdiEscape interface. This tin give the axe last called straight from user mode, in addition to accepts arbitrary information that is parsed in addition to handled inwards a vendor specific way (essentially an IOCTL). For the residuum of this post, we’ll exercise the term “escape” to announce a special command that’s supported yesteryear the DxgkDdiEscape function.
NVIDIA has a whopping 400 escapes hither at fourth dimension of writing, hence this was where I spent most of my fourth dimension (the necessity of many of these beingness inwards the essence is questionable):
// (names of these structs are made upwards yesteryear me)
// Represents a grouping of escape codes
struct NvEscapeRecord {
DWORD action_num;
DWORD expected_magic;
void *handler_func;
NvEscapeRecordInfo *info;
_QWORD num_codes;
};
// Information nearly a specific escape code.
struct NvEscapeCodeInfo {
DWORD code;
DWORD unknown;
_QWORD expected_size;
WORD unknown_1;
};
// Represents a grouping of escape codes
struct NvEscapeRecord {
DWORD action_num;
DWORD expected_magic;
void *handler_func;
NvEscapeRecordInfo *info;
_QWORD num_codes;
};
// Information nearly a specific escape code.
struct NvEscapeCodeInfo {
DWORD code;
DWORD unknown;
_QWORD expected_size;
WORD unknown_1;
};
NVIDIA implements their mortal information (pPrivateDriverData inwards the DXGKARG_ESCAPE struct) for each escape every bit a header followed yesteryear data. The header has the next format:
struct NvEscapeHeader {
DWORD magic;
WORD unknown_4;
WORD unknown_6;
DWORD size;
DWORD magic2;
DWORD code;
DWORD unknown[7];
};
DWORD magic;
WORD unknown_4;
WORD unknown_6;
DWORD size;
DWORD magic2;
DWORD code;
DWORD unknown[7];
};
These escapes are identified yesteryear a 32-bit code (first fellow member of the NvEscapeCodeInfo struct above), in addition to are grouped yesteryear their most pregnant byte (from 1 - 9).
There is to a greater extent than or less validation beingness done earlier each escape code is handled. In particular, each NvEscapeCodeInfo contains the expected size of the escape information next the header. This is validated against the size inwards the NvEscapeHeader, which itself is validated against the PrivateDriverDataSize plain given to DxgkDdiEscape. However, it’s possible for the expected size to last 0 (usually when the escape information is expected to last variable sized) which agency that the escape handler is responsible for doing its ain validation. This has led to to a greater extent than or less bugs (1, 2).
Most of the vulnerabilities works life (13 inwards total) inwards escape handlers were really basic mistakes, such every bit writing to user provided pointers blindly, disclosing uninitialised essence retentiveness to user mode, in addition to wrong bounds checking. There were also numerous issues that I noticed (e.g. OOB reads) that I didn’t study because they didn’t seem exploitable.
DxgkDdiSubmitBufferVirtual
Another interesting entry dot is the DxgkDdiSubmitBufferVirtual function, which is newly introduced inwards Windows 10 in addition to WDDM 2.0 to back upwards GPU virtual retentiveness (deprecating the erstwhile DxgkDdiSubmitBuffer/DxgkDdiRender functions). This business office is fairly complicated, in addition to also accepts vendor specific information from the user fashion driver for each command submitted. One põrnikas was works life here.
Others
There are a few other WDDM functions that convey vendor-specific data, only zilch of involvement were works life inwards those later on a quick review.
Exposed devices
NVIDIA also exposes to a greater extent than or less additional devices that tin give the axe last opened yesteryear whatever user:
- \\.\NvAdminDevice which appears to last used for NVAPI. H5N1 lot of the ioctl handlers seem to telephone band into DxgkDdiEscape.
- \\.\UVMLite{Controller,Process*}, probable related to NVIDIA’s “unified memory”. 1 bug was works life here.
- \\.\NvStreamKms, installed yesteryear default every bit component of GeForce Experience, only yous tin give the axe opt out during installation. It’s non precisely clear why this special driver is necessary. 1 bug was works life hither also.
More interesting bugs
Most of the bugs I works life were yesteryear manual reversing in addition to analysis, along amongst to a greater extent than or less custom IDA scripts. I also ended upwards writing a fuzzer, which was surprisingly successful given how uncomplicated it was.
While most of the bugs were rather slowly (simple cases of missing validation), at that spot were a few that were a fight to a greater extent than interesting.
NvStreamKms
This driver registers a procedure creation notification callback using the PsSetCreateProcessNotifyRoutineEx function. This callback checks if novel processes created on the organisation fit icon names that were previously laid yesteryear sending IOCTLs.
This creation notification routine contained a bug:
(Simplified decompiled output)
wchar_t Dst[BUF_SIZE];
...
if ( cur->image_names_count > 0 ) {
// info_ is the PPS_CREATE_NOTIFY_INFO that is passed to the routine.
image_filename = info_->ImageFileName;
buf = image_filename->Buffer;
if ( buf ) {
filename_length = 0i64;
num_chars = image_filename->Length / 2;
// Look for the filename yesteryear scanning for backslash.
if ( num_chars ) {
while ( buf[num_chars - filename_length - 1] != '\\' ) {
++filename_length;
if ( filename_length >= num_chars )
goto DO_COPY;
}
buf += num_chars - filename_length;
}
DO_COPY:
wcscpy_s(Dst, filename_length, buf);
Dst[filename_length] = 0;
wcslwr(Dst);
...
if ( cur->image_names_count > 0 ) {
// info_ is the PPS_CREATE_NOTIFY_INFO that is passed to the routine.
image_filename = info_->ImageFileName;
buf = image_filename->Buffer;
if ( buf ) {
filename_length = 0i64;
num_chars = image_filename->Length / 2;
// Look for the filename yesteryear scanning for backslash.
if ( num_chars ) {
while ( buf[num_chars - filename_length - 1] != '\\' ) {
++filename_length;
if ( filename_length >= num_chars )
goto DO_COPY;
}
buf += num_chars - filename_length;
}
DO_COPY:
wcscpy_s(Dst, filename_length, buf);
Dst[filename_length] = 0;
wcslwr(Dst);
This routines extracts the icon shout out from the ImageFileName fellow member of PS_CREATE_NOTIFY_INFO yesteryear searching backwards for backslash (‘\’). This is in addition to hence copied to a stack buffer (Dst) using wcscpy_s, only the length passed is the length of the calculated name, in addition to non the length of the finish buffer.
Even though Dst is a fixed size buffer, this isn’t a straightforward overflow. Its size is bigger than 255 wchars, in addition to for most Windows filesystems path components cannot last greater than 255 characters. Scanning for backslash is also valid for most cases because ImageFileName is a canonicalised path.
It is however, possible to transcend a UNC path that keeps forrad slash (‘/’) every bit the path separator later on beingness canonicalised (credits to James Forshaw for pointing me to this). This agency nosotros tin give the axe larn a filename of the shape “aaa/bbb/ccc/...” in addition to displace an overflow.
For example: CreateProcessW(L"\\\\?\\UNC\\127.0.0.1@8000\\DavWWWRoot\\aaaa/bbbb/cccc/blah.exe", …)
Another interesting banking concern annotation is that the wcslwr next the bad re-create doesn’t truly boundary the contents of the overflow (the exclusively requirement is valid UTF-16). Since the calculated filename_length doesn’t include the null terminator, wcscpy_s volition retrieve that the finish is every bit good pocket-sized in addition to volition clear the finish string yesteryear writing a null byte at the showtime (after copying the contents upwards to filename_length bytes firstly hence the overflow still happens). This agency that the wcslwr is useless because this wcscpy_s telephone band in addition to component of the code never worked to get with.
Exploiting this is trivial, every bit the driver is non compiled amongst stack cookies (hacking similar it’s 1999). H5N1 local privilege escalation exploit is attached inwards the original issue that sets upwards a simulated WebDAV server to exploit the vulnerability (ROP, pin stack to user buffer, ROP i time again to allocate rwx mem containing shellcode in addition to bound to it).
Incorrect validation inwards UVMLiteController
NVIDIA’s driver also exposes a device at \\.\UVMLiteController that tin give the axe last opened yesteryear whatever user (including from the sandboxed Chrome GPU process). The IOCTL handlers for this device write results straight to Irp->UserBuffer, which is the output pointer passed to DeviceIoControl (Microsoft’s documentation says non to create this).The IO command codes specify METHOD_BUFFERED, which agency that the Windows essence checks that the address hit provided is writeable yesteryear the user earlier passing it off to the driver.
However, these handlers lacked bounds checking for the output buffer, which agency that a user fashion context could transcend a length of 0 amongst whatever arbitrary address (which passes the ProbeForWrite check) to number inwards a express write-what-where (the “what” hither is express to to a greater extent than or less specific values: including 32-bit 0xffff, 32-bit 0x1f, 32-bit 0, in addition to 8-bit 0).
Remote assault vector?
Given the quantity of bugs that were discovered, I investigated whether if whatever of them tin give the axe last reached from a completely remote context without having to compromise a sandboxed procedure firstly (e.g. through WebGL inwards a browser, or through video acceleration).
Luckily, this didn’t appear to last the case. This wasn’t every bit good surprising, given that the vulnerable APIs hither are really depression score in addition to exclusively reached later on going through many layers (for Chrome, libANGLE -> Direct3D runtime in addition to user fashion driver -> essence fashion driver), in addition to to a greater extent than ofttimes than non called amongst valid arguments constructed inwards the user fashion driver.
NVIDIA’s response
The nature of the bugs works life showed that NVIDIA has a lot of piece of occupation to do. Their drivers contained a lot of code which in all probability shouldn’t last inwards the kernel, in addition to most of the bugs discovered were really basic mistakes. One of their drivers (NvStreamKms.sys) also lacks really basic mitigations (stack cookies) fifty-fifty today.
However, their response was mostly quick in addition to positive. Most bugs were fixed good nether the deadline, in addition to it seems that they’ve been finding to a greater extent than or less bugs on their ain internally. They also indicated that they’ve been working on re-architecturing their essence drivers for security, only weren’t ready to portion whatever concrete details.
Timeline
2016-07-26 | First põrnikas reported to NVIDIA. |
2016-09-21 | 6 of the bugs reported were fixed silently inwards the 372.90 release. Discussed field gap issues amongst NVIDIA. |
2016-10-23 | Patch released that includes prepare for residuum (all 14) of the bugs that were reported at the fourth dimension (375.93). |
2016-10-28 | Public bulletin released, in addition to P0 bugs derestricted. |
2016-11-04 | Realised that https://bugs.chromium.org/p/project-zero/issues/detail?id=911 wasn’t fixed properly. Notified NVIDIA. |
2016-12-14 | |
2017-02-14 |
Patch gap
NVIDIA’s firstly patch, which included fixes to vi of the bugs I reported, did non include a world bulletin (the release notes cite “security updates”). They had planned to loose world details a calendar month later on the field is released. We noticed this, in addition to allow them know that nosotros didn’t consider this to last expert practise every bit an aggressor tin give the axe contrary the field to uncovering the vulnerabilities earlier Earth is made aware of the details given this large window.
While the firstly vi bugs fixed did non receive got details released for to a greater extent than than xxx days, the remaining 8 at the fourth dimension had a field released v days earlier the firstly bulletin was released. It looks similar NVIDIA has been trying to cut down this gap, only based on recent bulletins it appears to last inconsistent.
Komentar
Posting Komentar