
Obfuscação de sono baseada em ROP para evadir scanners de memória
Shelter é uma técnica de ofuscação de sono completamente instrumentalizada que permite criptografar totalmente seu payload na memória, fazendo uso extensivo de ROP.
Este crate oferece as seguintes características:
Importe este crate em seu projeto adicionando a seguinte linha ao seu cargo.toml:
[dependencies]
shelter = "=0.1.2"
Em seguida, compile seu projeto no modo --release.
A funcionalidade principal deste crate foi encapsulada em três funções:
fluctuate() permite criptografar a região de memória atual ou todo o PE. Esta função requer a presença dos bytes MZ do PE para recuperar dinamicamente seu endereço base.fluctuate_from_address() criptografa completamente o PE. Esta função espera como parâmetro de entrada o endereço base do PE.fluctuate_from_pattern() também criptografa completamente o PE. Esta função espera como parâmetro de entrada um conjunto personalizado de dois bytes para usar na determinação do endereço base do PE. Esses bytes mágicos personalizados substituem o padrão clássico MZ.Sempre que todo o PE é criptografado, as proteções de memória das seções originais são armazenadas na heap para serem restauradas posteriormente.
Shelter usa NtWaitForSingleObject para dormir. Além de indicar quantos segundos você deseja dormir, você também pode passar um handle de evento e sinalizá-lo a qualquer momento para retornar antes do tempo limite expirar (usando SetEvent, por exemplo). Leve em consideração que, se todo o seu payload estiver criptografado (que é o objetivo, suponho), você precisará de uma maneira alternativa de sinalizar o evento caso tenha dormido indefinidamente.
A função espera os seguintes parâmetros:
true requer que os bytes MZ estejam presentes na memória.None, o tempo limite será infinito, o que significa que a execução não retornará até que o evento passado para NtWaitForSingleObject seja sinalizado.None. O programa ficará travado se você definir este parâmetro e o tempo limite como None.let time_to_sleep = Some(10); // Dormir por 10 segundos
let _ = shelter::fluctuate(false, time_to_sleep, None); // Criptografar apenas a região de memória atual
let time_to_sleep = Some(10); // Dormir por 10 segundos
let _ = shelter::fluctuate(true, time_to_sleep, None); // Criptografar todo o 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; // Dormir indefinidamente
let _ = shelter::fluctuate(true, time_to_sleep, event_handle); // Criptografar todo o PE até que o evento seja sinalizado
A função espera os seguintes parâmetros:
None, o tempo limite será infinito, o que significa que a execução não retornará até que o evento passado para NtWaitForSingleObject seja sinalizado.None. O programa ficará travado se você definir este parâmetro e o tempo limite como None.Uma maneira de usar esta função seria mapear manualmente nosso payload com Dinvoke_rs. Dessa forma, o loader pode enviar ao payload seu próprio endereço base, para que então o payload possa usá-lo para se ofuscar sempre que necessário. Isso permite que o loader remova com segurança os cabeçalhos do PE para alcançar um certo nível de discrição.
Exemplo de loader:
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);
Exemplo de payload:
#[no_mangle]
fn run(base_address: usize)
{
...
let time_to_sleep = Some(10); // Dormir por 10 segundos
let _ = shelter::fluctuate_from_address(time_to_sleep, None, base_address); // Criptografar todo o PE a partir deste endereço base específico
...
}
A função espera os seguintes parâmetros:
None, o tempo limite será infinito, o que significa que a execução não retornará até que o evento passado para NtWaitForSingleObject seja sinalizado.None. O programa ficará travado se você definir este parâmetro e o tempo limite como None.[u8;2] contendo bytes mágicos personalizados para procurar a fim de obter o endereço base do PE.O objetivo de criar esta função é permitir que o loader remova o cabeçalho do PE e outras assinaturas, incluindo os bytes MZ clássicos. Dessa forma, esses bytes podem ser substituídos por um padrão personalizado que o Shelter irá procurar para recuperar o endereço base do PE.
let time_to_sleep = Some(10); // Dormir por 10 segundos
let pattern = [0x29,0x07];
let _ = shelter::fluctuate_from_pattern(time_to_sleep, None, pattern); // Criptografar todo o PE usando padrão personalizado como bytes mágicos
Para testar a implementação da técnica, foi utilizado principalmente o PE-sieve. Por padrão, o PE-sieve procura por implantes dentro de regiões de memória executáveis, o que significa que mesmo ofuscar exclusivamente a região de memória atual (.text) já é suficiente para evitar detecções:

Observe que, como estamos usando Unwinder, a pilha de chamadas é falsificada e, portanto, a flag /threads também não detecta a dll mapeada.