Automating detection of possible DLL interception paths (DLL Hijacks)

Hello, Khabrovites. Recruitment for the new stream of the course “Pentest. Penetration Testing Practice " . In anticipation of the start of the course, we are sharing with you the translation of interesting material.







Introduction



In this article, we'll look at the concept of DLL hijacking and how it can be used to achieve userland persistence on Windows systems. This method is described in MITER ATT & CK under: "Intercepting DLL Search Order (T1038) ".



DLL spoofing can be used by attackers for many different purposes, but this article will focus on achieving resiliency with auto-start applications. For example, since Slack and Microsoft Teams are launched at boot (by default), DLL spoofing in one of these applications would allow an attacker to gain persistent access to their target - whenever a user logs in.



After introducing the concept of DLLs, DLL lookup order, and DLL spoofing, I'll walk you through the process of automating DLL interception detection . This article will talk about detecting DLL interception paths in Slack, Microsoft Teams, and Visual Studio Code.



Finally, I discovered multiple DLL interception paths used by different applications, investigated the root cause, and found that applications using certain Windows API calls are prone to DLL interception when not running from C:\Windows\System32\.



I want to thank my colleague Josiah Massari ( @Airzero24) for being the first to spot some of these DLL hooks, explain their methodology, and inspire me to automate the detection.



What is a DLL?



A DLL is a library containing code and data that can be used simultaneously by more than one program. ( Source ) The



functionality of a DLL can be used by a Windows application using one of the functions LoadLibrary*. Applications can reference DLLs designed specifically for those applications, or Windows DLLs already on disk in System32. Developers can load DLLs from System32 to use functionality already implemented in Windows in their applications without having to write this functionality from scratch.



For example, a developer who needs to make HTTP requests can use the WinHTTP ( winhttp.dll) library instead of implementing HTTP requests using raw sockets.



DLL search order and interception



Since DLLs exist as files on disk, you may be wondering how an application knows where to load the DLL from? Microsoft has documented the DLL lookup order in detail here .



Starting with Windows XP SP2, DLL Safe Search Mode is enabled by default ( HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Session Manager\SafeDllSearchMode). When safe mode is enabled, the DLL search order is as follows:



  1. The directory from which the application was loaded.
  2. System directory. Use the GetSystemDirectory function to get the path to this directory.
  3. 16-bit system directory. There is no function that provides a path to this directory, but it is searched.
  4. Windows directory. Use the GetWindowsDirectory function to get the path to this directory.
  5. Current directory.
  6. , PATH. , , App Paths. App Paths DLL.


A system can contain multiple versions of the same DLL. Applications can control the choice of the location from which the DLL should be loaded by specifying the full path or by using another mechanism such as a manifest. ( Source )



If the application does not specify where to load the DLL from, Windows uses the default DLL search order shown above. The first position in the DLL search order (the directory from which the application is loaded) is of interest to attackers.



If the application developer intends to load the DLL fromC:\Windows\System32, but did not explicitly write it in the application, the malicious DLL placed in the application directory will be loaded before the legitimate DLL from System32. Loading a malicious DLL is called DLL spoofing (or interception) and is used by attackers to load malicious code into trusted / signed applications.



Using DLL Spoofing to Achieve Resilience



DLL spoofing can be used to achieve resiliency when a vulnerable application / service is started and a malicious DLL is placed in a vulnerable location. A colleague of mine @Airzero24discovered DLL spoofing in Microsoft OneDrive, Microsoft Teams and Slack as userenv.dll.



It was these programs that became the target of the interception, because by default they are configured to start at Windows startup. This can be seen below in Task Manager:





Windows Applications Configured to Autostart



To test DLL spoofing, I created a DLL shellcode loader that started Cobalt Strike Beacon. I renamed the malicious DLL to userenv.dlland copied it to the affected application directory. I launched the application and saw my new Beacon callback.





Cobalt Strike Beacon Through DLL Interception



UsingProcess Explorer , I can check if my malicious DLL was actually loaded by a vulnerable application.





Process Explorer showing loaded malicious DLL



Automatic detection of DLL interception potential



After confirming the previously known DLL hijacking, I wanted to see if I could find other DLL spoofing capabilities that could be exploited.



The code used in my checkout can be found here .



Using Slack as an example



To start this process, I ran Process Monitor (ProcMon) with the following filters:



  • Process name -slack.exe
  • Result containsNOT FOUND
  • The path ends with .dll.




Find missing DLLs in ProcMon.



Then I started Slack and examined ProcMon for any DLLs that Slack was looking for but couldn't find.





Possible DLL Interception Paths Discovered by ProcMon



I exported this data from ProcMon as a CSV file for easier parsing in PowerShell.



With my current shellcode loader DLL, I couldn't easily figure out the DLL names that were successfully loaded by Slack. I created a new DLL, which is used GetModuleHandleEx, and GetModuleFileNameto determine the name of the loaded DLL and write it to a text file .



My next goal was to parse the CSV file for DLL paths in the list, view that list, copy my test DLL to the specified path, start the target process, stop the target process, and delete the test DLL. If the test DLL was loaded successfully, it will write its name to the resulting file.



When this process finishes, I will have a list of possible DLL hijacks (I hope) written to a text file.



All the magic in my DLLHijackTest project is done by a PowerShell script . It accepts the path to the CSV file generated by ProcMon, the path to your malicious DLL, the path to the process you want to run, and any arguments you want to pass to the process.





Get-PotentialDLLHijack Parameters





Get-PotentialDLLHijack.ps1



After a few minutes, I check the text file listed in my "malicious" DLL for possible DLL hijacks. I found the following possible interception paths for Slack:



PS C:Users\John\Desktop> Get-PotentialDLLHijack -CSVPath .\Logfile.CSV -MaliciousDLLPath .\DLLHijackTest.dll -ProcessPath "C:\Users\John\AppData\Local\slack\slack.exe"
C:\Users\John\AppData\Local\slack\app-4.6.0\WINSTA.dll
C:\Users\John\AppData\Local\slack\app-4.6.0\LINKINFO.dll
C:\Users\John\AppData\Local\slack\app-4.6.0\ntshrui.dll
C:\Users\John\AppData\Local\slack\app-4.6.0\srvcli.dll
C:\Users\John\AppData\Local\slack\app-4.6.0\cscapi.dll
C:\Users\John\AppData\Local\slack\app-4.6.0\KBDUS.DLL


Using Microsoft Teams as an example



We carry out the process described above again:



  1. Use ProcMon to identify potential DLL interception paths, export this data as a CSV file.
  2. Determine the path to start the process.
  3. Define any arguments you want to pass to the process.
  4. Run Get-PotentialDLLHijack.ps1with appropriate arguments.


I found the following possible interception paths for Microsoft Teams:



PS C:Users\John\Desktop> Get-PotentialDLLHijack -CSVPath .\Logfile.CSV -MaliciousDLLPath .\DLLHijackTest.dll -ProcessPath "C:\Users\John\AppData\Local\Microsoft\Teams\Update.exe" -ProcessArguments '--processStart "Teams.exe"'
C:\Users\John\AppData\Local\Microsoft\Teams\current\WINSTA.dll
C:\Users\John\AppData\Local\Microsoft\Teams\current\LINKINFO.dll
C:\Users\John\AppData\Local\Microsoft\Teams\current\ntshrui.dll
C:\Users\John\AppData\Local\Microsoft\Teams\current\srvcli.dll
C:\Users\John\AppData\Local\Microsoft\Teams\current\cscapi.dll
C:\Users\John\AppData\Local\Microsoft\Teams\current\WindowsCodecs.dll
C:\Users\John\AppData\Local\Microsoft\Teams\current\TextInputFramework.dll


Note : I had to make small changes to the PowerShell script to complete Teams.exe, as my script is trying to terminate the process it was trying to start, in this case it is Update.exe.


Using Visual Studio Code as an example



By repeating the above process, I found the following potential interception paths for Visual Studio Code:



PS C:Users\John\Desktop> Get-PotentialDLLHijack -CSVPath .\Logfile.CSV -MaliciousDLLPath .\DLLHijackTest.dll -ProcessPath "C:\Users\John\AppData\Local\Programs\Microsoft VS Code\Code.exe"
C:\Users\John\AppData\Local\Programs\Microsoft VS Code\WINSTA.dll
C:\Users\John\AppData\Local\Programs\Microsoft VS Code\LINKINFO.dll
C:\Users\John\AppData\Local\Programs\Microsoft VS Code\ntshrui.dll
C:\Users\John\AppData\Local\Programs\Microsoft VS Code\srvcli.dll
C:\Users\John\AppData\Local\Programs\Microsoft VS Code\cscapi.dll


Sharing DLLs



I noticed that Slack, Microsoft Teams and Visual Studio Code share the following DLLs:



  • WINSTA.dll
  • LINKINFO.dll
  • ntshrui.dll
  • srvcli.dll
  • cscapi.dll


I found this interesting, and I wanted to understand what causes this behavior.



Methodology: Understanding Ways to Intercept Shared DLLs



I watched Tracy stack when Slack tried to load WINSTA.dll, LINKINFO.dll, ntshrui.dll, srvcli.dlland cscapi.dll.



DLL with lazy loading



I noticed similarities in Tracy stack when loading WINSTA.dll, LINKINFO.dll, ntshrui.dlland srvcli.dll.





Stack trace when Code.exe tries to load WINSTA.dll





a stack trace when Teams.exetrying to load LINKINFO.dll,





stack trace when Slack tries to loadntshrui.dll



a stack trace constantly contains a call _tailMerge_<dllname>_dll, delayLoadHelper2followed LdrResolveDelayLoadedAPI. This behavior was the same for all three applications.



I have determined that this behavior is related to lazy DLL loading . From the trace stack at bootWINSTA.dllI could see that the module responsible for this lazy loading was wtsapi32.dll.



I opened wtsapi32.dllin Ghidra and used Search -> For Strings -> Filter: WINSTA.dll. Double click on the found line will take you to its location in memory.





The line " WINSTA.dll" inwtsapi32.dll



By right-clicking on a location in memory, we can find any references to this address.





Links toWINSTA.dll



Following the links, we can see that the string WINSTA.dllis passed to a structure named ImgDelayDescr. Looking at the documentation for this structure, we can confirm that it is related to lazy DLL loading.



typedef struct ImgDelayDescr {
   DWORD        grAttrs;        // 
   RVA          rvaDLLName;     // RVA   dll 
   RVA          rvaHmod;        // RVA  
   RVA          rvaIAT;         // RVA IAT
   RVA          rvaINT;         // RVA INT
   RVA          rvaBoundIAT;    // RVA   IAT
   RVA          rvaUnloadIAT;   // RVA    IAT
   DWORD        dwTimeStamp;    // 0,   ,
                                // O.W. / DLL,   (Old BIND)
   } ImgDelayDescr, * PImgDelayDescr;


This structure can be passed to __delayLoadHelper2, which will use LoadLibrary/ GetProcAddressto load the specified DLL and fix the imported function address in the lazy load import address table (IAT).



FARPROC WINAPI __delayLoadHelper2(
   PCImgDelayDescr pidd,  //     ImgDelayDescr
   FARPROC * ppfnIATEntry //     IAT  
);


By finding other references to our structure ImgDelayDescr, we can find a call __delayLoadHelper2that then calls ResolveDelayLoadedAPI. I have renamed the function name, types and variables to make it easier to understand.





__delayLoadHelper2and ResolveDelayLoadedAPIat Ghidra



Excellent! This is consistent with what we saw in our ProcMon stack trace when Slack tried to load WINSTA.dll.





__delayLoadHelper2 and ResolveDelayLoadedAPIin ProcMon.



This behavior was uniformly for WINSTA.dll, LINKINFO.dll, ntshrui.dlland srvcli.dll. The main difference between each lazy-load DLL was the "parent" DLL. In all three applications:



  • wtsapi32.dll deferred loaded WINSTA.dll
  • shell32.dll lazy loaded LINKINFO.dll
  • LINKINFO.dll deferred loaded ntshrui.dll
  • ntshrui.dll deferred loaded srvcli.dll


Did you notice anything interesting? It looks like it shell32.dlldownloads LINKINFO.dll, which downloads ntshrui.dll, which finally downloads srvcli.dll. This brings us to our last common potential DLL spoofing option - cscapi.dll.



DLL substitution in NetShareGetInfo and NetShareEnum



I followed the stack trace when Slack tried to load cscapi.dlland saw a call LoadLibraryExWthat apparently came from srvcli.dll. I opened the





stack trace at bootcscapi.dll in Ghidra and used . Double clicking on the found line and following the links leads to the expected call. calls LoadLibrary for Renaming the function containing the call and following the links, I got two places where the function is used:



srvcli.dllSearch -> For Strings -> Filter: cscapi.dllLoadLibrary





srvcli.dllcscapi.dll



LoadLibrary







NetShareEnum downloads cscapi.dll





NetShareGetInfo downloadscscapi.dll



I checked this with PoC programs that called NetShareEnumand NetShareGetInfo:





NetShareEnum.exedownloads cscapi.dll





NetShareGetInfo.exedownloadscscapi.dll



results



The following DLL spoofing paths are available in Slack:



C:\Users\John\AppData\Local\slack\app-4.6.0\WINSTA.dll
C:\Users\John\AppData\Local\slack\app-4.6.0\LINKINFO.dll
C:\Users\John\AppData\Local\slack\app-4.6.0\ntshrui.dll
C:\Users\John\AppData\Local\slack\app-4.6.0\srvcli.dll
C:\Users\John\AppData\Local\slack\app-4.6.0\cscapi.dll
C:\Users\John\AppData\Local\slack\app-4.6.0\KBDUS.DLL


The following DLL spoofing paths are available in Microsoft Teams:



C:\Users\John\AppData\Local\Microsoft\Teams\current\WINSTA.dll
C:\Users\John\AppData\Local\Microsoft\Teams\current\LINKINFO.dll
C:\Users\John\AppData\Local\Microsoft\Teams\current\ntshrui.dll
C:\Users\John\AppData\Local\Microsoft\Teams\current\srvcli.dll
C:\Users\John\AppData\Local\Microsoft\Teams\current\cscapi.dll
C:\Users\John\AppData\Local\Microsoft\Teams\current\WindowsCodecs.dll
C:\Users\John\AppData\Local\Microsoft\Teams\current\TextInputFramework.dll


The following DLL spoofing paths are available in Visual Studio Code:



C:\Users\John\AppData\Local\Programs\Microsoft VS Code\WINSTA.dll
C:\Users\John\AppData\Local\Programs\Microsoft VS Code\LINKINFO.dll
C:\Users\John\AppData\Local\Programs\Microsoft VS Code\ntshrui.dll
C:\Users\John\AppData\Local\Programs\Microsoft VS Code\srvcli.dll
C:\Users\John\AppData\Local\Programs\Microsoft VS Code\cscapi.dll


In addition, I found that programs using NetShareEnumand NetShareGetInfoprovide the ability to replace DLLs in the form cscapi.dlldue to the hard-coded call LoadLibrary. I have verified this behavior with Ghidra and PoC.



Conclusion



As a reminder, DLL interception is a method by which attackers can interfere with code execution in signed / trusted applications. I have created tools to help automate DLL interception path detection. Using this tool, I discovered DLL interception paths in Slack, Microsoft Teams and Visual Studio Code.



I noticed that the DLL interception paths of these three applications overlap and investigated the cause. I highlighted my method of understanding this coincidence. I learned about lazy loading DLLs and discovered two API calls that make it possible to intercept DLLs in any program that calls them:



  • NetShareEnum loads cscapi.dll
  • NetShareGetInfo loads cscapi.dll


Thanks for taking the time to read this article, I hope you've learned a thing or two about Windows APIs, Ghidra, ProcMon, DLLs and DLL interception!



Links



Big hello to my colleagues Daniel Heinsen ( @hotnops), Lee Christensen ( @tifkin_) and Matt Hand ( @matterpreter) for helping out with Ghidra / ProcMon!






Checking public PoCs for use in pentesting






Read more:






All Articles