rust stack trace로부터 모듈 네임 얻는법

wangki·2025년 3월 20일
0

Rust

목록 보기
29/54

개요

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를 통해 기본 주소에 대한 포인터를 얻을 수있다. AllocationBaseHMODULE로 변환을 해준 뒤 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를 전역 후킹을 하게되면 시스템 전반에서 해당 함수가 매우 많이 호출되기 때문에
성능 저하나 원치 않는 부작용이 발생할 수 있다.
이럴 때 어떤 모듈에서 호출했는지를 기준으로 필터링해주는 방식이 유용하다.
앞서 작성 한 코드를 사용하면 된다. 모듈 명을 알 수 있기 때문이다.
이렇게 하면 불필요한 오버헤드를 줄이고, 원하는 대상만 깔끔하게 후킹할 수 있는 구조를 만들 수 있다.

0개의 댓글