During research for some new User Account Control (UAC) bypass techniques, I discovered what I believe to be a new bypass method (at the time of this writing). It is worth mentioning that Microsoft doesn’t consider UAC a security boundary, however we still reported the bug to Microsoft and want to share its details here. This method was successfully tested against Windows 10 Build 17134. Before I dive into the details of the bypass, I will first offer a short primer on UAC and its quirks for those unfamiliar with some of it’s inner-workings.
When a user that is part of the Administrators group wants to execute a process that requires elevation, the UAC prompt is presented to confirm process elevation to the user. This UAC prompt however, is not popped for ALL administrative executables on Windows. There are a few exceptions that will “auto elevate” the target executable with no UAC prompt being thrown thus bypassing UAC (to many’s surprise!). This select group of trusted executables have additional security checks done to them to ensure they are in fact trusted so this feature is not abused by malicious actors. This approach has been used in previous UAC bypasses and will be the heart of this bypass method as well. There are a few loopholes we need to bypass for this attack to succeed however. Let’s check out some of these requirements that are enforced if we want our executable to “auto elevate”. For this, I will show some disassembly shots of appinfo.dll (the AIS service that processes elevation requests — one of the core components of UAC).
Requirement 1: File is Configured for Auto Elevation
When a request to elevate a program occurs, an RPC call is made to AIS (appinfo.dll) with the target executable path passed as an argument. This service will then map the target executable file contents for reading. The executable’s manifest is attempted to be read in order to obtain a value for a potential “autoElevate” key (if it exists).
If found and the value is “True,” it will be considered an auto elevating executable which will be ran elevated and bypass any UAC dialog (provided it passed the next requirements mentioned later). There is one exception to this “autoElevate” rule however. Regardless of manifest, if the file name itself matches one of the whitelisted EXE names, it will also be considered an “auto elevating” executable. Below you’ll see a bsearch call after this manifest check to see if the file name exists in a list of whitelisted executable names. If the exe name matches one of these executable names, then auto elevation will be attempted regardless of manifest.
A few of these hardcoded whitelisted file names are:
‘cttunesvr.exe’, ‘inetmgr.exe’, ‘migsetup.exe’, ‘mmc.exe’, ‘oobe.exe’, ‘pkgmgr.exe’, ‘provisionshare.exe’, ‘provisionstorage.exe’, ‘spinstall.exe’, ‘winsat.exe’
Requirement 2: Properly Signed
Assuming the binary being submitted for UAC request is considered “auto elevating,” it will now do a signature check using wintrust!WTGetSignatureInfo. This means an attacker won’t be able to simply craft their own “autoElevating” manifest or executable file name to get auto elevation to succeed, as the attacker’s binary is most likely not properly signed and it also probably doesn’t pass the last requirement, which is Executing from Trusted Directory.
Requirement 3: Executing from Trusted Directory
The last auto elevating requirement is that the target executable resides in a “trusted directory,” such as “C:\Windows\System32”. Figure 3 shows AIS doing this check on a path requesting elevation, in this case one of the paths its considering “trusted” is “C:\Windows\System32”.
The name of this write up is “Bypassing UAC by Mocking Trusted Directories,” so you can probably guess what’s coming next.
As mentioned in the UAC primer section, auto elevation (UAC bypass) will occur for executables that are
- Configured for Auto Elevation
- Properly Signed
- Executing from a trusted directory (“C:\Windows\System32”) .
Appinfo.dll (AIS) will use RtlPrefixUnicodeString API to see if the target executable path begins with “C:\Windows\System32\” for one of the trusted directory checks. This is pretty bullet proof check considering its comparing against the canonicalized path name of the target executable requesting elevation. So for bypassing this check, I construct a directory called “C:\Windows \” (notice trailing space after “Windows”). This won’t pass the RtlPrefixUnicodeString check of course, and I’ll also mention that this is somewhat invalid (or in the very least “unfriendly”) directory name, as Windows does not allow trailing spaces when you create a directory (try it). Using the CreateDirectory API however, and prepending a “\\?\” to the directory name I want to create, we can bypass some of these naming filter rules and send the directory creation request directly to file system.
This results in a bit of an awkward directory happily coexisting on the filesystem alongside the real “C:\Windows\” (except for when you try to do anything with it in Windows Explorer).
Now that we have a “C:\Windows \“ directory, we can create a “system32” directory in it and copy one of the signed, auto elevating executables from the real “C:\Windows\System32”. For this, I copied “winSAT.exe” (one of the whitelisted auto elevating Windows executables). When we attempt to run this “C:\Windows \System32\winSAT.exe”, it will go through the following APIs (seen in Figure 6) in appinfo.dll before performing the trusted directory check. This is important, and core of why this bypass works.
When this awkward path is sent to AIS for an elevation request, the path is passed to GetLongPathNameW, which converts it back to “C:\Windows\System32\winSAT.exe” (space removed). Perfect! This is now the string that trusted directory checks are performed against (using RtlPrefixUnicodeString) for the rest of the routine. The beauty is that after the trusted directory check is done with this converted path string, it is then freed, and rest of checks (and final elevated execution request) are done with the original executable path name (with the trailing space). This allows all other checks to pass and results in appinfo.dll spawning my winSAT.exe copy as auto elevated (since it is both properly signed and whitelisted for auto elevation).
To actually elevate attacker code through this, I simply dropped a fake WINMM.dll (imported by winSAT.exe) in its current directory “C:\Windows \System32\” for a local dll hijack. The full concept can be seen in figure below.