RCE Endeavors 😅

June 18, 2023

DLL Injection: Background & DLL Proxying (1/5)

Filed under: Programming,Reverse Engineering — admin @ 7:42 PM

Table of Contents:

Dynamic-link libraries (DLLs) are code modules that contain sets of functions that other executables can call. Unlike statically linked libraries, which become part of an executable during the compilation process, DLLs can live on their own outside of the application that uses them. There are two ways to perform linking with DLLs: implicit or explicit. In implicit linking, during the compilation phase, the application links with an import library file provided by the developer of the DLL. When the application is loaded, the Windows loader will identify that there is a dynamically linked reference and load the DLL into the application’s address space. On the other hand, explicit linking involves the application loading the DLL manually with the use of the LoadLibrary function, and resolving pointers to functions that it would like to call by calling GetProcAddress.

For the purpose of these demonstrating DLL injection, only explicit linking will be used. DLLs serve as a great entry point into understanding how a process behaves since the DLL will get loaded into the processes address space. The best way to perform complex process manipulation, i.e. hooking functions, modifying memory state, changing control flow, etc., is to write a DLL with your functionality and inject it into the target process. This series of posts will cover the various techniques by which this can be accomplished.

Before you can inject a DLL, you will need to create one. Like the main function of a console application, DLLs have their own entry point called DllMain.

// hinstDLL will contain the address that the
// DLL was loaded at.
BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason,
    LPVOID lpvReserved) {

    switch (fdwReason) {

    // DLL is being mapped into another processes address space.
    case DLL_PROCESS_ATTACH:
        break;

    // A thread in the process is being created
    case DLL_THREAD_ATTACH:
        break;

    // A thread in the process is terminating
    case DLL_THREAD_DETACH:
        break;

    // DLL is being unmapped from the process address space.
    case DLL_PROCESS_DETACH:
        break;
    }

    return TRUE;
}

A DllMain function that does not do anything.

This function is initially called when your DLL is loaded, but it may also be called again various times afterwards. When DllMain is called, the second parameter that is passed to it will contain a reason code that indicates what condition is causing the call. The reason code will be one of four possible values, whose purpose is described in the code above. The primary reason for these different calls is to allow developers to perform any per-process or per-thread initialization and clean up logic. As a developer, you do not need to handle all four possible states; write code just for the cases that you are interested in*.

* You actually do not need to define a DllMain function at all if your DLL is a resource-only DLL. However, that will not be the case for any examples presented in this series.

Windows processes have address spaces that are isolated from each other. While you can break this isolation and affect change in another processes address space with the help of functions like QueryVirtualMemoryInformation, VirtualAllocEx, ReadProcessMemory, WriteProcessMemory, and similar, it would be very tedious and error-prone to write an application that externally changes a lot of memory state in another process – especially if that second process isn’t expecting it. That is where DLL injection comes in; this technique allows you to execute functions that you have written in a DLL in another processes address space. There are many ways to perform DLL injection, which the rest of this series will cover. Unless explicitly stated, the DLL that is being injected will have the same code as the code shown above, but with one minor modification: it will display a message box on DLL_PROCESS_ATTACH.

// DLL is being mapped into another processes address space.
case DLL_PROCESS_ATTACH:
    MessageBoxA(nullptr, "DLL Injected!", nullptr, 0);
    break;

Debugging Injected DLLs

If you are writing code, chances are you will need to debug it at some point. This becomes even more true when you are writing code that is going to be injected into another process. Debugging will be even more complex since your code may be interacting with the target process at a very low level, i.e., directly overwriting executable code, modifying in-memory structures, calling internal functions, and the like. If you find that your target process crashes after you have injected your DLL, it can be difficult to pin down where the problem occurred. Fortunately, you can debug your injected DLL with Visual Studio. Before injecting your DLL, attach to the target process with the Visual Studio debugger by selecting Debug -> Attach to process… from the menu bar. After the debugger is attached, you can set breakpoints in your DLL code and then inject it. Once the DLL is injected, Visual Studio will load the symbol information for the DLL and allow you to debug the executing code like normal.

DLL Proxying

The first technique presented does not actually perform DLL injection in the traditional sense. Instead, DLL proxying involves replacing a DLL that your target process loads with your own. For example, if you know that your target process loads GenericDll.dll, you can rename GenericDll.dll to GenericDll2.dll, and put your DLL file as GenericDll.dll in the application path. Then the application will load your DLL, at which point you can do whatever you wish. The only caveat with DLL proxying is that your DLL must export the same functions as the original DLL. The target application is loading the DLL for a reason; it is expected that at some point the application will want to call functions that the DLL provides. If your replacement DLL does not export them then it’s very likely that bad things will happen, i.e., the application crashes. Let’s assume that GenericDll.dll is a very simple DLL with the following source code:

extern "C" {

__declspec(dllexport) void DisplayHelloWorld() {
    MessageBoxA(nullptr, "Hello World!", nullptr, 0);
}

}

BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason,
    LPVOID lpvReserved) {

    return TRUE;
}

The source code for GenericDll.dll.

Creating the proxy DLL

GenericDll.dll simply exports a function called DisplayHelloWorld. To get your proxy DLL to work, create a DisplayHelloWorld function, load the original DLL in it, use GetProcAddress on the original DisplayHelloWorld to get the function address, and perform the call on behalf of the target process. This will work, but it doesn’t scale well. DLLs can have hundreds of functions, and re-creating them – including their parameters and return types – requires a lot of boilerplate code and is very error prone. The solution to this is to use forwarding functions. When creating a DLL, you can specify a linker directive that will create an export for a function, and forward the actual implementation to another DLL.

#pragma comment(linker, "/export:DisplayHelloWorld=GenericDll2.DisplayHelloWorld")

#include <Windows.h>

BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason,
    LPVOID lpvReserved) {

    switch (fdwReason) {
    case DLL_PROCESS_ATTACH:
        MessageBoxA(nullptr, "Proxy DLL Loaded!", nullptr, 0);
    }

    return TRUE;
}

The Proxy DLL source code with a function forwarder.

The pragma directive above tells the linker that a function named DisplayHelloWorld will actually be implemented by another function called DisplayHelloWorld in a DLL named GenericDll2.dll. When creating a proxy DLL, you can specify this directive for all of the original DLL’s exports. This will allow your DLL to be loaded, while also keeping the expected functionality for the application that is loading your DLL.

No Comments »

No comments yet.

RSS feed for comments on this post. TrackBack URL

Leave a comment

 

Powered by WordPress