
콜 스택 스푸핑 for Rust
Unwinder는 SilentMoonWalk 기법의 완전한 무기화(weaponization)를 제공하여 Rust에서 완전하고 안정적인 호출 스택 스푸핑(call stack spoofing)을 달성할 수 있게 합니다.
이 기법은 다음과 같은 특징을 가집니다:
SilentMoonWalk 기법을 만든 이들에게 감사를 전합니다:
그리고 물론 이 프로젝트 전체에 영감을 준 Twitter 스레드에 대해 namazso에게 큰 감사를 보냅니다.
프로젝트에 이 크레이트를 가져오려면 cargo.toml에 다음 줄을 추가하고 release 모드로 컴파일하세요:
[dependencies]
unwinder = "=0.1.4"
이 크레이트의 주요 기능은 두 개의 매크로로 래핑되어 있습니다:
call_function!() 매크로는 깨끗한 호출 스택으로 임의의 함수를 실행할 수 있게 합니다.indirect_syscall!() 매크로는 깨끗한 호출 스택으로 지정된 (간접) 시스템 콜을 실행합니다.이 매크로들을 사용하려면 std::ffi::c_void 데이터 타입을 가져와야 합니다.
두 매크로 모두 실행된 함수가 반환한 값을 검색하는 데 사용할 수 있는 *mut c_void를 반환합니다. 자세한 내용은 예제 섹션을 참조하세요.
이 매크로는 깨끗한 호출 스택으로 원하는 함수를 호출하는 데 사용됩니다. 매크로는 다음 매개변수를 기대합니다:
usize, isize 또는 포인터로 전달해야 합니다.이 매크로는 깨끗한 호출 스택으로 원하는 간접 시스템 콜을 수행하는 데 사용됩니다. 매크로는 다음 매개변수를 기대합니다:
이 두 매크로에 다양한 타입의 인수를 전달하려면 다음 사항을 고려해야 합니다:
usize로 변환할 수 있는 모든 기본 데이터 타입(u8-u64, i8-i64, bool 등)은 매크로에 직접 전달할 수 있습니다.&str 및 String)은 포인터로 전달해야 합니다.ptr::null(), ptr::null_mut() 등)는 0으로 전달됩니다(u8, u16, i32 또는 다른 타입이든 상관없습니다).let k32 = dinvoke_rs::dinvoke::get_module_base_address("kernel32.dll");
let sleep = dinvoke_rs::dinvoke::get_function_address(k32, "Sleep"); // Memory address of kernel32.dll!Sleep()
let miliseconds = 1000i32;
unwinder::call_function!(sleep, false, miliseconds);
let k32 = dinvoke_rs::dinvoke::get_module_base_address("kernel32.dll");
let open_process: isize = dinvoke_rs::dinvoke::get_function_address(k32, "Openprocess");
let desired_access: u32 = 0x1000;
let inherit = 0i32;
let pid = 20628i32;
let handle = unwinder::call_function!(open_process, false, desired_access, inherit, pid); // returns *mut c_void
let handle: HANDLE = std::mem::transmute(handle);
println!("Handle id: {:x}", handle.0);
매크로는 *mut c_void를 반환하며, 두 데이터 타입의 크기가 같으므로 이를 HANDLE로 직접 변환할 수 있습니다. 이를 통해 OpenProcess가 반환한 값, 즉 대상 프로세스에 대한 새 핸들에 접근할 수 있습니다.
let large = 0x8000000000000000 as u64; // Sleep indefinitely
let large: *mut i64 = std::mem::transmute(&large);
let alertable = false;
let ntstatus = unwinder::indirect_syscall!("NtDelayExecution", false, alertable, large); // returns *mut c_void
println!("ntstatus: {:x}", ntstatus as i32);
매크로는 *mut c_void를 반환하며, 이를 사용하여 NtDelayExecution이 반환한 NTSTATUS 값을 검색할 수 있습니다.
스푸핑 프로세스는 비정상적인 호출 스택 크기 증가 없이 몇 번이든 연결할 수 있습니다. 실행 흐름도 보존됩니다. 다음 코드는 이에 대한 예입니다:
fn main()
{
function_a();
}
fn function_a()
{
unsafe
{
let func_b = function_b as usize;
call_function!(func_b, false);
println!("function_a done.");
}
}
fn function_b()
{
unsafe
{
let func_c = function_c as usize;
call_function!(func_c, false);
println!("function_b done.")
}
}
fn function_c()
{
unsafe
{
let large = 0x0000000000000000 as u64; // Don't sleep so we return to function_b, allowing to check the execution flow preservation.
let large: *mut i64 = std::mem::transmute(&large);
let alertable = false;
let ntstatus = unwinder::indirect_syscall!("NtDelayExecution", false, alertable, large);
println!("ntstatus: {:x}", (ntstatus as usize) as i32); //NTSTATUS is a i32, although that second casting is not really required in this case.
}
}
두 매크로 모두 두 번째 매개변수를 true로 설정하면, 스푸핑 프로세스는 합법성을 높이기 위해 스레드 시작 주소의 프레임을 호출 스택에 유지하려고 시도합니다.

때때로 스레드의 시작 함수는 후속 함수에 대한 call을 수행하지 않습니다(예: 대신 jmp 명령이 실행됨). 즉, 스택에 반환 주소가 푸시되지 않습니다. 이 시나리오에서는(그리고 두 번째 매개변수를 false로 설정한 경우에도) 스푸핑된 호출 스택은 BaseThreadInitThunk의 프레임에서 시작됩니다.

기법의 구현을 테스트하기 위해 /threads 플래그와 함께 PE-sieve가 사용되었습니다. 테스트 결과는 이 크레이트의 기능을 사용할 때 호출 스택 검사가 페이로드의 존재를 드러내지 않음을 보여줍니다. 두 번째 이미지에서 볼 수 있듯이 unwinder를 사용하지 않으면 페이로드가 탐지됩니다.

이 기법은 SilentMoonWalk의 대안이 되는 호출 스택 스푸핑 방식으로, 프로그램 실행 중에 깨끗한 호출 스택을 유지할 수 있게 합니다. 이 기법의 기본 아이디어는 모듈 내부에서 호출되는 각 함수가 이전에 푸시된 반환 주소를 처리하고, 런타임에 스푸핑할 반환 주소와 동일한 프레임 크기를 가진 합법적인 함수를 찾는 것입니다. 동일한 프레임 크기의 정상적인 함수를 찾으면 그 함수 내부의 오프셋을 계산하고, 최종 주소를 사용하여 마지막 반환 주소를 교체하여 호출 스택에서 비정상적인 항목을 숨기고 스택을 계속 해제(unwind) 가능하게 유지합니다. 원래 반환 주소는 unwinder에 저장되며, return 명령이 실행되기 전에 스택의 올바른 위치로 다시 이동되어 프로그램의 정상적인 흐름을 계속할 수 있습니다.
이 기능은 완전히 작동하지만 여전히 개발 및 연구 중인 실험적 기능입니다. 따라서 이 기법을 코드에 통합하려면 반드시 테스트를 수행하세요.
스택 교체 기능을 사용하려면 cargo.toml에 다음 줄을 추가하고 release 모드로 컴파일해야 합니다:
[dependencies]
unwinder = {version = "0.1.4", features = ["Experimental"]}