在 Windows 上触发缺页异常
Triggering page fault exception on Windows
我正在尝试对 Windows 上的页面错误异常进行一些测试。需求是将一些数据放到page boundary,这样读取数据会触发page fault异常。
具体来说,测试由逻辑上连续分配的7个字节(例如)组成。我需要前 5 个字节位于物理分配的页面上,但接下来的 2 个字节位于尚未物理分配的页面上。
因此读取这7个字节会触发页面错误异常,而只读取4个字节则不会。
我最初认为我应该分配两个页面,在边界上写入7字节数据,然后将第二页调出。
我可以使用一些用户模式来做到这一点 Windows API 吗?
是的,这在用户空间是可能的。您通常会在提交后使用 VirtualAlloc
and then commit part of it. You can change the protection back and forth with VirtualProtect
立即保留所有页面。
#include <cstdint>
#include <iostream>
#include <Windows.h>
int main()
{
//Retrieve currently configured page-size
SYSTEM_INFO info;
GetSystemInfo(&info);
DWORD pageSize = info.dwPageSize;
//Reserve 2 pages
void *mem = VirtualAlloc(NULL, pageSize*2, MEM_RESERVE, PAGE_NOACCESS);
//Commit first page with read/write premissions
VirtualAlloc(mem, pageSize, MEM_COMMIT, PAGE_READWRITE);
//get pointer with 5 bytes in the first page
uint8_t* ptrAcross = (uint8_t*)mem + pageSize - 5;
//Fill first 5 bytes
FillMemory(ptrAcross, 5, 0x55);
try
{
//Try to fill 6th byte
FillMemory(ptrAcross+5, 1, 0x55);
}
catch(...) // only catches the access violation when compiled with /EHa
{
std::cout << "Access violation" << std::endl;
}
std::cout << "Program finished" <<std::endl;
VirtualFree(mem, 0, MEM_RELEASE);
return 0;
}
基本上,这个答案没有添加任何比@PeterT 之一更重要的东西。对其进行了一些修改以在页面边界上写入任意字节(如我们所愿):
use std::{
mem::{transmute, MaybeUninit},
ptr,
};
use winapi::{
shared::{minwindef::TRUE, ntdef::NULL},
um::{
memoryapi::{VirtualAlloc, VirtualFree, VirtualProtect},
sysinfoapi::GetSystemInfo,
winnt::{
MEM_COMMIT, MEM_DECOMMIT, MEM_RESERVE, PAGE_EXECUTE_READWRITE,
},
},
};
fn main() {
let page_size = {
let mut sys_info = MaybeUninit::uninit();
let sys_info = unsafe {
GetSystemInfo(sys_info.as_mut_ptr());
sys_info.assume_init()
};
sys_info.dwPageSize
} as usize;
let region_base = unsafe {
VirtualAlloc(
NULL,
page_size * 2,
MEM_RESERVE | MEM_COMMIT,
PAGE_EXECUTE_READWRITE,
)
} as usize;
println!("Allocated region base: 0x{:x}", region_base);
let ud1_addr = region_base + page_size - 0x2;
print!("Writing 0f b9 27 (ud1 esp, [rdi]) to: 0x{:x}... ", ud1_addr);
let ud1_ptr = ud1_addr as *mut u8;
unsafe {
ptr::write(ud1_ptr, 0x0f);
ptr::write(ud1_ptr.add(1), 0xb9);
ptr::write(ud1_ptr.add(2), 0x27);
};
println!("ok");
let last_page_addr: usize = region_base + page_size;
print!("Decommitting the last page at 0x{:x}... ", last_page_addr);
let free_ok = unsafe { VirtualFree(last_page_addr as _, page_size, MEM_DECOMMIT) };
if free_ok == TRUE {
println!("ok. Executing: ud1 esp, [rdi]");
let ud1: extern "C" fn() = unsafe { transmute(ud1_ptr as *const ()) };
ud1();
} else {
println!("failed");
}
}
我正在尝试对 Windows 上的页面错误异常进行一些测试。需求是将一些数据放到page boundary,这样读取数据会触发page fault异常。
具体来说,测试由逻辑上连续分配的7个字节(例如)组成。我需要前 5 个字节位于物理分配的页面上,但接下来的 2 个字节位于尚未物理分配的页面上。
因此读取这7个字节会触发页面错误异常,而只读取4个字节则不会。
我最初认为我应该分配两个页面,在边界上写入7字节数据,然后将第二页调出。
我可以使用一些用户模式来做到这一点 Windows API 吗?
是的,这在用户空间是可能的。您通常会在提交后使用 VirtualAlloc
and then commit part of it. You can change the protection back and forth with VirtualProtect
立即保留所有页面。
#include <cstdint>
#include <iostream>
#include <Windows.h>
int main()
{
//Retrieve currently configured page-size
SYSTEM_INFO info;
GetSystemInfo(&info);
DWORD pageSize = info.dwPageSize;
//Reserve 2 pages
void *mem = VirtualAlloc(NULL, pageSize*2, MEM_RESERVE, PAGE_NOACCESS);
//Commit first page with read/write premissions
VirtualAlloc(mem, pageSize, MEM_COMMIT, PAGE_READWRITE);
//get pointer with 5 bytes in the first page
uint8_t* ptrAcross = (uint8_t*)mem + pageSize - 5;
//Fill first 5 bytes
FillMemory(ptrAcross, 5, 0x55);
try
{
//Try to fill 6th byte
FillMemory(ptrAcross+5, 1, 0x55);
}
catch(...) // only catches the access violation when compiled with /EHa
{
std::cout << "Access violation" << std::endl;
}
std::cout << "Program finished" <<std::endl;
VirtualFree(mem, 0, MEM_RELEASE);
return 0;
}
基本上,这个答案没有添加任何比@PeterT 之一更重要的东西。对其进行了一些修改以在页面边界上写入任意字节(如我们所愿):
use std::{
mem::{transmute, MaybeUninit},
ptr,
};
use winapi::{
shared::{minwindef::TRUE, ntdef::NULL},
um::{
memoryapi::{VirtualAlloc, VirtualFree, VirtualProtect},
sysinfoapi::GetSystemInfo,
winnt::{
MEM_COMMIT, MEM_DECOMMIT, MEM_RESERVE, PAGE_EXECUTE_READWRITE,
},
},
};
fn main() {
let page_size = {
let mut sys_info = MaybeUninit::uninit();
let sys_info = unsafe {
GetSystemInfo(sys_info.as_mut_ptr());
sys_info.assume_init()
};
sys_info.dwPageSize
} as usize;
let region_base = unsafe {
VirtualAlloc(
NULL,
page_size * 2,
MEM_RESERVE | MEM_COMMIT,
PAGE_EXECUTE_READWRITE,
)
} as usize;
println!("Allocated region base: 0x{:x}", region_base);
let ud1_addr = region_base + page_size - 0x2;
print!("Writing 0f b9 27 (ud1 esp, [rdi]) to: 0x{:x}... ", ud1_addr);
let ud1_ptr = ud1_addr as *mut u8;
unsafe {
ptr::write(ud1_ptr, 0x0f);
ptr::write(ud1_ptr.add(1), 0xb9);
ptr::write(ud1_ptr.add(2), 0x27);
};
println!("ok");
let last_page_addr: usize = region_base + page_size;
print!("Decommitting the last page at 0x{:x}... ", last_page_addr);
let free_ok = unsafe { VirtualFree(last_page_addr as _, page_size, MEM_DECOMMIT) };
if free_ok == TRUE {
println!("ok. Executing: ud1 esp, [rdi]");
let ud1: extern "C" fn() = unsafe { transmute(ud1_ptr as *const ()) };
ud1();
} else {
println!("failed");
}
}