W4LK3R
GitHubLinkedInEmail
  • Home
    • Who am I ?
  • Research
    • Double Take Zero Day (CVE-2023–40459)
  • Red Team Diaries
    • #1 Domain Admin in 2 Hours
    • #2 Low Hanging Credentials
  • Malware Development
    • Basics
    • Dynamic Link Library
    • Code Execution
      • Create Local Thread
      • DLL Execution ( Disk )
      • Function Pointer (No API)
      • Handle2Self
      • Thread2Fiber
      • Callback Functions
      • Local Thread Hijacking / Context Injection
      • Local Mapping Injection
      • Local Module Stomping / DLL Hollowing
      • Local Function Stomping
Powered by GitBook
On this page
  • Dynamic Link Library
  • DLL Code Structure
  • Precompiled Header
  • DLL Attach and Detach Events
  • DLL Execution
  • Explicit and Implicit Linking
  • Dynamic Loading
  • Default DLL Search Paths
  • Default DLL Search Order (Windows 10/11)
  • Modifying the Search Order

Was this helpful?

  1. Malware Development

Dynamic Link Library

Intro to DLL code structure and concepts

Dynamic Link Library

DLL (Dynamic Link Library) is one of the most used PE (Portable Executable) file formats in Windows. as the name suggests, DLLs are linked during or before application run-time ( depending on execution flow and compilation options). the main advantage of DLL is code reuse, dependency and backward compatibility and maintainability. DLLs can be compiled once and used many times between many different applications that want to use the same capabilities (same functions and sub-routines). you can update DLL functionality without recompiling the dependent programs.


DLL Code Structure

A simple DLL code looks like this:

#include <windows.h>
#include "pch.h"

/*
Exported function

--> extern "C" prevents C++ name mangling so other programs can access 
 the function with its exact name (HelloWorld).

 --> __declspec(dllexport): Exports the HelloWorld function, 
making it available to programs that load the DLL.

*/
extern "C" __declspec(dllexport) void HelloWorld() {
    MessageBoxW(NULL, L"Hello, World!", L"Hello from DLL", MB_OK | MB_ICONINFORMATION);
}

// Entry point for the DLL
BOOL APIENTRY DllMain(HMODULE hModule, DWORD  ul_reason_for_call, LPVOID lpReserved) {
    switch (ul_reason_for_call) {
    case DLL_PROCESS_ATTACH:
        // executes when the DLL is loaded into a process
        break;
    case DLL_THREAD_ATTACH:
        // executes when a new thread uses the DLL (after loading)
    case DLL_THREAD_DETACH:
        // executes when detached from the thread (termination)
        break;
    case DLL_PROCESS_DETACH:
        // executes when the DLL is unloaded from the process
        break;
    }
    return TRUE;
}

To write a DLL in VisualStudio, create a new project and select the (DLL) project type:

After creating the project, you will see two source files in the right hand side (solution explorer):

  1. dllamin.cpp is the main dll source file.

  2. pch.cpp is the "Pre-compiled Header" file.

Precompiled Header

Is a header file that includes other commonly used header files that don’t change frequently (such as standard libraries or system headers). The purpose of precompiled headers is to speed up the build process. Compilers often take a significant amount of time compiling the same headers across multiple source files. By creating a precompiled header, the compiler only needs to process the included headers once and can reuse the precompiled version in subsequent compilations.

Large projects typically include many common header files (like windows.h, iostream, etc.). Without pre-compilation, every time a source file is compiled, these headers would be processed again, which is inefficient.

By using precompiled headers (in pch.h), Visual Studio compiles these headers only once, and then stores the compiled version. When compiling other source files, the compiler simply uses the precompiled version, reducing compilation time significantly.

As you can in the image bellow, the pch.h header file is including another header called "framework.h":

The framework.h file is importing the standard Windows API header file:


DLL Attach and Detach Events

When a DLL is loaded into or unloaded from a process or thread, the system notifies the DLL through the DllMain function. These events are signaled by the values of the ul_reason_for_call parameter passed to DllMain.

DLL_PROCESS_ATTACH : This happens when the DLL is loaded into the memory space of a process. This usually occurs when:

  1. A process starts and the DLL is statically linked.

  2. A process explicitly loads the DLL using LoadLibrary.

The DllMain function is called once per process, so we should avoid performing lengthy operations here as it can delay the process startup.

DLL_PROCESS_DETACH : This happens when the DLL is unloaded from a process. This can occur when:

  1. The process terminates.

  2. The DLL is explicitly unloaded using FreeLibrary.

This is the last chance to clean up before the DLL is removed from the process memory space, so we should ensure that all dynamically allocated resources are freed to avoid memory leaks.

DLL_THREAD_ATTACH : This happens when a new thread is created in the process that uses the DLL. this event occurs only if the thread is created after the DLL is loaded.

DLL_THREAD_DETACH : This happens when a thread exits cleanly within a process that uses the DLL.


DLL Execution

There are two (practical) ways to load and execute DLLs:

  1. Loading the DLL into a local or remote running process (e.g., loading or injecting)

  2. Calling and executing the exported function using tools like rundll32.exe

To keep things simple, in this example we will use the second way, compile the DLL and use rundll32.exe to execute the entry point. the entry point for our simple DLL is HelloWorld so we run it and call the function:

// rundll32.exe DllName, ExportedFunction
rundll32.exe hello-dll.dll, HelloWorld

You can't pass custom parameters to DllMain(). The signature is fixed, and you don't call DllMain() directly anyway, only the OS does.

Your options are to either:

  1. have the DLL export a separate function that you call after injecting/loading the DLL into a process.

  2. store the data in a block of shared memory that the DLL can access after being injected/loaded.

  3. setup an inter-process communication channel between the DLL and injector/loader, such as with a named pipe or a socket.


Explicit and Implicit Linking

There are two ways to link a DLL to a program:

Implicit Linking

An executable (or another DLL) links with a small LIB file containing DLL name (no path) and exported symbols. at runtime, the loaders job is to locate the DLL and bind to the real functions implemented. if the dll is not found, the process terminates.

Explicit Linking

A process calls LoadLibrary(Ex) to load the DLL from disk when the process is already running a full path can be specified. if an error occurs a NULL handle is returned (process is not terminated). actual functions addresses are retrieved with GetProcAddress API.

Explicit linking is usually the best choice because we can load the library whenever we want. in malware development, this gives us the advantage to avoid some static analysis and detection mechanisms such as IAT (Import Address Table) analysis which will reveal the name of the functions/libraries that we are using in the code.


Dynamic Loading

So how can we dynamically (and manually) load a DLL into our program?

There are 2 Windows API functions that are heavily used when dealing with DLLs:

  1. LoadLibraryA : loads a DLL from disk. takes a ASCII DLL name string (or path) as parameter

  2. GetProcAddress : finds the address of DLL's exported function (ASCII string). takes a handle to DLL and a function name to search for as parameter.

There is another API function called GetModuleHandle that is used when importing DLLs that export the standard Windows API (like ntdll.dll or kernerl32.dll) or other DLLs that are universally loaded in the system memory. this will be discussed in later sections when we go through the process of function call obfuscation.

In this case, we just want to load our custom DLL that was compiled and written to disk, so we use LoadLibraryA instead of GetModuleHandle.

Here is a simple code that will load the hello-dll DLL from disk and execute the HelloWorld function:

#include <windows.h>
#include <stdio.h>

// Define a function pointer type for HelloWorld
typedef void (*HELLOWORLD_FUNC)();

int main() {
    // Path to the DLL on disk (relative to current executable path)
    const char* dllPath = "hello-dll.dll"; 

    // Load the DLL
    HMODULE hDll = LoadLibraryA(dllPath);
    if (hDll == NULL) {
        printf("Failed to load the DLL. Error: %lu\n", GetLastError());
        return 1;
    }
    printf("DLL loaded successfully.\n");

    // Get the address of the exported HelloWorld function
    HELLOWORLD_FUNC helloWorldFunc = (HELLOWORLD_FUNC)GetProcAddress(hDll, "HelloWorld");
    if (helloWorldFunc == NULL) {
        printf("Failed to find the HelloWorld function. Error: %lu\n", GetLastError());
        FreeLibrary(hDll);
        return 1;
    }
    printf("HelloWorld function found successfully.\n");

    // Call the HelloWorld function
    helloWorldFunc();

    // Unload the DLL
    if (FreeLibrary(hDll)) {
        printf("DLL unloaded successfully.\n");
    }
    else {
        printf("Failed to unload the DLL. Error: %lu\n", GetLastError());
    }
    return 0;
}

By default, LoadLibraryA will look for DLLs in following order:

  1. Known system DLLs (e.g., kernel32.dll)

  2. Directory of the executable (current program path)

  3. The system directory ( GetSystemDirectory, e.g. C:\Windows\System32 )


Default DLL Search Paths

When a Windows program attempts to load a DLL, it searches for the DLL in a series of predefined locations, which are referred to as DLL search paths. The order in which these locations are searched is important to understand, as it determines where Windows looks for the required DLLs when you call LoadLibrary or similar functions.

Default DLL Search Order (Windows 10/11)

The default search order for a DLL in Windows is as follows:

  1. Current Directory (Application Directory): When you call LoadLibrary without specifying a full path, Windows will first look for the DLL in the directory from which the executable was launched. If the DLL is in the same directory as the application, Windows will find it here first.

  2. System Directory (C:\Windows\System32): Windows will then look in the system directory, which is typically C:\Windows\System32 for 64-bit Windows systems. For 32-bit applications on a 64-bit Windows system, this will be C:\Windows\SysWow64.

  3. Windows Directory (C:\Windows): After the system directory, Windows searches the C:\Windows directory itself.

  4. PATH Environment Variable: Windows will then search all directories listed in the PATH environment variable, which may include custom paths set by the user or installed programs. This is the final location where Windows will look.


Modifying the Search Order

  1. Current Directory:

    • The current directory search behavior can be modified in a few ways:

      • By changing the working directory before calling LoadLibrary using SetCurrentDirectory.

      • By providing an absolute or relative path in the LoadLibrary call (e.g., LoadLibrary("C:\\path\\to\\mydll.dll")).

  2. Environment Variable PATH:

    • You can modify the PATH environment variable to include additional directories where your DLLs might be located.

    • This can be done via the System Properties (Control Panel → System → Advanced System Settings → Environment Variables) or by modifying it in your program.

  3. Explicit Path in LoadLibrary:

    • Always specify the full path of the DLL if you want to ensure the correct version is loaded:

      LoadLibrary("C:\\path\\to\\mydll.dll");
  4. Set DLL Search Order with SetDllDirectory:

    • You can use the SetDllDirectory function to add a specific directory to the search order at runtime:

      SetDllDirectory(L"C:\\path\\to\\additional\\dlls");
PreviousBasicsNextCode Execution

Last updated 5 months ago

Was this helpful?