Exploitation Tools

Shelter : Mastering In-Memory Payload Encryption With Advanced ROP Techniques

Shelter is a completely weaponized sleep obfuscation technique that allows to fully encrypt your in-memory payload making an extensive use of ROP.

This crate comes with the following characteristics:

  • AES-128 encryption.
  • Whole PE encryption capability.
  • Removal of execution permission during sleep time.
  • No APC/HWBP/Timers used, exclusive use of ROP to achieve the obfuscation.
  • Use of Unwinder to achieve call stack spoofing before executing the ROP chain.
  • Different methods of execution to adapt to various circumstances.
  • Other OPSEC considerations: DInvoke_rs, indirect syscalls, string literals encryption, etc.

Content

Usage

Import this crate into your project by adding the following line to your cargo.toml:

[dependencies]
shelter = "0.1.0"

Then, compile your project on --release mode.

The main functionality of this crate has been wrapped in three functions:

  • fluctuate() allows to encrypt either the current memory region or the whole PE. This function requires the PE’s MZ bytes to be present in order to dynamically retrieve its base address.
  • fluctuate_from_address() completely encrypts the PE. This function expects as input parameter the PE’s base address.
  • fluctuate_from_pattern() also completely encrypts the PE. This function expects as input parameter a custom set of two bytes to use to determine the PE’s base address. These custom magic bytes replace the classic MZ pattern.

Whenever the whole PE is encrypted, the original sections’ memory protections are stored in the heap in order to restore them afterwards.

Shelter uses NtWaitForSingleObject to sleep. In addition to indicating how many seconds you want to sleep, you can also pass an event handle and signal it at any time to return before the timeout expires (using SetEvent for example).

Take into account that if your whole payload is encrypted (which is the whole point I guess), you will need an alternative way to signal the event in case that you have slept indefinitely.

Examples

fluctuate

The function expects the following parameters:

  • A boolean value indicating whether encrypt the whole PE or just the current memory region. Passing true requires the MZ bytes to be present in memory.
  • The number of seconds that the program will sleep for. If it is left to None, the timeout will be infinite, which means the execution will not return until the event passed to NtWaitForSingleObject is signaled.
  • An event handle to be passed to NtWaitForSingleObject. This parameter can be None. The program will get stuck if you set this parameter and the timeout both to None.
let time_to_sleep = Some(10); // Sleep for 10 seconds
let _ = shelter::fluctuate(false, time_to_sleep, None); // Encrypt only the current memory region
let time_to_sleep = Some(10); // Sleep for 10 seconds
let _ = shelter::fluctuate(true, time_to_sleep, None); // Encrypt the whole PE
pub type CreateEventW = unsafe extern "system" fn (*const SECURITY_ATTRIBUTES, i32, i32, *const u16) -> HANDLE;

let k32 = dinvoke_rs::dinvoke::get_module_base_address("kernel32.dll"); 
let create_event: CreateEventW;
let event_handle: Option<HANDLE>;
dinvoke_rs::dinvoke::dynamic_invoke!(k32,"CreateEventW",create_event,event_handle,ptr::null_mut(),0,0,ptr::null());
let time_to_sleep = None; // Sleep indefinitely
let _ = shelter::fluctuate(true, time_to_sleep, event_handle); // Encrypt the whole PE until the event is signaled

fluctuate_from_address

The function expects the following parameters:

  • The number of seconds that the program will sleep for. If it is left to None, the timeout will be infinite, which means the execution will not return until the event passed to NtWaitForSingleObject is signaled.
  • An event handle to be passed to NtWaitForSingleObject. This parameter can be None. The program will stuck if you set this parameter and the timeout both to None.
  • The base address from which the PE is mapped.

One way to use this function would be to manually map our payload with Dinvoke_rs. This way, the loader can send the payload its own base address, so then the payload can use it to obfuscate itself whenever is needed. This way, the loader can safely remove the PE’s headers in order to achieve a certain level of stealthiness.

Loader example:

let payload: Vec<u8> = your_download_function();
let mut m = dinvoke_rs::manualmap::manually_map_module(payload.as_ptr(), true).unwrap();
println!("The dll is loaded at base address 0x{:x}", m.1);
let dll_exported_function = dinvoke::get_function_address(m.1, "run");

let run: unsafe extern "Rust" fn (usize) = std::mem::transmute(dll_exported_function);
run(m.1 as usize);
Tamil S

Tamil has a great interest in the fields of Cyber Security, OSINT, and CTF projects. Currently, he is deeply involved in researching and publishing various security tools with Kali Linux Tutorials, which is quite fascinating.

Recent Posts

ShadowDumper – Advanced Techniques For LSASS Memory Extraction

Shadow Dumper is a powerful tool used to dump LSASS (Local Security Authority Subsystem Service)…

13 hours ago

Shadow-rs : Harnessing Rust’s Power For Kernel-Level Security Research

shadow-rs is a Windows kernel rootkit written in Rust, demonstrating advanced techniques for kernel manipulation…

2 weeks ago

ExecutePeFromPngViaLNK – Advanced Execution Of Embedded PE Files via PNG And LNK

Extract and execute a PE embedded within a PNG file using an LNK file. The…

3 weeks ago

Red Team Certification – A Comprehensive Guide To Advancing In Cybersecurity Operations

Embark on the journey of becoming a certified Red Team professional with our definitive guide.…

3 weeks ago

CVE-2024-5836 / CVE-2024-6778 : Chromium Sandbox Escape via Extension Exploits

This repository contains proof of concept exploits for CVE-2024-5836 and CVE-2024-6778, which are vulnerabilities within…

4 weeks ago

Rust BOFs – Unlocking New Potentials In Cobalt Strike

This took me like 4 days (+2 days for an update), but I got it…

4 weeks ago