skip to content
Pocket Change

Malware Analysis: Zero2Automated Custom Sample PART 1

/ 5 min read

Introduction

In this post, I will analyze a custom malware sample provided during the Zero2Automated course. This sample is introduced halfway through the course, after covering topics such as unpacking malware, in-depth analysis of first and second stages, developing configuration extraction techniques, evasion methods (e.g., process hollowing, propagation injection), and persistence mechanisms.


Scenario

Hi there,

During an ongoing investigation, one of our IR team members located an unknown sample on an infected machine belonging to one of our clients. While we cannot share the exact sample as we are still analyzing it to determine what data was exfiltrated, one of our backend analysts developed a YARA rule based on the malware packer. Using this rule, we identified a similar binary that appears to be an earlier version of the sample.

Would you be able to analyze it? We’re all hands on deck here, so we cannot investigate it ourselves. Developing automation tools might also be a good idea, in case the threat actors behind this malware start using something like Cutwail to distribute their samples.

The sample has been uploaded alongside this email. Good luck!


Obfuscated Strings

image

Upon initial inspection, we noticed obfuscated strings. The function sub_403100 appears to be the decryption routine. Renaming it to string_decrypt_func makes its purpose clearer.

Decryption Logic

image The decryption function uses a lookup table and shifts the index of each character by 13 places. Here’s the lookup table and the decryption logic:

lookUpTable = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890./="

def decrypt(word):
    decr_string = ""
    for i in word:
        decr_string += lookUpTable[(lookUpTable.index(i) + 13) % len(lookUpTable)]
    return decr_string

This script simulates the behavior of the cipher by shifting each character in the input string by 13 positions in the lookup table.

Binary Ninja Implementation To automate the decryption process in Binary Ninja, the following script loops through all calls to the string_decrypt_func function, extracts the obfuscated strings, decrypts them, and overwrites the original strings with their decrypted values:

lookUpTable = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890./="

def decrypt(word):
    decr_string = ""
    for i in word:
        print("index: " + i)
        decr_string += lookUpTable[(lookUpTable.index(i) + 13) % len(lookUpTable)]
    print(decr_string)
    return decr_string

# Loop through all calls to the decryption function
for func in bv.get_functions_by_name("string_decrypt_func")[0].caller_sites:
    param = func.hlil.params[0]  # Extract the parameter passed to the function
    print(param)
    s = str(param).replace('"', "")  # Remove quotes from the string
    dec_string = decrypt(s)  # Decrypt the string
    bv.write(param.value.value, dec_string.encode(), len(dec_string))  # Overwrite the original string

This script:

Extracts the obfuscated strings passed to the string_decrypt_func. Decrypts the strings using the same logic as the first script. Overwrites the obfuscated strings in the binary with their decrypted values.

image

After deobfuscation, we can see the resolved functions clearly.


image

Analyzing the code further, we observe that it locates RT_RCDATA (resource type for raw binary data) and identifies the encrypted RC4 data. The malware then loads the encrypted buffer into memory using VirtualAlloc.

image

The presence of x100 (256) is a strong indicator of RC4 encryption. This is the decryption loop, which generates the key from var_10c. The loop XORs the encrypted buffer at eax_17 with the generated keystream to produce the plaintext.

The length of the encrypted buffer is stored in edi_3.

image

Next, we analyze the function that processes the deobfuscated buffer. The malware performs multiple API calls to achieve process injection:

  1. CreateProcessA: Creates a process in a suspended state, using the CREATE_SUSPENDED flag (0x4).
    CreateProcessA(
        lpApplicationName = &lpFilename,
        lpCommandLine = 0,
        lpProcessAttributes = 0,
        lpThreadAttributes = 0,
        bInheritHandles = 0,
    
        dwCreationFlags = 4,        // CREATE_SUSPENDED
        lpEnvironment = 0,
        lpCurrentDirectory = 0,
        lpStartupInfo = &var_44c,
        lpProcessInformation = &var_45c
    );
  2. VirtualAllocEx: Allocates memory in the target process for injection.
  3. WriteProcessMemory: Copies arg1 (the decrypted PE file) into the allocated memory. The entire PE file is written into the target process.
WriteProcessMemory(
    hProcess = var_45c.hProcess,
    lpBaseAddress = allocated_memory_address,
    lpBuffer = arg1,         // pointer to decrypted PE file
    nSize = size_of_pe,
    lpNumberOfBytesWritten = NULL
);
  1. SetThreadContext: Sets the execution point to the injected PE.
  2. ResumeThread: Resumes the process, allowing the injected PE to execute.

Dynamic Analysis – Extracting the Decrypted Executable from Memory

To extract the embedded executable at runtime, we use x64dbg to intercept key stages of the decryption and injection process.


Step 1: Set Breakpoints

Set a Breakpoint on VirtualAlloc

image

Set a Breakpoint at the XOR for RC4

Set a breakpoint right at the XOR loop used for RC4 decryption, and another right before the buffer is passed to the function that uses it:

image image

Following the return address from VirtualAlloc in the dump

image

The dump, filled with obfuscated data, can be seen. Our breakpoint is currently at the XOR loop for RC4.

image

After the XOR operation completes, we can see that the memory now contains a valid executable.

image

Follow the dump in the memory map and save it using “Dump Memory to File”.

Conclusion – Bridging Static and Dynamic Analysis

By combining static and dynamic techniques, we were able to fully uncover the malware’s behavior:

  • Static analysis helped us identify the presence of encrypted data within the .rsrc section, understand the RC4-like decryption routine, and recognize the use of common injection APIs such as VirtualAlloc, WriteProcessMemory, and SetThreadContext.

  • Using this knowledge, we guided our dynamic analysis by setting strategic breakpoints on API calls and the RC4 loop. This allowed us to monitor the exact moment the payload was decrypted in memory.

  • By observing the decrypted buffer at the VirtualAlloc return address and dumping it post-decryption, we successfully extracted the injected executable for further offline analysis.

This process demonstrates the power of linking static code inspection with real-time execution, enabling us to defeat obfuscation and extract hidden payloads.