
Shelter는 완전한 무장형 수면 난독화 기법으로, ROP를 광범위하게 사용하여 메모리 내 페이로드를 완전히 암호화할 수 있습니다.
이 크레이트는 다음과 같은 특징을 가지고 있습니다:
이 크레이트를 프로젝트에 추가하려면 cargo.toml에 다음 줄을 추가하세요:
[dependencies]
shelter = "=0.1.2"
그런 다음 프로젝트를 --release 모드로 컴파일하세요.
이 크레이트의 주요 기능은 세 가지 함수로 래핑되었습니다:
fluctuate()는 현재 메모리 영역 또는 전체 PE를 암호화할 수 있습니다. 이 함수는 PE의 MZ 바이트가 메모리에 존재하여 기본 주소를 동적으로 검색해야 합니다.fluctuate_from_address()는 PE를 완전히 암호화합니다. 이 함수는 입력 매개변수로 PE의 기본 주소를 요구합니다.fluctuate_from_pattern()도 PE를 완전히 암호화합니다. 이 함수는 입력 매개변수로 PE의 기본 주소를 결정하는 데 사용할 사용자 정의 2바이트 세트를 요구합니다. 이 사용자 정의 매직 바이트는 기존의 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!("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); // 10초 동안 수면
let _ = shelter::fluctuate_from_address(time_to_sleep, None, base_address); // 이 특정 기본 주소에서 전체 PE 암호화
...
}
이 함수는 다음 매개변수를 요구합니다:
None으로 설정하면 타임아웃이 무한대가 되어, NtWaitForSingleObject에 전달된 이벤트가 신호를 받을 때까지 실행이 반환되지 않습니다.None이 될 수 있습니다. 이 매개변수와 타임아웃이 모두 None으로 설정되면 프로그램이 멈춥니다.[u8;2] 배열.이 함수를 만든 이유는 로더가 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 플래그가 매핑된 DLL도 탐지하지 못합니다.
이제 PE-sieve는 /data 플래그를 사용하여 실행 불가능한 메모리 영역도 검사할 수 있습니다. 공식 문서에 따르면, 이 플래그를 always로 설정하면 "많은 노이즈/거짓 양성"을 생성할 수 있습니다. 그럼에도 불구하고, 우리는 전체 PE 암호화 기능의 효과를 확인하기 위해 이 옵션을 사용하기로 결정했습니다. 이는 메모리 내 임플란트 존재의 지표가 될 수 있는 PE의 데이터 영역을 숨길 수 있기 때문입니다.

보시다시피, 첫 번째 그림에서는 PE-sieve가 실행 불가능한 메모리 페이지를 스캔할 때 .text 섹션만 난독화하는 것으로는 충분하지 않다는 것을 보여줍니다. 일부 영역에는 DLL의 존재를 드러낼 수 있는 문자열 (MZ, DOS 헤더, 섹션 이름 등)이 포함될 수 있기 때문입니다. 반면, 두 번째 그림은 Shelter의 전체 PE 난독화 메커니즘을 사용하여 이 문제를 해결할 수 있음을 보여줍니다. PE-sieve 위키에서 언급했듯이, 이 옵션은 힙에 ".data" 또는 "rdata"와 같은 문자열이 존재하는 것만으로도 가능한 임플란트 PE를 경고하지만 (해당 영역에 실제 PE 내용이 없기 때문에 아무것도 덤프할 수 없음) 수많은 거짓 양성을 초래합니다.
마지막으로, PE-sieve에는 높은 엔트로피 메모리 영역을 찾아 난독화된 임플란트의 존재를 탐지하는 비교적 새로운 옵션이 있습니다. 이 옵션(/obfusc)과 /data를 함께 사용하면 페이로드가 포함된 메모리 영역의 높은 엔트로피로 인해 페이로드 존재를 탐지할 수 있습니다 (완전히 암호화되어 있어 PE를 검색할 수는 없지만):
Shelter는 사용할 준비가 되어 있고 OPSEC을 고려하여 개발되었지만, 가까운 미래에 추가될 몇 가지 개선 사항이 있습니다:
BCryptEncrypt/BCryptDecrypt를 해당 Nt 함수로 대체.