microsoft/windows-rs

PVECTORED_EXCEPTION_HANDLER function cannot change the EIP content

Closed this issue · 7 comments

Summary

Set the breakpoint address in register dr0 and set dr7 to 0x55. Then use AddVectoredExceptionHandler to add a callback function (PVECTORED_EXCEPTION_HANDLER), set EIP in the callback function and return EXCEPTION_CONTINUE_SEARCH. But the end result is that the breakpoint is triggered repeatedly. The expected result is that execution starts from EIP after the callback function ends.

Crate manifest

windows=0.58.0

Crate code

use std::io::Write;
use std::mem::size_of_val;
use std::ptr::addr_of_mut;

use windows::core::imp::CloseHandle;
use windows::Win32::Foundation::{EXCEPTION_SINGLE_STEP, FALSE, INVALID_HANDLE_VALUE};
use windows::Win32::System::Diagnostics::Debug::{AddVectoredExceptionHandler, CONTEXT, CONTEXT_ALL_X86, EXCEPTION_CONTINUE_EXECUTION, EXCEPTION_CONTINUE_SEARCH, EXCEPTION_POINTERS, GetThreadContext, PVECTORED_EXCEPTION_HANDLER, RemoveVectoredExceptionHandler, SetThreadContext};
use windows::Win32::System::Diagnostics::ToolHelp::{TH32CS_SNAPTHREAD, Thread32First, Thread32Next, THREADENTRY32};
use windows::Win32::System::Threading::{GetCurrentProcessId, OpenThread, ResumeThread, SuspendThread, THREAD_ALL_ACCESS};

const ADDRESS: u32 = 0x00430A11u32;

/// 硬件hook结构体
pub struct Hook {
    count: u32,
    /// 设置回调函数时返回的地址
    result_ptr: Option<*mut core::ffi::c_void>,
    /// hook的地址
    dr0: u32,
    dr1: u32,
    dr2: u32,
    dr3: u32,
    /// 0x55
    dr7: u32,
    /// 不用hook的线程id
    no_hook_thread: Vec<u32>,
    /// 异常时的回调函数
    hook_callback: PVECTORED_EXCEPTION_HANDLER,
}

impl Hook {
    /// 构建hook对象
    pub fn new(dr1: u32,
               dr2: u32,
               dr3: u32,
               no_hook_thread: Vec<u32>) -> Self {
        Self { count: 0, result_ptr: None, dr0: ADDRESS, dr1, dr2, dr3, dr7: 0x55, no_hook_thread, hook_callback: Some(sunlight) }
    }

    /// hook
    pub fn hook(&self) -> bool {
        unsafe {
            let result = windows::Win32::System::Diagnostics::ToolHelp::CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
            if result.is_err() {
                // 如果出现错误返回失败
                return false;
            }
            let process_snapsho_handle = result.unwrap();
            if process_snapsho_handle == INVALID_HANDLE_VALUE {
                // 如果句柄无效返回失败
                return false;
            }
            // 创建一个变量存储线程信息
            let mut threadentry32: THREADENTRY32 = THREADENTRY32::default();
            threadentry32.dwSize = size_of_val(&threadentry32) as u32;
            let threadentry32_ptr = addr_of_mut!(threadentry32);
            let result1 = Thread32First(process_snapsho_handle, threadentry32_ptr);
            // 获取第一个线程
            if result1.is_ok() {
                loop {
                    if threadentry32.th32OwnerProcessID == GetCurrentProcessId() {
                        // 判断线程是否可以hook
                        if self.is_hook_thread(threadentry32.th32ThreadID) {
                            // 如果可以则获取线程权限以及上下文
                            match OpenThread(THREAD_ALL_ACCESS, FALSE, threadentry32.th32ThreadID) {
                                Ok(thread_handle) => {
                                    // 暂停线程
                                    SuspendThread(thread_handle);
                                    let mut context = CONTEXT::default();
                                    context.ContextFlags = CONTEXT_ALL_X86;
                                    let context_ptr = addr_of_mut!(context);
                                    // 获取线程上下文
                                    let _ = GetThreadContext(thread_handle, context_ptr);
                                    // 设置断点
                                    context.Dr0 = self.dr0;
                                    context.Dr1 = self.dr1;
                                    context.Dr2 = self.dr2;
                                    context.Dr3 = self.dr3;
                                    context.Dr7 = self.dr7;
                                    // 设置线程上下文
                                    let _ = SetThreadContext(thread_handle, context_ptr);
                                    ResumeThread(thread_handle);
                                    CloseHandle(thread_handle.0);
                                }
                                Err(_) => {}
                            }
                        }
                    }
                    // 如果线程还有则获取,没有跳出循环
                    if !Thread32Next(process_snapsho_handle, threadentry32_ptr).is_ok() {
                        break;
                    }
                }
            }
            // 释放句柄
            CloseHandle(process_snapsho_handle.0);
        }
        return true;
    }

    /// 安装hook函数
    pub fn set_hook_fn(&mut self) {
        if self.count == 0 {
            self.count = 1;
            unsafe {
                let handler = AddVectoredExceptionHandler(0, self.hook_callback);
                self.result_ptr = Some(handler);
            }
        }
    }

    /// 取消hokk
    pub fn unhook(&mut self) {
        self.dr0 = 0;
        self.dr1 = 0;
        self.dr2 = 0;
        self.dr3 = 0;
        self.dr7 = 0;
        let _ = self.hook();
        unsafe { RemoveVectoredExceptionHandler(self.result_ptr.unwrap()) };
        if self.count == 1 {
            self.count = 0;
        }
    }

    /// 判断线程是否可以hook
    fn is_hook_thread(&self, thread_id: u32) -> bool {
        !self.no_hook_thread.contains(&thread_id)
    }
}


///  阳光回调函数
unsafe extern "system" fn sunlight(exceptioninfo: *mut EXCEPTION_POINTERS) -> i32 {
    let mut exceptioninfo_value = exceptioninfo.read();
    let mut record_value = exceptioninfo_value.ExceptionRecord.read();
    if record_value.ExceptionCode == EXCEPTION_SINGLE_STEP {
        if (record_value.ExceptionAddress as u32) == 0x00430A11 {
            let mut context = exceptioninfo_value.ContextRecord.read();
            context.Ecx = 100;
            // Execute the assembly of the current hook
            // 00430A11 add dword ptr ds:[eax+5560],ecx
            let ptr = (context.Eax + 0x5560) as *mut u32;
            ptr.write(ptr.read() + context.Ecx);
            // Execute next assembly after changes
            // 00430A17 mov ecx,dword ptr ds:[eax+5560]
            let eip_ptr = addr_of_mut!(context.Eip);
            eip_ptr.write(context.Eip + 6);
            return EXCEPTION_CONTINUE_EXECUTION;
        }
    }
    return EXCEPTION_CONTINUE_SEARCH;
}

I suspect you may have forgotten to clear the Trap Flag (TF) in EFLAGS/RFLAGS, so you're single stepping the target. (EFlags &= 0b_0001_0000_0000)

Closing this as there doesn't appear to be a bug in the crate. Feel free to continue the discussion if needed.

For future API questions, please consider https://stackoverflow.com/questions/tagged/windows-rs.

I suspect you may have forgotten to clear the Trap Flag (TF) in EFLAGS/RFLAGS, so you're single stepping the target. (EFlags &= 0b_0001_0000_0000)

I tried to set E Flags, but it also got stuck in a loop. I saw in the C++ example that TF was not handled, so I suspect it's a bug

I don't believe SEH works in Rust at the present.

I don't believe SEH works in Rust at the present.

I don't believe SEH works in Rust at the present.

VEH is used here, and as far as I know, it only requires calling the AddVectoredExceptionHandler to add the processor and set breakpoints

Here's a simple working sample:

[package]
name = "app"
version = "0.0.0"
edition = "2021"
publish = false

[dependencies.windows]
version = "0.58.0"
features = [
    "Win32_Foundation",
    "Win32_System_Diagnostics_Debug",
    "Win32_System_Kernel"
]
use windows::Win32::{
    Foundation::EXCEPTION_SINGLE_STEP,
    System::Diagnostics::Debug::{
        AddVectoredExceptionHandler, RaiseException, EXCEPTION_CONTINUE_EXECUTION,
        EXCEPTION_CONTINUE_SEARCH, EXCEPTION_POINTERS,
    },
};

fn main() -> windows::core::Result<()> {
    println!("Start");

    unsafe {
        let _ = AddVectoredExceptionHandler(0, Some(handler));
        RaiseException(EXCEPTION_SINGLE_STEP.0 as _, 0, None);
    };

    println!("End");
    Ok(())
}

unsafe extern "system" fn handler(info: *mut EXCEPTION_POINTERS) -> i32 {
    let exception_record = info.read().ExceptionRecord.read();
    match exception_record.ExceptionCode {
        EXCEPTION_SINGLE_STEP => {
            println!("[*] Swallowing exception");
            EXCEPTION_CONTINUE_EXECUTION
        }
        _ => EXCEPTION_CONTINUE_SEARCH,
    }
}

If you're still having trouble, please create an issue with a minimal standalone repro case.

lngex commented

Here's a simple working sample:

[package]
name = "app"
version = "0.0.0"
edition = "2021"
publish = false

[dependencies.windows]
version = "0.58.0"
features = [
    "Win32_Foundation",
    "Win32_System_Diagnostics_Debug",
    "Win32_System_Kernel"
]
use windows::Win32::{
    Foundation::EXCEPTION_SINGLE_STEP,
    System::Diagnostics::Debug::{
        AddVectoredExceptionHandler, RaiseException, EXCEPTION_CONTINUE_EXECUTION,
        EXCEPTION_CONTINUE_SEARCH, EXCEPTION_POINTERS,
    },
};

fn main() -> windows::core::Result<()> {
    println!("Start");

    unsafe {
        let _ = AddVectoredExceptionHandler(0, Some(handler));
        RaiseException(EXCEPTION_SINGLE_STEP.0 as _, 0, None);
    };

    println!("End");
    Ok(())
}

unsafe extern "system" fn handler(info: *mut EXCEPTION_POINTERS) -> i32 {
    let exception_record = info.read().ExceptionRecord.read();
    match exception_record.ExceptionCode {
        EXCEPTION_SINGLE_STEP => {
            println!("[*] Swallowing exception");
            EXCEPTION_CONTINUE_EXECUTION
        }
        _ => EXCEPTION_CONTINUE_SEARCH,
    }
}

If you're still having trouble, please create an issue with a minimal standalone repro case.

I made some modifications to your sample to confirm the question I asked.

#[cfg(test)]
mod tests {
    use std::arch::asm;

    use windows::Win32::Foundation::EXCEPTION_SINGLE_STEP;
    use windows::Win32::System::Diagnostics::Debug::{AddVectoredExceptionHandler, RaiseException};

    use crate::handler;

    #[test]
    fn test1() -> windows::core::Result<()> {
        println!("Start");
        unsafe {
            let _ = AddVectoredExceptionHandler(0, Some(handler));
            RaiseException(EXCEPTION_SINGLE_STEP.0 as _, 0, None);
        };
        let value:i64;
        unsafe {asm!("mov {}, rax", out(reg) value)}
        println!("Before the end rax:{:x}", value);
        println!("End");
        Ok(())
    }
}

unsafe extern "system" fn handler(info: *mut EXCEPTION_POINTERS) -> i32 {
    let exception_record = info.read().ExceptionRecord.read();
    match exception_record.ExceptionCode {
        EXCEPTION_SINGLE_STEP => {
            let mut context = info.read().ContextRecord.read();
            println!("before change rax:{:x}", context.Rax);
            context.Rax = 0x64;
            println!("after change rax:{:x}", context.Rax);
            EXCEPTION_CONTINUE_EXECUTION
        }
        _ => EXCEPTION_CONTINUE_SEARCH,
    }
}

result

Start
before change rax:0
after change rax:64
Before the end rax:0
End

In the callback function I changed the value of Rax and printed the information before and after the change. At the same time, after the callback function ends, the value of rax is printed before the program ends, and the value before the change is consistent with the value before the program ends. This shows that modifying the rax value in the callback function was unsuccessful.

Similarly, it can be inferred that modifying rip was also unsuccessful, which resulted in infinite callbacks.