use crate::emulator::{mmu::Perm, Cpu, Mmu};
use crate::pe::{Image, Loader};
use crate::win32::{
call_guest, run_until_sentinel as run_until_sentinel_free, vfw32, HostState, Registry,
DATA_IMPORT_BASE,
};
pub const DLL_PROCESS_ATTACH: u32 = 1;
pub const DLL_PROCESS_DETACH: u32 = 0;
const HEAP_ARENA_START: u32 = 0x6000_0000;
const HEAP_ARENA_END: u32 = 0x7000_0000;
const CONST_ARENA_START: u32 = 0x7000_0000;
const CONST_ARENA_END: u32 = 0x7010_0000;
const DATA_IMPORT_REGION_SIZE: u32 = 0x0000_1000;
const STACK_BOTTOM: u32 = 0x9000_0000;
const STACK_SIZE: u32 = 0x0010_0000; const STACK_TOP: u32 = STACK_BOTTOM + STACK_SIZE;
const THREAD_STACK_POOL_BOTTOM: u32 = 0x8000_0000;
const THREAD_STACK_POOL_SIZE: u32 = 0x1000_0000; const THREAD_STACK_POOL_TOP: u32 = THREAD_STACK_POOL_BOTTOM + THREAD_STACK_POOL_SIZE;
const TEB_BASE: u32 = 0x7FFD_E000;
const TEB_SIZE: u32 = 0x0000_1000; const SEH_END_OF_CHAIN: u32 = 0xFFFF_FFFF;
const TIB_POOL_BOTTOM: u32 = 0x7FFC_0000;
const TIB_POOL_SIZE: u32 = 0x0001_E000; const TIB_POOL_TOP: u32 = TIB_POOL_BOTTOM + TIB_POOL_SIZE;
const CHILD_IMAGE_BASE_START: u32 = 0x1000_0000;
const CHILD_HEAP_POOL_START: u32 = 0xA000_0000;
const CHILD_HEAP_POOL_SIZE: u32 = 0x1000_0000; const CHILD_HEAP_POOL_END: u32 = CHILD_HEAP_POOL_START + CHILD_HEAP_POOL_SIZE;
pub struct Sandbox {
pub mmu: Mmu,
pub cpu: Cpu,
pub registry: Registry,
pub host: HostState,
}
impl Default for Sandbox {
fn default() -> Self {
Self::new()
}
}
impl Sandbox {
#[must_use]
pub fn coverage(&self) -> &crate::coverage::CoverageMap {
&self.mmu.coverage
}
pub fn coverage_mut(&mut self) -> &mut crate::coverage::CoverageMap {
&mut self.mmu.coverage
}
#[must_use]
pub fn context(&self) -> &crate::context::Context {
&self.host.context
}
pub fn context_mut(&mut self) -> &mut crate::context::Context {
&mut self.host.context
}
#[must_use]
pub fn with_vfs(mut self, vfs: crate::context::VirtualFs) -> Self {
self.host.context.vfs = Some(vfs);
self
}
#[must_use]
pub fn with_registry(mut self, reg: crate::context::VirtualRegistry) -> Self {
self.host.context.registry = Some(reg);
self
}
pub fn new() -> Self {
let mut mmu = Mmu::new();
mmu.map(
HEAP_ARENA_START,
HEAP_ARENA_END - HEAP_ARENA_START,
Perm::R | Perm::W | Perm::X,
);
mmu.map(
CONST_ARENA_START,
CONST_ARENA_END - CONST_ARENA_START,
Perm::R | Perm::W,
);
mmu.map(DATA_IMPORT_BASE, DATA_IMPORT_REGION_SIZE, Perm::R | Perm::W);
mmu.map(STACK_BOTTOM, STACK_SIZE, Perm::R | Perm::W);
mmu.map(
THREAD_STACK_POOL_BOTTOM,
THREAD_STACK_POOL_SIZE,
Perm::R | Perm::W,
);
mmu.map(crate::win32::THUNK_BASE, 0x1_0000, Perm::R);
mmu.map(TEB_BASE, TEB_SIZE, Perm::R | Perm::W);
mmu.write_initializer(TEB_BASE, &SEH_END_OF_CHAIN.to_le_bytes())
.expect("seed TEB FS:[0]");
mmu.write_initializer(TEB_BASE + 0x18, &TEB_BASE.to_le_bytes())
.expect("seed TEB FS:[0x18] (self pointer)");
mmu.map(TIB_POOL_BOTTOM, TIB_POOL_SIZE, Perm::R | Perm::W);
mmu.map(
CHILD_HEAP_POOL_START,
CHILD_HEAP_POOL_SIZE,
Perm::R | Perm::W | Perm::X,
);
let mut cpu = Cpu::new();
cpu.regs.set_esp(STACK_TOP - 0x100); cpu.set_fs_base(TEB_BASE);
let mut registry = Registry::new();
registry.register_all();
for (_dll, _name, d) in registry.data_imports() {
mmu.write_initializer(d.addr, &d.initial.to_le_bytes())
.expect("seed data import");
}
let mut host = HostState::new(HEAP_ARENA_START, HEAP_ARENA_END)
.with_const_arena(CONST_ARENA_START, CONST_ARENA_END)
.with_thread_stack_pool(THREAD_STACK_POOL_BOTTOM, THREAD_STACK_POOL_TOP)
.with_tib_pool(TIB_POOL_BOTTOM, TIB_POOL_TOP)
.with_child_arena(
CHILD_IMAGE_BASE_START,
CHILD_HEAP_POOL_START,
CHILD_HEAP_POOL_END,
);
if let Some(t) = host.threads.get_mut(&1) {
t.tib_addr = TEB_BASE;
}
for (i, dll) in [
"kernel32.dll",
"user32.dll",
"gdi32.dll",
"advapi32.dll",
"ole32.dll",
"shell32.dll",
"shlwapi.dll",
"comctl32.dll",
"winmm.dll",
"msvcrt.dll",
"msvcr71.dll",
"msvcr80.dll",
"msvcr90.dll",
"pncrt.dll",
"mfplat.dll",
"version.dll",
"vfw32.dll",
]
.iter()
.enumerate()
{
let handle = 0x7800_0000u32.wrapping_add((i as u32) * 0x10_0000);
host.modules.insert((*dll).to_string(), handle);
}
if let Ok(factory) =
crate::com::mint_host_mem_allocator_class_factory(&mut host, &mut mmu, ®istry)
{
host.com
.register_class_factory(crate::com::CLSID_MEMORY_ALLOCATOR, factory);
}
Sandbox {
mmu,
cpu,
registry,
host,
}
}
pub fn with_rand_seed(mut self, seed: u32) -> Self {
self.host.rand_state = seed;
self
}
pub fn set_rand_seed(&mut self, seed: u32) {
self.host.rand_state = seed;
}
pub fn set_command_line(&mut self, cmdline: &str) -> Result<(), crate::Error> {
let mut bytes = cmdline.as_bytes().to_vec();
bytes.push(0);
let addr = self
.host
.arena_const_alloc(bytes.len() as u32)
.map_err(crate::Error::Win32)?;
self.mmu.write_initializer(addr, &bytes)?;
self.host.command_line_ptr = addr;
Ok(())
}
pub fn rand_seed(&self) -> u32 {
self.host.rand_state
}
pub fn load(&mut self, name: &str, bytes: &[u8]) -> Result<Image, crate::Error> {
let mut loader = Loader::new(&mut self.mmu, &mut self.registry, &mut self.host);
let img = loader.load(name, bytes)?;
self.host.primary_module_base = img.image_base;
self.host
.modules
.insert(name.to_ascii_lowercase(), img.image_base);
Ok(img)
}
pub fn load_fail_soft(
&mut self,
name: &str,
bytes: &[u8],
) -> Result<(Image, Vec<(String, String)>), crate::Error> {
let mut options = crate::pe::LoadOptions {
imports: crate::pe::imports::ResolveMode::FailSoft,
fail_soft_log: Some(Vec::new()),
target_image_base: None,
};
let mut loader = Loader::new(&mut self.mmu, &mut self.registry, &mut self.host);
let img = loader.load_with_options(name, bytes, &mut options)?;
self.host.primary_module_base = img.image_base;
self.host
.modules
.insert(name.to_ascii_lowercase(), img.image_base);
Ok((img, options.fail_soft_log.unwrap_or_default()))
}
pub fn call_dll_main(&mut self, image: &Image, reason: u32) -> Result<u32, crate::Error> {
let h_module = image.image_base;
let lpv_reserved = 0u32;
let target = image.export("DllMain").unwrap_or(image.entry_point);
if target == 0 {
return Err(crate::Error::Win32(
crate::win32::Win32Error::InvalidArgument {
stub: "call_dll_main",
reason: format!(
"no DllMain export and no PE entry point in {:?}",
image.name
),
},
));
}
call_guest(
&mut self.cpu,
&mut self.mmu,
&self.registry,
&mut self.host,
target,
&[h_module, reason, lpv_reserved],
)
}
pub fn call_export(
&mut self,
image: &Image,
name: &str,
args: &[u32],
) -> Result<u32, crate::Error> {
let target = image.export(name).ok_or_else(|| {
crate::Error::Win32(crate::win32::Win32Error::InvalidArgument {
stub: "call_export",
reason: format!("export {name:?} not found in {:?}", image.name),
})
})?;
call_guest(
&mut self.cpu,
&mut self.mmu,
&self.registry,
&mut self.host,
target,
args,
)
}
pub fn call_entry_point(&mut self, image: &Image) -> Result<u32, crate::Error> {
if image.entry_point == 0 {
return Err(crate::Error::Win32(
crate::win32::Win32Error::InvalidArgument {
stub: "call_entry_point",
reason: format!("no PE entry point in {:?}", image.name),
},
));
}
call_guest(
&mut self.cpu,
&mut self.mmu,
&self.registry,
&mut self.host,
image.entry_point,
&[],
)
}
pub fn run_until_sentinel(&mut self) -> Result<(), crate::Error> {
run_until_sentinel_free(&mut self.cpu, &mut self.mmu, &self.registry, &mut self.host)
}
pub fn install_codec(&mut self, image: &Image) -> Result<(), crate::Error> {
let dp = image.export("DriverProc").ok_or_else(|| {
crate::Error::Win32(crate::win32::Win32Error::InvalidArgument {
stub: "install_codec",
reason: format!("DriverProc not exported by {:?}", image.name),
})
})?;
self.host.default_driver_proc = dp;
Ok(())
}
pub fn ic_open(
&mut self,
fcc_type: u32,
fcc_handler: u32,
mode: u32,
) -> Result<u32, crate::Error> {
vfw32::ic_open(
&mut self.cpu,
&mut self.mmu,
&self.registry,
&mut self.host,
fcc_type,
fcc_handler,
mode,
)
}
pub fn ic_close(&mut self, hic: u32) -> Result<u32, crate::Error> {
vfw32::ic_close(
&mut self.cpu,
&mut self.mmu,
&self.registry,
&mut self.host,
hic,
)
}
pub fn ic_get_info(&mut self, hic: u32, cb: u32) -> Result<Vec<u8>, crate::Error> {
vfw32::ic_get_info(
&mut self.cpu,
&mut self.mmu,
&self.registry,
&mut self.host,
hic,
cb,
)
}
pub fn ic_decompress_query(
&mut self,
hic: u32,
input: &vfw32::Bih,
output: Option<&vfw32::Bih>,
) -> Result<u32, crate::Error> {
vfw32::ic_decompress_query(
&mut self.cpu,
&mut self.mmu,
&self.registry,
&mut self.host,
hic,
input,
output,
)
}
pub fn ic_decompress_get_format(
&mut self,
hic: u32,
input: &vfw32::Bih,
) -> Result<(u32, vfw32::Bih), crate::Error> {
vfw32::ic_decompress_get_format(
&mut self.cpu,
&mut self.mmu,
&self.registry,
&mut self.host,
hic,
input,
)
}
pub fn ic_decompress_begin(
&mut self,
hic: u32,
input: &vfw32::Bih,
output: &vfw32::Bih,
) -> Result<u32, crate::Error> {
vfw32::ic_decompress_begin(
&mut self.cpu,
&mut self.mmu,
&self.registry,
&mut self.host,
hic,
input,
output,
)
}
pub fn ic_decompress_end(&mut self, hic: u32) -> Result<u32, crate::Error> {
vfw32::ic_decompress_end(
&mut self.cpu,
&mut self.mmu,
&self.registry,
&mut self.host,
hic,
)
}
#[cfg(feature = "trace")]
pub fn watch(&mut self, addr: u32, size: u32, mode: crate::trace::WatchMode) {
self.mmu.trace.watch(addr, size, mode);
}
#[cfg(feature = "trace")]
pub fn unwatch(&mut self, addr: u32, size: u32) {
self.mmu.trace.unwatch(addr, size);
}
#[cfg(feature = "trace")]
pub fn set_exec_trace(&mut self, on: bool) {
self.mmu.trace.exec_on = on;
}
#[cfg(feature = "trace")]
pub fn set_trace_sink(&mut self, sink: Box<dyn std::io::Write + Send>) {
self.mmu.trace.set_sink(sink);
}
pub fn dll_get_class_object(
&mut self,
image: &crate::pe::Image,
clsid: crate::com::Guid,
riid: crate::com::Guid,
) -> Result<u32, crate::Error> {
let target = image.export("DllGetClassObject").ok_or_else(|| {
crate::Error::Win32(crate::win32::Win32Error::InvalidArgument {
stub: "dll_get_class_object",
reason: format!("DllGetClassObject not exported by {:?}", image.name),
})
})?;
let scratch = self.host.arena_alloc(36).map_err(crate::Error::Win32)?;
clsid
.stage(&mut self.mmu, scratch)
.map_err(crate::Error::Trap)?;
riid.stage(&mut self.mmu, scratch + 16)
.map_err(crate::Error::Trap)?;
self.mmu
.write_initializer(scratch + 32, &0u32.to_le_bytes())
.map_err(crate::Error::Trap)?;
let hr = call_guest(
&mut self.cpu,
&mut self.mmu,
&self.registry,
&mut self.host,
target,
&[scratch, scratch + 16, scratch + 32],
)?;
if hr != crate::com::S_OK {
return Err(crate::Error::Win32(
crate::win32::Win32Error::InvalidArgument {
stub: "dll_get_class_object",
reason: format!("DllGetClassObject returned HRESULT {hr:#010x}"),
},
));
}
let out_ptr = self.mmu.load32(scratch + 32).map_err(crate::Error::Trap)?;
if out_ptr == 0 {
return Err(crate::Error::Win32(
crate::win32::Win32Error::InvalidArgument {
stub: "dll_get_class_object",
reason: "DllGetClassObject succeeded but *ppv is NULL".into(),
},
));
}
self.host.com.intern(out_ptr, Some(riid));
if riid == crate::com::IID_ICLASSFACTORY {
self.host.com.register_class_factory(clsid, out_ptr);
}
Ok(out_ptr)
}
pub fn co_create_instance(
&mut self,
clsid: crate::com::Guid,
riid: crate::com::Guid,
) -> Result<u32, crate::Error> {
let factory = self.host.com.lookup_class_factory(&clsid).ok_or_else(|| {
crate::Error::Win32(crate::win32::Win32Error::InvalidArgument {
stub: "co_create_instance",
reason: format!(
"CLSID {clsid} not registered; \
call dll_get_class_object first"
),
})
})?;
let scratch = self.host.arena_alloc(20).map_err(crate::Error::Win32)?;
riid.stage(&mut self.mmu, scratch)
.map_err(crate::Error::Trap)?;
self.mmu
.write_initializer(scratch + 16, &0u32.to_le_bytes())
.map_err(crate::Error::Trap)?;
let r = crate::com::call::call_method(
&mut self.cpu,
&mut self.mmu,
&self.registry,
&mut self.host,
factory,
crate::com::SLOT_CLASS_FACTORY_CREATE_INSTANCE,
&[0, scratch, scratch + 16],
)?;
if r != crate::com::S_OK {
return Err(crate::Error::Win32(
crate::win32::Win32Error::InvalidArgument {
stub: "co_create_instance",
reason: format!("CreateInstance returned HRESULT {r:#010x}"),
},
));
}
let out = self.mmu.load32(scratch + 16).map_err(crate::Error::Trap)?;
if out != 0 {
self.host.com.intern(out, Some(riid));
}
Ok(out)
}
pub fn query_interface(
&mut self,
obj: u32,
riid: crate::com::Guid,
) -> Result<u32, crate::Error> {
let scratch = self.host.arena_alloc(20).map_err(crate::Error::Win32)?;
riid.stage(&mut self.mmu, scratch)
.map_err(crate::Error::Trap)?;
self.mmu
.write_initializer(scratch + 16, &0u32.to_le_bytes())
.map_err(crate::Error::Trap)?;
let r = crate::com::call::query_interface(
&mut self.cpu,
&mut self.mmu,
&self.registry,
&mut self.host,
obj,
scratch,
scratch + 16,
)?;
if r != crate::com::S_OK {
return Err(crate::Error::Win32(
crate::win32::Win32Error::InvalidArgument {
stub: "query_interface",
reason: format!("QueryInterface returned HRESULT {r:#010x}"),
},
));
}
let out = self.mmu.load32(scratch + 16).map_err(crate::Error::Trap)?;
if out != 0 {
self.host.com.intern(out, Some(riid));
}
Ok(out)
}
pub fn mint_host_filter_graph(&mut self) -> Result<u32, crate::Error> {
crate::com::mint_host_filter_graph(&mut self.host, &mut self.mmu, &self.registry)
}
pub fn mint_host_output_pin(&mut self, amt_addr: u32) -> Result<u32, crate::Error> {
crate::com::host_iface::mint_host_output_pin(
&mut self.host,
&mut self.mmu,
&self.registry,
amt_addr,
)
}
pub fn mint_host_output_pin_with_connection(
&mut self,
amt_addr: u32,
connected_pin: u32,
) -> Result<u32, crate::Error> {
crate::com::host_iface::mint_host_output_pin_with_connection(
&mut self.host,
&mut self.mmu,
&self.registry,
amt_addr,
connected_pin,
)
}
pub fn query_pin_info_call_count(&self) -> usize {
crate::com::host_iface::query_pin_info_call_count(&self.host)
}
pub fn query_filter_info_call_count(&self) -> usize {
crate::com::host_iface::query_filter_info_call_count(&self.host)
}
pub fn query_pin_info_calls(&self) -> Vec<u32> {
crate::com::host_iface::query_pin_info_calls(&self.host)
}
pub fn query_filter_info_calls(&self) -> Vec<u32> {
crate::com::host_iface::query_filter_info_calls(&self.host)
}
pub fn clear_query_info_log(&self) {
crate::com::host_iface::clear_query_info_log(&self.host)
}
pub fn mint_host_mem_allocator(
&mut self,
pool_size: u32,
sample_capacity: u32,
media_type_ptr: u32,
) -> Result<u32, crate::Error> {
crate::com::mint_host_mem_allocator(
&mut self.host,
&mut self.mmu,
&self.registry,
pool_size,
sample_capacity,
media_type_ptr,
)
}
pub fn mint_host_mem_allocator_class_factory(&mut self) -> Result<u32, crate::Error> {
crate::com::mint_host_mem_allocator_class_factory(
&mut self.host,
&mut self.mmu,
&self.registry,
)
}
pub fn mint_host_media_sample(
&mut self,
data_capacity: u32,
media_type_ptr: u32,
) -> Result<u32, crate::Error> {
crate::com::mint_host_media_sample(
&mut self.host,
&mut self.mmu,
&self.registry,
data_capacity,
media_type_ptr,
)
}
pub fn media_sample_set_payload(
&mut self,
sample: u32,
payload: &[u8],
sync_point: bool,
) -> Result<(), crate::Error> {
crate::com::media_sample_set_payload(&mut self.mmu, sample, payload, sync_point)
}
pub fn host_iface_r31_mint_input_pin_pair(&mut self) -> Result<(u32, u32), crate::Error> {
crate::com::host_iface_r31::mint_host_input_pin_pair(
&mut self.host,
&mut self.mmu,
&self.registry,
)
}
pub fn host_iface_r31_mint_base_filter(&mut self, input_pin: u32) -> Result<u32, crate::Error> {
crate::com::host_iface_r31::mint_host_base_filter(
&mut self.host,
&mut self.mmu,
&self.registry,
input_pin,
)
}
pub fn pop_received_sample(&self) -> Option<crate::com::host_iface_r31::ReceivedSample> {
crate::com::host_iface_r31::pop_sample(&self.host)
}
pub fn received_samples_len(&self) -> usize {
crate::com::host_iface_r31::queue_len(&self.host)
}
pub fn last_set_properties(&self) -> Option<crate::com::AllocatorPropertiesCapture> {
crate::com::last_set_properties(&self.host)
}
pub fn all_set_properties(&self) -> Vec<crate::com::AllocatorPropertiesCapture> {
crate::com::all_set_properties(&self.host)
}
pub fn clear_set_properties_log(&self) {
crate::com::clear_set_properties_log(&self.host)
}
pub fn com_add_ref(&mut self, obj: u32) -> Result<u32, crate::Error> {
crate::com::call::add_ref(
&mut self.cpu,
&mut self.mmu,
&self.registry,
&mut self.host,
obj,
)
}
pub fn com_release(&mut self, obj: u32) -> Result<u32, crate::Error> {
crate::com::call::release(
&mut self.cpu,
&mut self.mmu,
&self.registry,
&mut self.host,
obj,
)
}
#[allow(clippy::too_many_arguments)]
pub fn ic_decompress(
&mut self,
hic: u32,
flags: u32,
input_bih: &vfw32::Bih,
input_bytes: &[u8],
output_bih: &vfw32::Bih,
output_capacity: u32,
) -> Result<(u32, Vec<u8>), crate::Error> {
vfw32::ic_decompress(
&mut self.cpu,
&mut self.mmu,
&self.registry,
&mut self.host,
hic,
flags,
input_bih,
input_bytes,
output_bih,
output_capacity,
)
}
pub fn ic_compress_query(
&mut self,
hic: u32,
input: &vfw32::Bih,
output: Option<&vfw32::Bih>,
) -> Result<u32, crate::Error> {
vfw32::ic_compress_query(
&mut self.cpu,
&mut self.mmu,
&self.registry,
&mut self.host,
hic,
input,
output,
)
}
pub fn ic_compress_get_format(
&mut self,
hic: u32,
input: &vfw32::Bih,
) -> Result<(u32, vfw32::Bih), crate::Error> {
vfw32::ic_compress_get_format(
&mut self.cpu,
&mut self.mmu,
&self.registry,
&mut self.host,
hic,
input,
)
}
pub fn ic_compress_get_size(
&mut self,
hic: u32,
input: &vfw32::Bih,
output: &vfw32::Bih,
) -> Result<u32, crate::Error> {
vfw32::ic_compress_get_size(
&mut self.cpu,
&mut self.mmu,
&self.registry,
&mut self.host,
hic,
input,
output,
)
}
pub fn ic_compress_begin(
&mut self,
hic: u32,
input: &vfw32::Bih,
output: &vfw32::Bih,
) -> Result<u32, crate::Error> {
vfw32::ic_compress_begin(
&mut self.cpu,
&mut self.mmu,
&self.registry,
&mut self.host,
hic,
input,
output,
)
}
pub fn ic_compress_end(&mut self, hic: u32) -> Result<u32, crate::Error> {
vfw32::ic_compress_end(
&mut self.cpu,
&mut self.mmu,
&self.registry,
&mut self.host,
hic,
)
}
#[allow(clippy::too_many_arguments)]
pub fn ic_compress(
&mut self,
hic: u32,
flags: u32,
input_bih: &vfw32::Bih,
input_bytes: &[u8],
output_bih: &vfw32::Bih,
output_capacity: u32,
ckid: u32,
frame_num: i32,
frame_size_limit: u32,
quality: u32,
prev_bih_opt: Option<&vfw32::Bih>,
prev_bytes_opt: Option<&[u8]>,
) -> Result<vfw32::CompressOutcome, crate::Error> {
vfw32::ic_compress(
&mut self.cpu,
&mut self.mmu,
&self.registry,
&mut self.host,
hic,
flags,
input_bih,
input_bytes,
output_bih,
output_capacity,
ckid,
frame_num,
frame_size_limit,
quality,
prev_bih_opt,
prev_bytes_opt,
)
}
pub fn ic_get_state(&mut self, hic: u32, dst_buf: &mut [u8]) -> Result<u32, crate::Error> {
vfw32::ic_get_state(
&mut self.cpu,
&mut self.mmu,
&self.registry,
&mut self.host,
hic,
dst_buf,
)
}
pub fn ic_set_state(&mut self, hic: u32, src_buf: &[u8]) -> Result<(), crate::Error> {
vfw32::ic_set_state(
&mut self.cpu,
&mut self.mmu,
&self.registry,
&mut self.host,
hic,
src_buf,
)
}
pub fn msadds32_patch_helper_addref(
&mut self,
image_base: u32,
value: u32,
) -> Result<(), crate::Error> {
const RVA_HELPER_ADDREF: u32 = 0x5cea;
let va = image_base.wrapping_add(RVA_HELPER_ADDREF);
let mut patch = [0u8; 6];
patch[0] = 0xb8; patch[1..5].copy_from_slice(&value.to_le_bytes());
patch[5] = 0xc3; self.mmu
.write_initializer(va, &patch)
.map_err(crate::Error::Trap)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::emulator::isa_int::RET_SENTINEL;
use crate::emulator::regs::Reg32;
use crate::pe::test_image::build_minimal_dll;
#[test]
fn load_synth_dll_and_run_dll_main_returns_to_sentinel() {
let bytes = build_minimal_dll();
let mut sb = Sandbox::new();
let img = sb.load("synth.dll", &bytes).unwrap();
sb.cpu.regs.set32(Reg32::Eax, 1);
let ret = sb.call_dll_main(&img, DLL_PROCESS_ATTACH).unwrap();
assert_eq!(ret, 1);
assert_eq!(sb.cpu.regs.eip, RET_SENTINEL);
}
#[test]
fn calling_through_iat_thunk_invokes_kernel32_stub() {
let mut sb = Sandbox::new();
let thunk = sb
.registry
.resolve("kernel32.dll", "GetProcessHeap")
.unwrap();
sb.mmu.map(0x1000, 0x1000, Perm::R | Perm::X);
sb.cpu.push32(&mut sb.mmu, RET_SENTINEL).unwrap();
sb.cpu.regs.eip = thunk;
sb.run_until_sentinel().unwrap();
assert_eq!(sb.cpu.regs.get32(Reg32::Eax), 0xDEAD_BEEF);
}
#[test]
fn wait_for_single_object_blocks_until_set_event() {
let mut sb = Sandbox::new();
let h = sb
.host
.scheduler
.insert_object(crate::sched::WaitObject::Event {
signaled: false,
manual_reset: false,
});
let mut t2 = crate::win32::ThreadState::new(2, 1);
t2.parked_cpu = Some(crate::emulator::Cpu::new());
t2.status = crate::sched::ThreadStatus::Waiting;
t2.wait = Some(crate::sched::WaitCondition::Object {
handle: h,
timeout_after: None,
});
sb.host.threads.insert(2, t2);
let set_event = sb.registry.resolve("kernel32.dll", "SetEvent").unwrap();
sb.cpu.push32(&mut sb.mmu, h).unwrap();
sb.cpu.push32(&mut sb.mmu, RET_SENTINEL).unwrap();
sb.cpu.regs.eip = set_event;
sb.run_until_sentinel().unwrap();
let t = sb.host.threads.get(&2).unwrap();
assert!(
matches!(t.status, crate::sched::ThreadStatus::Ready),
"expected thread 2 Ready, got {:?}",
t.status
);
assert!(t.wait.is_none(), "wait condition must be cleared");
match sb.host.scheduler.objects.get(&h).unwrap() {
crate::sched::WaitObject::Event { signaled, .. } => assert!(!*signaled),
_ => panic!(),
}
}
#[test]
fn mutex_transfers_ownership_on_release() {
let mut sb = Sandbox::new();
let h = sb
.host
.scheduler
.insert_object(crate::sched::WaitObject::Mutex {
owner: Some(1),
recursion: 1,
});
let mut t2 = crate::win32::ThreadState::new(2, 1);
t2.parked_cpu = Some(crate::emulator::Cpu::new());
t2.status = crate::sched::ThreadStatus::Waiting;
t2.wait = Some(crate::sched::WaitCondition::Object {
handle: h,
timeout_after: None,
});
sb.host.threads.insert(2, t2);
let release = sb.registry.resolve("kernel32.dll", "ReleaseMutex").unwrap();
sb.cpu.push32(&mut sb.mmu, h).unwrap();
sb.cpu.push32(&mut sb.mmu, RET_SENTINEL).unwrap();
sb.cpu.regs.eip = release;
sb.run_until_sentinel().unwrap();
assert_eq!(sb.cpu.regs.get32(Reg32::Eax), 1, "release succeeds");
match sb.host.scheduler.objects.get(&h).unwrap() {
crate::sched::WaitObject::Mutex { owner, recursion } => {
assert_eq!(*owner, Some(2));
assert_eq!(*recursion, 1);
}
_ => panic!(),
}
let t = sb.host.threads.get(&2).unwrap();
assert!(matches!(t.status, crate::sched::ThreadStatus::Ready));
}
#[test]
fn semaphore_release_wakes_waiters_and_bumps_count() {
let mut sb = Sandbox::new();
let h = sb
.host
.scheduler
.insert_object(crate::sched::WaitObject::Semaphore { count: 0, max: 10 });
for tid in [2u32, 3u32] {
let mut t = crate::win32::ThreadState::new(tid, 1);
t.parked_cpu = Some(crate::emulator::Cpu::new());
t.status = crate::sched::ThreadStatus::Waiting;
t.wait = Some(crate::sched::WaitCondition::Object {
handle: h,
timeout_after: None,
});
sb.host.threads.insert(tid, t);
}
let release = sb
.registry
.resolve("kernel32.dll", "ReleaseSemaphore")
.unwrap();
sb.mmu.map(0x500_0000, 0x1000, Perm::R | Perm::W);
let p_prev = 0x500_0000u32;
sb.cpu.push32(&mut sb.mmu, p_prev).unwrap();
sb.cpu.push32(&mut sb.mmu, 2u32).unwrap();
sb.cpu.push32(&mut sb.mmu, h).unwrap();
sb.cpu.push32(&mut sb.mmu, RET_SENTINEL).unwrap();
sb.cpu.regs.eip = release;
sb.run_until_sentinel().unwrap();
assert_eq!(sb.cpu.regs.get32(Reg32::Eax), 1);
assert_eq!(sb.mmu.load32(p_prev).unwrap(), 0);
match sb.host.scheduler.objects.get(&h).unwrap() {
crate::sched::WaitObject::Semaphore { count, .. } => {
assert_eq!(*count, 0, "both waiters consumed their signals");
}
_ => panic!(),
}
for tid in [2u32, 3u32] {
let t = sb.host.threads.get(&tid).unwrap();
assert!(matches!(t.status, crate::sched::ThreadStatus::Ready));
}
}
#[test]
fn create_process_a_loads_real_child_pe_from_vfs() {
use crate::pe::test_image::build_minimal_dll;
let mut sb = Sandbox::default();
let dll_bytes = build_minimal_dll();
let child_path = "c:\\setup\\helper.exe";
let mut vfs = crate::context::VirtualFs::new();
vfs.insert(child_path, dll_bytes);
sb.host.context.vfs = Some(vfs);
sb.mmu.map(0x500_0000, 0x1000, Perm::R | Perm::W);
let pi = 0x500_0000u32;
sb.mmu.map(0x500_1000, 0x1000, Perm::R | Perm::W);
let app_ptr = 0x500_1000u32;
sb.mmu
.write_initializer(app_ptr, child_path.as_bytes())
.unwrap();
sb.mmu.store8(app_ptr + child_path.len() as u32, 0).unwrap();
let create_proc = sb
.registry
.resolve("kernel32.dll", "CreateProcessA")
.unwrap();
for &a in [app_ptr, 0u32, 0, 0, 0, 0, 0, 0, 0, pi].iter().rev() {
sb.cpu.push32(&mut sb.mmu, a).unwrap();
}
sb.cpu.push32(&mut sb.mmu, RET_SENTINEL).unwrap();
sb.cpu.regs.eip = create_proc;
sb.run_until_sentinel().unwrap();
assert_eq!(sb.cpu.regs.get32(Reg32::Eax), 1, "CreateProcessA TRUE");
let child_pid = sb.mmu.load32(pi + 8).unwrap();
let child_tid = sb.mmu.load32(pi + 12).unwrap();
let p = sb
.host
.processes
.get(&child_pid)
.expect("child process present");
assert_ne!(
p.image_base, 0,
"child PE was rebased into a fresh image slot"
);
assert_eq!(p.parent_pid, 1);
let t = sb
.host
.threads
.get(&child_tid)
.expect("child primary thread present");
assert!(matches!(
t.status,
crate::sched::ThreadStatus::Ready | crate::sched::ThreadStatus::Running
));
}
#[test]
fn enter_leave_critical_section_round_trip() {
let mut sb = Sandbox::new();
sb.mmu.map(0x400_0000, 0x1000, Perm::R | Perm::W);
let cs = 0x400_0000u32;
let enter = sb
.registry
.resolve("kernel32.dll", "EnterCriticalSection")
.unwrap();
let leave = sb
.registry
.resolve("kernel32.dll", "LeaveCriticalSection")
.unwrap();
sb.cpu.push32(&mut sb.mmu, cs).unwrap();
sb.cpu.push32(&mut sb.mmu, RET_SENTINEL).unwrap();
sb.cpu.regs.eip = enter;
sb.run_until_sentinel().unwrap();
let h = sb.host.scheduler.critical_sections[&cs];
match sb.host.scheduler.objects.get(&h).unwrap() {
crate::sched::WaitObject::CriticalSection {
owner, recursion, ..
} => {
assert_eq!(*owner, Some(1));
assert_eq!(*recursion, 1);
}
_ => panic!(),
}
sb.cpu.push32(&mut sb.mmu, cs).unwrap();
sb.cpu.push32(&mut sb.mmu, RET_SENTINEL).unwrap();
sb.cpu.regs.eip = enter;
sb.run_until_sentinel().unwrap();
match sb.host.scheduler.objects.get(&h).unwrap() {
crate::sched::WaitObject::CriticalSection { recursion, .. } => {
assert_eq!(*recursion, 2);
}
_ => panic!(),
}
sb.cpu.push32(&mut sb.mmu, cs).unwrap();
sb.cpu.push32(&mut sb.mmu, RET_SENTINEL).unwrap();
sb.cpu.regs.eip = leave;
sb.run_until_sentinel().unwrap();
sb.cpu.push32(&mut sb.mmu, cs).unwrap();
sb.cpu.push32(&mut sb.mmu, RET_SENTINEL).unwrap();
sb.cpu.regs.eip = leave;
sb.run_until_sentinel().unwrap();
match sb.host.scheduler.objects.get(&h).unwrap() {
crate::sched::WaitObject::CriticalSection {
owner, recursion, ..
} => {
assert_eq!(*owner, None);
assert_eq!(*recursion, 0);
}
_ => panic!(),
}
}
#[test]
fn create_thread_spawns_a_real_runnable_thread() {
let mut sb = Sandbox::new();
sb.mmu.map(0x1000, 0x1000, Perm::R | Perm::X);
let proc_va = 0x1010u32;
let proc_code: [u8; 8] = [
0xb8, 0x42, 0x00, 0x00, 0x00, 0xc2, 0x04, 0x00, ];
sb.mmu.write_initializer(proc_va, &proc_code).unwrap();
let create_thread = sb.registry.resolve("kernel32.dll", "CreateThread").unwrap();
for &a in [0u32, 0, proc_va, 0xCAFE, 0, 0].iter().rev() {
sb.cpu.push32(&mut sb.mmu, a).unwrap();
}
sb.cpu.push32(&mut sb.mmu, RET_SENTINEL).unwrap();
sb.cpu.regs.eip = create_thread;
sb.run_until_sentinel().unwrap();
let thread_handle = sb.cpu.regs.get32(Reg32::Eax);
assert!(thread_handle >= crate::sched::WAIT_OBJECT_HANDLE_BASE);
let t = sb.host.threads.get(&2).expect("new thread present");
assert!(matches!(t.status, crate::sched::ThreadStatus::Ready));
assert!(t.parked_cpu.is_some());
assert_ne!(
t.parked_cpu.as_ref().unwrap().regs.esp(),
0,
"stack was carved from the pool"
);
let sleep_thunk = sb.registry.resolve("kernel32.dll", "Sleep").unwrap();
sb.cpu.push32(&mut sb.mmu, 1u32).unwrap();
sb.cpu.push32(&mut sb.mmu, RET_SENTINEL).unwrap();
sb.cpu.regs.eip = sleep_thunk;
sb.run_until_sentinel().unwrap();
let t = sb.host.threads.get(&2).expect("new thread still present");
assert!(
matches!(t.status, crate::sched::ThreadStatus::Terminated),
"expected thread 2 Terminated, got {:?}",
t.status
);
}
}