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 * * * *************************************************************************** DRIVER_OVERRAN_STACK_BUFFER (f7) A driver has overrun a stack-based buffer. This overrun could potentially allow a malicious user to gain control of this machine. DESCRIPTION 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 variable(s). Arguments: 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: ------------------ DEFAULT_BUCKET_ID: GS_FALSE_POSITIVE_MISSING_GSFRAME 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 1.0.0.5000 which was released on the 20th September 2016.
TimeLine
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 [1.2.1.1000] [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 : 29.1.34.1821
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 : 28.1.48.2039
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.