rust
를 활용하여 stack trace를 통해 현재 함수가 어떤 모듈로 호출이 되었는지 확인하는 방법을 알아보겠다.
windows
crate를 활용하였다.
RtlCaptureStackBackTrace
winapi를 통해서 스택트레이스를 가져올 수 있다.
RtlCaptureStackBackTrace 참조
let mut back_trace: [*mut c_void; 256] = [std::ptr::null_mut();256];
let stack_cnt = unsafe {RtlCaptureStackBackTrace(0, &mut back_trace, None) };
println!("{}", stack_cnt);
stack_cnt
값은 총 stack 트레이스의 개수라고 보면된다.
back_trace
에 각 stack 프레임의 주소값이 담겨져있다.
스택프레임 주소 값을 VirtualQuery
winapi를 통해 넘기게 되면 MEMORY_BASIC_INFORMATION
구조체의 값을 얻어올 수 있다.
즉, 해당 주소가 속한 메모리 영역에 대한 정보를 얻을 수 있다.
MSDN을 참고하면 구조체의 멤버 변수인
AllocationBase
를 통해 기본 주소에 대한 포인터를 얻을 수있다. AllocationBase
를 HMODULE
로 변환을 해준 뒤 GetModuleFileNameW
api의 매개변수로 넘겨서
module의 path
를 구할 수 있다.
fn get_module_info_from_address(address: *const c_void) -> Result<String, ()>{
let mut mbi: Box<MEMORY_BASIC_INFORMATION> = Box::new(MEMORY_BASIC_INFORMATION::default());
let size = size_of::<MEMORY_BASIC_INFORMATION>(); // size를 얻어오는 방법.
let mut path_buff: [u16;MAX_PATH as usize] = [0;MAX_PATH as usize];
unsafe {
VirtualQuery(Some(address), mbi.as_mut(), size);
let hmodule: HMODULE = std::mem::transmute(mbi.AllocationBase);
GetModuleFileNameW(Some(hmodule), &mut path_buff);
};
// [u16] -> String
let res = String::from_utf16(&path_buff).unwrap();
Ok(res)
}
Error
및 실패
에 대한 부분은 하지 않았다.
위에서 얻은 back_trace
를 loop를 돌며 get_module_info_from_address
을 호출하면 각 스택트레이스의 실제 호출된 모듈의 path
를 알 수 있다.
//전체 코드
use std::{ffi::c_void, mem};
use windows::Win32::Foundation::{HMODULE, MAX_PATH};
use windows::Win32::System::Diagnostics::Debug::RtlCaptureStackBackTrace;
use windows::Win32::System::Memory::{VirtualQuery, MEMORY_BASIC_INFORMATION};
use windows::Win32::System::LibraryLoader::{GetModuleFileNameW};
fn main() {
let mut back_trace: [*mut c_void; 256] = [std::ptr::null_mut();256];
let stack_cnt = unsafe {RtlCaptureStackBackTrace(0, &mut back_trace, None) };
println!("{}", stack_cnt);
for module in back_trace.iter().take(stack_cnt as usize) {
let name = get_module_info_from_address(*module).unwrap();
println!("name: {}", name);
}
}
fn get_module_info_from_address(address: *const c_void) -> Result<String, ()>{
let mut mbi: Box<MEMORY_BASIC_INFORMATION> = Box::new(MEMORY_BASIC_INFORMATION::default());
let size = size_of::<MEMORY_BASIC_INFORMATION>(); // size를 얻어오는 방법.
let mut path_buff: [u16;MAX_PATH as usize] = [0;MAX_PATH as usize];
unsafe {
VirtualQuery(Some(address), mbi.as_mut(), size);
let hmodule: HMODULE = HMODULE(mbi.AllocationBase);
GetModuleFileNameW(Some(hmodule), &mut path_buff);
};
// [u16] -> String
let res = String::from_utf16(&path_buff).unwrap();
Ok(res)
}
결론적으로 detour
로 winapi를 전역 후킹을 하게되면 시스템 전반에서 해당 함수가 매우 많이 호출되기 때문에
성능 저하나 원치 않는 부작용이 발생할 수 있다.
이럴 때 어떤 모듈에서 호출했는지를 기준으로 필터링해주는 방식이 유용하다.
앞서 작성 한 코드를 사용하면 된다. 모듈 명을 알 수 있기 때문이다.
이렇게 하면 불필요한 오버헤드를 줄이고, 원하는 대상만 깔끔하게 후킹할 수 있는 구조를 만들 수 있다.