
Obsuscation du sommeil basée sur ROP pour contourner les scanners mémoire
Shelter est une technique d'obfuscation de veille complètement armée qui permet de crypter entièrement votre payload en mémoire en utilisant abondamment les ROP.
Cette crate offre les caractéristiques suivantes :
Importez cette crate dans votre projet en ajoutant la ligne suivante à votre cargo.toml :
[dependencies]
shelter = "=0.1.2"
Ensuite, compilez votre projet en mode --release.
La fonctionnalité principale de cette crate a été encapsulée dans trois fonctions :
fluctuate() permet de chiffrer soit la région mémoire actuelle, soit l'intégralité du PE. Cette fonction nécessite la présence des octets MZ du PE afin de retrouver dynamiquement son adresse de base.fluctuate_from_address() chiffre complètement le PE. Cette fonction attend comme paramètre d'entrée l'adresse de base du PE.fluctuate_from_pattern() chiffre également complètement le PE. Cette fonction attend comme paramètre d'entrée un ensemble personnalisé de deux octets à utiliser pour déterminer l'adresse de base du PE. Ces octets magiques personnalisés remplacent le motif MZ classique.Lorsque l'intégralité du PE est chiffrée, les protections mémoire des sections d'origine sont stockées dans le tas afin d'être restaurées par la suite.
Shelter utilise NtWaitForSingleObject pour dormir. En plus d'indiquer combien de secondes vous voulez dormir, vous pouvez également passer un handle d'événement et le signaler à tout moment pour revenir avant l'expiration du délai (en utilisant SetEvent par exemple). Gardez à l'esprit que si votre payload entier est chiffré (ce qui est le but, je suppose), vous aurez besoin d'un moyen alternatif pour signaler l'événement au cas où vous auriez dormi indéfiniment.
La fonction attend les paramètres suivants :
true nécessite que les octets MZ soient présents en mémoire.None, le délai sera infini, ce qui signifie que l'exécution ne reviendra pas tant que l'événement passé à NtWaitForSingleObject n'est pas signalé.None. Le programme restera bloqué si vous définissez ce paramètre et le délai tous les deux à 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
La fonction attend les paramètres suivants :
None, le délai sera infini, ce qui signifie que l'exécution ne reviendra pas tant que l'événement passé à NtWaitForSingleObject n'est pas signalé.None. Le programme restera bloqué si vous définissez ce paramètre et le délai tous les deux à None.Une façon d'utiliser cette fonction serait de mapper manuellement notre payload avec Dinvoke_rs. Ainsi, le chargeur peut envoyer au payload sa propre adresse de base, de sorte que le payload puisse l'utiliser pour s'obfusquer lui-même en cas de besoin. De cette façon, le chargeur peut supprimer en toute sécurité les en-têtes du PE pour atteindre un certain niveau de discrétion.
Exemple de chargeur :
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);
Exemple de payload :
#[no_mangle]
fn run(base_address: usize)
{
...
let time_to_sleep = Some(10); // Sleep for 10 seconds
let _ = shelter::fluctuate_from_address(time_to_sleep, None, base_address); // Encrypt the entire PE from this specific base address
...
}
La fonction attend les paramètres suivants :
None, le délai sera infini, ce qui signifie que l'exécution ne reviendra pas tant que l'événement passé à NtWaitForSingleObject n'est pas signalé.None. Le programme restera bloqué si vous définissez ce paramètre et le délai tous les deux à None.[u8;2] contenant des octets magiques personnalisés à rechercher pour obtenir l'adresse de base du PE.L'intérêt de créer cette fonction est de permettre au chargeur de supprimer l'en-tête du PE et d'autres signatures, y compris les octets MZ classiques. Ainsi, ces octets peuvent être remplacés par un motif personnalisé que Shelter recherchera pour récupérer l'adresse de base du PE.
let time_to_sleep = Some(10); // Sleep for 10 seconds
let pattern = [0x29,0x07];
let _ = shelter::fluctuate_from_pattern(time_to_sleep, None, pattern); // Encrypt the whole PE using custom pattern as magic bytes
Afin de tester l'implémentation de la technique, PE-sieve a été principalement utilisé. Par défaut, PE-sieve cherche les implants dans les régions mémoire exécutables, ce qui signifie que même obfusquer exclusivement la région mémoire actuelle (.text) suffit pour éviter les détections :
