#sleep #obfuscation #call-stack #rop #scanner

shelter

基于ROP的sleep混淆以躲避内存扫描器

2个不稳定版本

0.1.0 2024年2月17日
0.0.0 2024年2月14日

#395 in 密码学

每月49次下载

Apache-2.0

68KB
1K SLoC

Rust 733 SLoC // 0.1% comments Assembly 281 SLoC // 0.1% comments

避难所

避难所是一种完全武装的sleep混淆技术,它允许你通过大量使用ROP来完全加密内存中的有效负载。

该软件包具有以下特性

  • AES-128加密。
  • 整个PE加密功能。
  • 睡眠时移除执行权限。
  • 不使用APC/HWBP/计时器,仅使用ROP来实现混淆。
  • 使用Unwinder在执行ROP链之前实现调用堆栈欺骗。
  • 不同的执行方法以适应各种情况。
  • 其他OPSEC考虑:DInvoke_rs、间接系统调用、字符串字面量加密等。

内容



用法

通过在您的cargo.toml文件中添加以下行将此软件包导入到您的项目中:

[dependencies]
shelter = "0.1.0"

然后,在--release模式下编译您的项目。

此软件包的主要功能已封装在三个函数中

  • 波动()允许加密当前内存区域或整个PE。此函数需要PE的MZ字节以动态检索其基本地址。
  • 从地址波动()完全加密PE。此函数期望输入参数为PE的基本地址。
  • 从模式波动()也完全加密PE。此函数期望输入参数为用于确定PE基本地址的自定义字节集。这些自定义魔法字节替换了经典的MZ模式。

当整个PE被加密时,原始部分的内存保护将存储在堆中,以便之后恢复。

避难所使用NtWaitForSingleObject来睡眠。除了指示您想要睡眠多少秒外,您还可以传递一个事件句柄,并在任何时间对其进行信号以在超时之前返回(例如,使用SetEvent)。请注意,如果您已经加密了整个有效负载(我认为这是主要目的),则需要一种替代方法来在无限期睡眠的情况下触发事件。

示例

波动

该函数期望以下参数

  • 一个布尔值,表示是否加密整个PE文件或仅当前内存区域。传递 true 需要在内存中存在MZ字节。
  • 程序将休眠的秒数。如果设置为 None,超时将是无限的,这意味着执行将不会返回,直到传递给NtWaitForSingleObject的事件被信号。
  • 传递给NtWaitForSingleObject的事件句柄。此参数可以是 None。如果同时将此参数和超时都设置为 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(false, 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

从地址波动

该函数期望以下参数

  • 程序将休眠的秒数。如果设置为 None,超时将是无限的,这意味着执行将不会返回,直到传递给NtWaitForSingleObject的事件被信号。
  • 传递给NtWaitForSingleObject的事件句柄。此参数可以是 None. 如果同时设置此参数和超时为 None,程序将卡住。
  • 映射PE的基址。

使用此函数的一种方法是手动使用 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!("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);

有效载荷示例

#[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
	...
}

从模式波动

该函数期望以下参数

  • 程序将休眠的秒数。如果设置为 None,超时将是无限的,这意味着执行将不会返回,直到传递给NtWaitForSingleObject的事件被信号。
  • 传递给NtWaitForSingleObject的事件句柄。此参数可以是 None。如果同时设置此参数和超时为 None,程序将卡住。
  • 一个包含用于获取PE基址的自定义魔数字节的 [u8;2] 数组。

创建此函数的目的是允许加载程序删除PE的头部和其他签名,包括经典的MZ字节。这样,这些字节可以被Shelter将用于检索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

待办事项

尽管Shelter已准备好使用,并且它是考虑到OPSEC而开发的,但未来还将添加一些增强功能。

  • 在加密整个PE时减少熵。
  • 用相应的Nt函数替换 BCryptEncrypt/BCryptDecrypt
  • 在选件选择过程中添加一些随机性。

依赖项

~134MB
~2M SLoC