Skip to main content

Sandbox

Struct Sandbox 

Source
pub struct Sandbox {
    pub mmu: Mmu,
    pub cpu: Cpu,
    pub registry: Registry,
    pub host: HostState,
}
Expand description

One sandbox instance per loaded codec DLL.

Fields§

§mmu: Mmu§cpu: Cpu§registry: Registry§host: HostState

Implementations§

Source§

impl Sandbox

Source

pub fn coverage(&self) -> &CoverageMap

Borrow the always-on coverage map populated by the interpreter. Records every dispatched instruction’s entry EIP plus every guest memory write. See crate::coverage::CoverageMap for the consumer surface.

Source

pub fn coverage_mut(&mut self) -> &mut CoverageMap

Mutable accessor for the coverage map — useful for per-export resets (coverage_mut().clear()) between runs of the same sandbox.

Source

pub fn context(&self) -> &Context

Borrow the emulation-context layer (virtual filesystem, virtual registry, future surfaces). Always present; the per-surface options decide whether the guest observes synthetic state or the fail-soft Win32 default. See crate::context::Context.

Source

pub fn context_mut(&mut self) -> &mut Context

Mutable accessor for the context.

Source

pub fn with_vfs(self, vfs: VirtualFs) -> Self

Builder: attach a virtual filesystem so guest file-API calls land in-memory instead of fail-soft no-ops. See crate::VirtualFs for the stage-some-files / capture- what’s-written workflow.

Source

pub fn with_registry(self, reg: VirtualRegistry) -> Self

Builder: attach a virtual registry so guest Reg* calls observe analyst-staged keys and writes land in-memory. See crate::VirtualRegistry.

Source

pub fn new() -> Self

Create a fresh sandbox with the heap arena and stack pre-mapped, the kernel32 stub set registered, and the CPU’s esp pointing at a freshly-allocated stack.

Source

pub fn with_rand_seed(self, seed: u32) -> Self

Builder-style seed setter for the msvcrt!rand LCG.

PRNG state for msvcrt!rand calls from sandboxed codec code. Default 1 matches MSVC’s documented “no srand called yet” initial value. Set via with_rand_seed / set_rand_seed for reproducible encode output: two sandboxes seeded identically produce identical rand sequences, which makes encode regression tests deterministic across runs.

The guest’s own msvcrt!srand(seed) call writes to the same field, so the codec may re-seed at any time; in that case Self::rand_seed will report whatever value the codec last installed.

Round 55.

Source

pub fn set_rand_seed(&mut self, seed: u32)

Set the msvcrt!rand LCG state at runtime.

Same contract as Self::with_rand_seed, but mutates an already-constructed sandbox — useful for tests that drive multiple encode runs with different seeds, or for fuzzing harnesses that want to force the codec into a known state before each iteration.

Round 55.

Source

pub fn set_command_line(&mut self, cmdline: &str) -> Result<(), Error>

Override the value kernel32!GetCommandLineA returns to the guest. The string is stashed (NUL-terminated) in the host’s const arena and a pointer to it is parked at command_line_ptr. Installer-class binaries consult this to pick up /quiet, /qn, /S and similar silent-install flags.

Source

pub fn rand_seed(&self) -> u32

Read the current msvcrt!rand LCG state.

Reflects whatever the host or the guest last wrote: a fresh sandbox returns 1 (MSVC’s documented “no srand called yet” initial value); after host Self::set_rand_seed / Self::with_rand_seed returns that value; after a guest msvcrt!srand(s) call returns s; after any number of msvcrt!rand calls returns the post-step LCG state.

Round 55.

Source

pub fn load(&mut self, name: &str, bytes: &[u8]) -> Result<Image, Error>

Load a PE32 image from bytes, mapping it into the sandbox’s MMU. The returned Image holds the entry point + export table.

Strict-resolution: any IAT entry the crate::win32::Registry doesn’t satisfy is a hard load-time error. Use Sandbox::load_fail_soft for EXEs whose import list exceeds the codec-class stub surface (installers, GUI apps, etc.).

Source

pub fn load_fail_soft( &mut self, name: &str, bytes: &[u8], ) -> Result<(Image, Vec<(String, String)>), Error>

Load a PE32 image in fail-soft import-resolution mode. Imports the codec-class stub registry doesn’t satisfy get a trap-on-call fallback thunk so the load succeeds. Returns the loaded Image plus the list of (dll, name) pairs that received a fallback — i.e. the set of APIs the operator now knows the binary uses but we don’t yet stub.

Intended for the install-monitor workflow: load QuickTimeInstaller.exe with fail-soft, drive the entry point, watch the trap stream for the next missing API.

Source

pub fn call_dll_main( &mut self, image: &Image, reason: u32, ) -> Result<u32, Error>

Synchronously call DllMain(hModule, fdwReason, lpvReserved) inside the emulator and return the dword eax value at the point the function returned to the synthetic RET_SENTINEL.

The DllMain ABI is stdcall (callee-cleanup), so we push lpvReserved first, then fdwReason, then hModule, then the return-address sentinel. The callee’s RET 12 (or equivalent) cleans the args.

Resolution: prefer the DllMain named export (Indeo codecs); fall back to the PE AddressOfEntryPoint (mpg4c32.dll and other CRT-startup-driven DLLs that don’t export DllMain by name). Both expose the same stdcall (HINSTANCE, DWORD, LPVOID) ABI.

Source

pub fn call_export( &mut self, image: &Image, name: &str, args: &[u32], ) -> Result<u32, Error>

Generic stdcall guest-call helper. Resolves name against image’s export table, pushes args right-to-left + the RET_SENTINEL, and runs until the callee returns. Returns eax.

Used both internally (by Self::call_dll_main) and by future codec adapter layers that need to drive arbitrary codec exports — DriverProc, MyCodecGetVersion, MyCodecExtraInit, etc. The round-2 vfw32::ic_* host surface uses crate::win32::call_guest directly with the codec’s DriverProc VA.

Source

pub fn call_entry_point(&mut self, image: &Image) -> Result<u32, Error>

Call the image’s PE entry point (AddressOfEntryPoint). For an EXE this is the CRT startup, which expects no arguments and never returns under normal Windows semantics (it calls ExitProcess). Here it runs until the runtime returns to the synthetic RET_SENTINEL or hits a trap (e.g. unresolved import, instruction limit).

Source

pub fn run_until_sentinel(&mut self) -> Result<(), Error>

Drive the CPU until eip == RET_SENTINEL, dispatching to Win32 stubs whenever eip lands on a registered thunk address. Thin wrapper over crate::win32::run_until_sentinel kept for API stability.

Source

pub fn install_codec(&mut self, image: &Image) -> Result<(), Error>

Mark image as the codec the next Self::ic_open call should target.

Round 2 supports a single codec image per sandbox — round 3 will lift that into a multi-codec registry. The image must export DriverProc.

Source

pub fn ic_open( &mut self, fcc_type: u32, fcc_handler: u32, mode: u32, ) -> Result<u32, Error>

Open the installed codec (DRV_OPEN).

Source

pub fn ic_close(&mut self, hic: u32) -> Result<u32, Error>

Close a codec instance (DRV_CLOSE).

Source

pub fn ic_get_info(&mut self, hic: u32, cb: u32) -> Result<Vec<u8>, Error>

Read the codec’s ICINFO block.

Source

pub fn ic_decompress_query( &mut self, hic: u32, input: &Bih, output: Option<&Bih>, ) -> Result<u32, Error>

ICDecompressQuery — does the codec accept this format?

Source

pub fn ic_decompress_get_format( &mut self, hic: u32, input: &Bih, ) -> Result<(u32, Bih), Error>

ICDecompressGetFormat — ask the codec for the output BIH matching input. Round 30 uses this to probe stream dimensions when CodecParameters lacks them.

Source

pub fn ic_decompress_begin( &mut self, hic: u32, input: &Bih, output: &Bih, ) -> Result<u32, Error>

ICDecompressBegin — set up the decoder pipeline.

Source

pub fn ic_decompress_end(&mut self, hic: u32) -> Result<u32, Error>

ICDecompressEnd — tear down the decoder pipeline.

Source

pub fn watch(&mut self, addr: u32, size: u32, mode: WatchMode)

Install a memory watchpoint covering [addr, addr+size). Any guest access whose address range intersects the watchpoint emits a kind=mem_write (or mem_read) JSONL event to the configured sink. Multiple watchpoints may overlap; each fires independently.

Source

pub fn unwatch(&mut self, addr: u32, size: u32)

Remove watchpoints whose (addr, size) exactly matches. Mode is ignored for the match.

Source

pub fn set_exec_trace(&mut self, on: bool)

Toggle per-instruction execution trace at runtime. Has no effect unless the crate was built with the trace-exec sub-feature.

Source

pub fn set_trace_sink(&mut self, sink: Box<dyn Write + Send>)

Override the trace JSONL sink at runtime. Defaults to honouring OXIDEAV_VFW_TRACE_FILE.

Source

pub fn dll_get_class_object( &mut self, image: &Image, clsid: Guid, riid: Guid, ) -> Result<u32, Error>

Drive DllGetClassObject(rclsid, riid, ppv) on image, staging the GUID arguments + the ppv out-slot in a freshly-allocated heap region inside the sandbox. On success returns the guest pointer the codec wrote into *ppv — typically a guest-side IClassFactory.

When riid == IID_IClassFactory, the returned pointer is also registered with crate::com::ComObjectTable::register_class_factory keyed under clsid, so subsequent Self::co_create_instance calls can resolve clsid without re-driving DllGetClassObject.

MSDN: HRESULT DllGetClassObject(REFCLSID rclsid, REFIID riid, LPVOID *ppv) — every COM in-process server exports it; DirectShow filter binaries (.ax) export it instead of DriverProc.

Source

pub fn co_create_instance( &mut self, clsid: Guid, riid: Guid, ) -> Result<u32, Error>

Drive CoCreateInstance(clsid, NULL, CLSCTX_INPROC_SERVER, riid, ppv) against the in-process class-factory cache. The CLSID must already be registered (typically by a prior Self::dll_get_class_object call); otherwise surfaces CLASS_E_CLASSNOTAVAILABLE as an error.

Source

pub fn query_interface(&mut self, obj: u32, riid: Guid) -> Result<u32, Error>

Drive obj->QueryInterface(riid, ppv) on a guest COM object, staging the IID + out-slot in arena memory. Returns the new interface pointer on success, or surfaces the HRESULT in an error message.

Source

pub fn mint_host_filter_graph(&mut self) -> Result<u32, Error>

Round 27 — mint a host-side IFilterGraph stub so the codec’s IBaseFilter::JoinFilterGraph(pGraph, pName) call has a non-NULL parent graph to record. The returned guest pointer’s vtable function-pointer slots are synthetic thunk addresses that route into the host stubs registered by crate::com::host_iface::register.

QueryInterface(IID_IUnknown | IID_IFilterGraph)S_OK + *ppv = obj; every other IID returns E_NOINTERFACE. All eight IFilterGraph methods return E_NOTIMPL — none are exercised on the JoinFilterGraph → ReceiveConnection path the round-27 probe takes.

Source

pub fn mint_host_output_pin(&mut self, amt_addr: u32) -> Result<u32, Error>

Round 27 — mint a host-side IPin stub that pretends to be an OUTPUT pin advertising amt_addr (a pointer to a staged AM_MEDIA_TYPE). Suitable as the pConnector argument of IPin::ReceiveConnection.

QueryDirection reports PIN_OUTPUT; QueryAccept returns S_OK; ConnectionMediaType copies the staged AMT; EnumMediaTypes vends an enumerator yielding the staged AMT once.

Source

pub fn mint_host_output_pin_with_connection( &mut self, amt_addr: u32, connected_pin: u32, ) -> Result<u32, Error>

Round 37 — same as Self::mint_host_output_pin but also stamps the codec’s input-pin pointer (connected_pin) into the new pin object so IPin::ConnectedTo can return it, and synthesizes a parent HostIBaseFilter so IPin::QueryPinInfo can fill in PIN_INFO::pFilter.

connected_pin == 0 falls back to the round-30 behaviour where the pin reports VFW_E_NOT_CONNECTED from ConnectedTo.

Source

pub fn query_pin_info_call_count(&self) -> usize

Round 37 — number of IPin::QueryPinInfo calls the codec has driven against any host pin during this sandbox’s lifetime.

Source

pub fn query_filter_info_call_count(&self) -> usize

Round 37 — number of IBaseFilter::QueryFilterInfo calls the codec has driven against any host filter during this sandbox’s lifetime.

Source

pub fn query_pin_info_calls(&self) -> Vec<u32>

Round 37 — this pointers of every IPin::QueryPinInfo call observed.

Source

pub fn query_filter_info_calls(&self) -> Vec<u32>

Round 37 — this pointers of every IBaseFilter::QueryFilterInfo call observed.

Source

pub fn clear_query_info_log(&self)

Round 37 — drop every captured introspection call from this sandbox’s per-state log.

Source

pub fn mint_host_mem_allocator( &mut self, pool_size: u32, sample_capacity: u32, media_type_ptr: u32, ) -> Result<u32, Error>

Round 30 — mint a host-side IMemAllocator backed by a pool of pool_size IMediaSample slots, each carrying a fresh sample_capacity-byte data region. The returned guest pointer is suitable as the pAllocator argument of IMemInputPin::NotifyAllocator.

media_type_ptr is returned by every minted sample’s IMediaSample::GetMediaType — pass 0 if no AMT should surface there (codecs then fall back to the upstream pin’s connection media type).

Source

pub fn mint_host_mem_allocator_class_factory(&mut self) -> Result<u32, Error>

Round 35 — mint a host-side IClassFactory whose CreateInstance mints fresh HostIMemAllocator instances.

Pre-registered in Sandbox::new under crate::com::CLSID_MEMORY_ALLOCATOR; this method exists for tests that want a raw factory pointer to drive IClassFactory::CreateInstance directly without going through the ole32!CoCreateInstance cascade.

Source

pub fn mint_host_media_sample( &mut self, data_capacity: u32, media_type_ptr: u32, ) -> Result<u32, Error>

Round 30 — mint a single host-side IMediaSample wrapping a fresh data_capacity-byte data region. Useful for stand-alone tests; production paths typically mint samples implicitly via Self::mint_host_mem_allocator.

Source

pub fn media_sample_set_payload( &mut self, sample: u32, payload: &[u8], sync_point: bool, ) -> Result<(), Error>

Round 30 — copy a payload into a previously-minted sample

  • flag whether it is a sync (key) frame.

Wraps crate::com::media_sample_set_payload.

Source

pub fn host_iface_r31_mint_input_pin_pair( &mut self, ) -> Result<(u32, u32), Error>

Round 31 — mint a paired downstream (HostIPin, HostIMemInputPin) for receiving samples the codec pushes from its output pin.

Source

pub fn host_iface_r31_mint_base_filter( &mut self, input_pin: u32, ) -> Result<u32, Error>

Round 31 — mint a minimal HostIBaseFilter exposing input_pin.

Source

pub fn pop_received_sample(&self) -> Option<ReceivedSample>

Round 31 — pop the oldest sample captured by the downstream HostIMemInputPin::Receive callback.

Source

pub fn received_samples_len(&self) -> usize

Round 31 — number of samples currently waiting in the host-side queue.

Source

pub fn last_set_properties(&self) -> Option<AllocatorPropertiesCapture>

Round 33 — return the most recent IMemAllocator::SetProperties capture observed on this sandbox, or None if no codec has called SetProperties yet. See crate::com::AllocatorPropertiesCapture for the captured field shape.

Source

pub fn all_set_properties(&self) -> Vec<AllocatorPropertiesCapture>

Round 33 — return every SetProperties capture observed on this sandbox, in arrival order.

Source

pub fn clear_set_properties_log(&self)

Round 33 — drop every captured SetProperties for this sandbox. Useful for resetting per-test state.

Source

pub fn com_add_ref(&mut self, obj: u32) -> Result<u32, Error>

Drive obj->AddRef(). Returns the codec-reported new refcount; the host’s bookkeeping is updated automatically.

Source

pub fn com_release(&mut self, obj: u32) -> Result<u32, Error>

Drive obj->Release(). Returns the codec-reported new refcount. The host’s bookkeeping is updated automatically.

Source

pub fn ic_decompress( &mut self, hic: u32, flags: u32, input_bih: &Bih, input_bytes: &[u8], output_bih: &Bih, output_capacity: u32, ) -> Result<(u32, Vec<u8>), Error>

ICDecompress — decode one frame.

Source

pub fn ic_compress_query( &mut self, hic: u32, input: &Bih, output: Option<&Bih>, ) -> Result<u32, Error>

ICCompressQuery — does the codec accept this input/output format pair? output may be None to defer the choice.

Source

pub fn ic_compress_get_format( &mut self, hic: u32, input: &Bih, ) -> Result<(u32, Bih), Error>

ICCompressGetFormat — ask the codec for the output BIH describing what its compressed format looks like for the supplied input.

Source

pub fn ic_compress_get_size( &mut self, hic: u32, input: &Bih, output: &Bih, ) -> Result<u32, Error>

ICCompressGetSize — max encoded-frame byte count for the supplied input/output BIH pair.

Source

pub fn ic_compress_begin( &mut self, hic: u32, input: &Bih, output: &Bih, ) -> Result<u32, Error>

ICCompressBegin — set up the encoder pipeline.

Source

pub fn ic_compress_end(&mut self, hic: u32) -> Result<u32, Error>

ICCompressEnd — tear down the encoder pipeline.

Source

pub fn ic_compress( &mut self, hic: u32, flags: u32, input_bih: &Bih, input_bytes: &[u8], output_bih: &Bih, output_capacity: u32, ckid: u32, frame_num: i32, frame_size_limit: u32, quality: u32, prev_bih_opt: Option<&Bih>, prev_bytes_opt: Option<&[u8]>, ) -> Result<CompressOutcome, Error>

ICCompress — encode one frame. Returns the full encode outcome: codec LRESULT, encoded bytes, the post-call output BIH (whose biSizeImage holds the actual encoded byte count), the codec-written *lpdwFlags (e.g. whether the codec marked the emitted frame as a keyframe), and the codec-written *lpckid.

prev_bih_opt / prev_bytes_opt are the previous reconstructed frame (P-frame encoding). Pass None for keyframes.

Source

pub fn ic_get_state( &mut self, hic: u32, dst_buf: &mut [u8], ) -> Result<u32, Error>

ICGetState — ask the codec to serialise its private per-instance state into dst_buf. Returns the byte count the codec actually wrote.

Round 70 — wraps ICM_GETSTATE (0x5009) per MSDN; required by oxideav-tracevfw to drive the encoder’s per-quality knob round-trip alongside Self::ic_set_state. See MSDN ICGetState topic page for the public contract.

Source

pub fn ic_set_state(&mut self, hic: u32, src_buf: &[u8]) -> Result<(), Error>

ICSetState — ask the codec to deserialise src_buf into its private per-instance state. Returns Ok(()) on ICERR_OK, or crate::Error wrapping the codec’s raw LRESULT otherwise.

Round 70 — wraps ICM_SETSTATE (0x500A) per MSDN. See MSDN ICSetState topic page for the public contract.

Source

pub fn msadds32_patch_helper_addref( &mut self, image_base: u32, value: u32, ) -> Result<(), Error>

Round 63 — patch msadds32.ax’s helper_addref thunk (at RVA 0x5cea) to unconditionally return value (32-bit integer).

Why. Round-62 forensics (docs/codec/msadds32-receive-null-0x20.md) traced the IMemInputPin::Receive NULL-deref trap at RVA 0x256a to a buffer-pool init that’s handed a size of zero. The size is (h * 10) / size_calc(...) where h is what helper_addref returns. On a fresh codec instance the helper-object field at helper_90 + 0x3c (the “initialised” flag the addref checks) is zero, so helper_addref returns 0, the quotient is 0, operator new(0) returns NULL, buffer_pool_init fails, and the Receive cleanup branch trips a NULL+0x20 deref.

In a real DirectShow host the flag is set during IFilterGraph::JoinFilterGraph / Pause (the codec stamps the field as part of its run-state machine). Until we drive that path, the surgical workaround is to short-circuit helper_addref to return a fixed non-zero value — which empirically lifts the trap and lets Receive run to completion (with HRESULT 0x8000ffff from the decode body, which is the next round’s investigation surface).

Encoding. The original function (RVA 0x5cea, 10 bytes) is:

0x5cea: 83 79 20 00  cmp [ecx+0x20], 0
0x5cee: 74 04        jz  +4
0x5cf0: 8b 41 28     mov eax, [ecx+0x28]
0x5cf3: c3           ret
0x5cf4: 33 c0        xor eax, eax
0x5cf6: c3           ret

We overwrite the first 6 bytes with:

b8 XX XX XX XX  mov eax, imm32
c3              ret

The remaining bytes at 0x5cf0..0x5cf6 are unreachable after the patch (no caller enters there directly), so the dead-code is harmless.

image_base is the address the codec was loaded at; pass the value returned by Self::load on msadds32.ax.

§Reference material (clean-room only)
  • Intel SDM Vol. 2A — MOV imm32 (B8+rd), RET (C3).
  • Raw bytes of msadds32.ax from docs/video/msmpeg4/reference/binaries/wmpcdcs8-2001/.

No Wine / ReactOS / MinGW / Microsoft DShow source consulted.

Trait Implementations§

Source§

impl Default for Sandbox

Source§

fn default() -> Self

Returns the “default value” for a type. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.