- Axis A: Analyze the hypervisor itself(VT-x/SVM instructions, VMCS/VMCB, EPT/NPT) → this main document
- Axis B: The target is under some kind of virtualization, determine "whether the addresses/devices/interrupts you see are fabricated by the virtualization layer" → see [[xen]] / [[qnx-hypervisor]] / [[jailhouse]] / [[acrn]] / [[bao]]
The common pitfall of Axis B istreating local handles as global identifiers, treating virtual addresses as physical addresses:
grant ref 是 grant table 的条目索引 ≠ 物理地址
event-channel port 是通知端口 ≠ IRQ 号
guest BAR / GPA ≠ 物理 BAR / HPA
shmem_id 才是共享对象 identity ≠ 两个 VM 里的映射地址
</CORE RULE>
When to use / when not to use
- Prerequisite:First determine which type of virtualization it is(host vendor string / configuration format / structural evidence) → the Hypervisor section of [[re-analyze/system-fingerprints]]
- Use: the target is a hypervisor / VMM binary or driver (malicious hypervisor, rootkit virtualization, VM-based protection)
- Use: the sample/program detects whether it is running in a virtual machine or nested virtualization (CPUID fingerprinting, timing detection)
- Use: analyze VT-x (VMX) or SVM-related startup code, VMCS/VMCB layout, EPT-related operations
- Use:the target runs under a partitioned/embedded hypervisor——PV devices, static partitioning, vdev, cross-VM shared memory and virtual interrupts (see the branches below)
- Don't use: ordinary Windows drivers/rootkits (go to [[re-kernel]]); if you only need to identify "am I in a VM or not" (for a quick determination, go to the [[re-triage]] approach or
virt-what) - Don't use: dynamic verification when there is no CPU virtualization support / no nested virtualization environment (static analysis first, see pitfall 1)
- Note: dynamic experiments (QEMU/KVM nesting) are conducted in a sandbox per the highest principle of [[re-analyze/platform-tips]]; hypervisor samples are highly privileged and should only be run in an experimental environment isolated from the host
Tool preparation
Static analysis (CPUID inspection / decompilation) requires no sandbox; QEMU/KVM dynamic experiments involve dynamic execution, default to sandbox + snapshot (the highest principle of [[re-analyze/platform-tips]]).
CPUID inspection tools (hypervisor identification)
- Built into Linux:
grep -E 'vmx|svm' /proc/cpuinfo——VT-x/SVM support flags (zero installation, use this first) cpuidtool (dumps the complete output of each CPUID leaf):- Debian/Ubuntu:
apt install cpuid;Fedora:dnf install cpuid - Arch has no standalone cpuid package → use
kcpuid(pacman -S kcpuid, linux-tools group) or the one bundled with libcpuidcpuid_tool(pacman -S libcpuid) - Verify:
cpuid -1output contains details of each leaf;cpuid -1 -l 0x40000000(hypervisor vendor string leaf)
- Debian/Ubuntu:
- Windows side: under WinDbg kernel debugging
!cpuid([[re-windbg]] extension command)
QEMU / KVM —— nested virtualization experimental environment
- Debian/Ubuntu:
apt install qemu-system-x86(Debian 12+ has removed the qemu-kvm transitional package, installing qemu-system-x86 directly includesqemu-system-x86_64; Ubuntu's qemu-kvm transitional package still exists, and installing it is equivalent to qemu-system-x86; Ubuntu additionally addslibvirt-daemon-system) - Fedora:
dnf install qemu-system-x86-core libvirt virt-install(ordnf group install virtualization) - Arch:
pacman -S qemu-system-x86 libvirt virt-manager(enablesystemctl enable --now libvirtd) - macOS:
brew install qemu(no KVM, using HVF) - Verify:
qemu-system-x86_64 --version;ls /dev/kvm(KVM acceleration available);kvm-ok(Debian/Ubuntu only, from the cpu-checker package——firstapt install cpu-checker)
Decompilation workbench ([[re-ghidra]] / [[re-ida]])
- [[re-ghidra]] (default) / [[re-ida]]: import the hypervisor binary (kernel module / raw binary)
- Verification: after import, the VMXON / VMPTRLD / VMREAD / VMWRITE call sites can be decompiled
Intel SDM / AMD APM (VMCS field encoding reference, no installation)
- Intel SDM Volume 3C Appendix B (VMCS field encoding table); AMD APM Volume 2 (VMCB layout)
- Purpose: VMREAD/VMWRITE operand decoding, exit reason number cross-reference (no standalone package, official documentation)
Procedure
Execute in order; record the evidence path + sha256 for each step's artifacts (CPUID output, VMCS field table, QEMU configuration) (see [[re-triage]]) for reference in the report.
-
hypervisor identification (CPUID leaf / VMX flag):
grep -E 'vmx|svm' /proc/cpuinfo | head -1 # 宿主 CPU 虚拟化支持 cpuid -1 -l 0x1 | grep -i -A1 'hypervisor' # CPUID.1:ECX[31] hypervisor present bit cpuid -1 -l 0x40000000 # 0x40000000 叶子的 hypervisor 厂商字符串 # "KVMKVMKVM" = KVM、"Microsoft Hv" = Hyper-V、"VMwareVMware" = VMware、"XenVMMXenVMM" = Xen- Detection points: hypervisor present bit (CPUID.1:ECX[31]) → hypervisor present;
0x40000000returns the vendor string (12 characters distributed in order across EBX:ECX:EDX, 4 bytes each; EAX returns the maximum hypervisor leaf number) - Malicious samples often perform this detection early during startup to decide subsequent behavior ([[re-evasion]] linkage, see pitfall 5)
- Record: host support status (vmx/svm), whether already inside a VM (including nesting, pitfall 4)
- Detection points: hypervisor present bit (CPUID.1:ECX[31]) → hypervisor present;
-
VMCS structure analysis (VT-x):
- Startup path:
VMXON(enter VMX operation mode) →VMPTRLD(load the current VMCS) → configure VMCS fields →VMLAUNCH/VMRESUME(enter guest) → after VM exit, check theVM_EXIT_REASONfield dispatch - Decompilation location: search for the
VMXON/VMPTRLD/VMWRITE/VMREAD/VMLAUNCHinstruction (directly readable in the Ghidra disassembly); the VMCS area is a memory block, so first find the VMCS buffer allocation and initialization code - The operands of VMREAD/VMWRITE are VMCS field encodings(32-bit, Intel SDM Appendix B Tables B-1~B-4):bit0 = access type(0=full field、1=high 32 bits);the index is in bits 9:1(9 bits, not bits 9:0); the type is in bits 11:10 (00=control, 01=read-only, 10=guest-state, 11=host-state); the width is in bits 14:13 (00=16-bit, 01=64-bit, 10=32-bit, 11=natural); bit 12 and bits 31:15 are reserved (must be 0). Decoding examples: 0x400C=VM-exit controls (32-bit, bits 14:13=10), 0x2000=I/O bitmap A (64-bit, bits 14:13=01), 0x681E=GUEST_RIP (natural, bits 14:13=11), 0x681C=GUEST_RSP (natural), 0x4402=VM-exit reason (32-bit read-only) — i.e.
access=enc&1; index=(enc>>1)&0x1ff; type=(enc>>10)&3; width=(enc>>13)&3, create enumerations/structures in Ghidra/IDA for annotation - Focus on three blocks: guest-state (saves guest registers/CR3/RSP), host-state (host context after VM exit), control fields (execution control determines which events trigger a VM exit)
- SVM counterpart: VMCB (physical address via the
VM_HSAVE_PAMSR), fields are at fixed offsets — annotate according to the AMD APM layout - Artifacts: VMCS field annotation table (encoding → field name → purpose) + initialization/exit handling flow
- Startup path:
-
Virtualization technology detection countermeasures (EPT hidden memory):
- EPT (Extended Page Tables): the second-level page table from guest physical address → host physical address (controlled by the VMM); a malicious hypervisor can use EPT to map the same guest page to different host pages, or modify page contents at the EPT level while the guest page tables appear unchanged
- Analysis approach: look for EPT-related operations in the decompilation——
INVEPT/INVVPID(TLB invalidation) usage point, EPT pointer field (VMCSEPTP) assignment, EPT page table construction function (walking the page table structure from the EPT pointer) - Observation method for detecting countermeasures: the memory contents seen inside the guest are inconsistent with directly reading the same physical page on the host side → evidence of EPT remapping (comparison of two views); [[re-kernel]] kernel debugging cooperates by dumping the same page on both the host and guest sides
- EPT Hook dual-view mechanism(core of traceless hook): prepare two HPAs for the same GPA—the original page (RW/NX) and the shadow page (X-only or patched code)—data accesses map to the original page, instruction fetches map to the shadow page; after CPU instruction fetch triggers an Execute Violation (VM-exit reason 48), switch to the shadow page in the VM-exit and INVEPT to invalidate the cache; scanners/CRC reading the address get clean bytes, but execution is the patched code (traceless on the read side)
- EPT traceless INT3: place
0xCConly in the shadow page, the original data page is unchanged—reading memory sees the original instruction, but instruction fetch breaks; after intercepting vector 3 via the VMCS Exception Bitmap, match the address usingguest_rip - 1(the RIP of a one-byte INT3 is after the breakpoint byte); after handling, single-step to restore and then re-set the breakpoint; #BP not from this framework must be reinjected back into the guest (must not swallow other debuggers'/system breakpoints) - MTF (Monitor Trap Flag) solves "read-execute same page": if a shadow page instruction reads a constant on the same page (instruction fetch wants the shadow page, data wants the original page—conflict) → temporarily relax the original page for execution and set MTF, the CPU single-steps one instruction and then tightens permissions in the MTF VM-exit; MTF is a VM-execution control, does not touch guest RFLAGS.TF, does not occupy DR0-DR7
- EPT monitoring = simulating unlimited hardware breakpoints: DR0-DR3 have only 4 slots, EPT monitoring revokes the target page's R/W/X permissions so matching accesses VM-exit, the count is limited only by memory/performance; hardware-breakpoint-type requirements (including interworking with traditional debuggers, such as hooking
SetThreadContextto detect whether a hardware breakpoint is set and then forward it to EPT monitoring) use this set instead - VMCALL traceless communication: the guest executes VMCALL to produce VM-exit reason 18 (without calling any address inside the guest), RAX holds the protocol identifier, RCX holds the operation number, other registers pass parameters, it is a private communication channel between the hypervisor and the guest
- Deliverable: EPT construction/switching code path + memory view difference evidence
-
Nested virtualization (debugging inside a VMM):
# KVM 开启嵌套(宿主)——module_param 权限为 0444,sysfs 只读,echo 写入会失败 sudo modprobe -r kvm_intel && sudo modprobe kvm_intel nested=1 # Intel(AMD 用 kvm_amd nested=1) # 持久化:/etc/modprobe.d/kvm.conf 写 `options kvm_intel nested=1`(重启后仍生效) cat /sys/module/kvm_intel/parameters/nested # 验证(Y/1 为已开启) # QEMU 启动带 VT-x 透传的嵌套 VM(`-cpu host` 暴露 vmx 标志) qemu-system-x86_64 -enable-kvm -cpu host,+vmx -m 4096 disk.img & # 嵌套 VM 内再验证: grep vmx /proc/cpuinfo 可见 → 可在此跑 hypervisor 样本- Usage: a debugger on the host (gdb/lldb or [[re-windbg]] kernel debugging) directly observes the execution of the hypervisor inside the nested VM—the sample thinks it is at the lowest level, but it is actually still within the host's debugging view
- VMware/Hyper-V is the same (enable "virtualization engine/nested virtualization" in the VM settings)
- Deliverable: nested configuration archive + debugging session record
-
Anti-virtualization bypass ([[re-evasion]] linkage):
- Identify detection methods: CPUID vendor string (step 1), timing (RDTSC instruction duration), device names (VM virtual devices), firmware/ACPI characteristics
- Following [[re-evasion]]'s "evasion identification→bypass point location" framework: hook CPUID (cpuid is aninstructionrather than an exported symbol,
findExportByNamecannot get it—instead hook the libc exported function__get_cpuid/__get_cpuid_maxto change the return value, or use Stalker instruction-level tracing to intercept the cpuid instruction; kernel-side hook/patch instruction), QEMU-cpuParameter forgery of vendor strings, device name renaming - Malware samples that "change behavior upon detecting a VM" (without executing malicious logic) are also a common form of adversarial behavior—record detection points and branches
- Deliverable: detection point checklist + bypass solutions (authorized research scenarios)
Platform branches (references)
Axis B: the target is "a system running under some kind of virtualization"; one must determine which phenomena are normal mechanisms created by the virtualization layer.
- [[xen]] —— PV frontend/backend four-piece set(XenStore / grant table / shared ring / event channel);grant ref is an entry index(entries contain flags/domid/frame, andrevocation is not supported during the mapping);event-channel port is not an IRQ number(bound to
shared_infobitmask);after migration the local port changes; what stays stable is the remote port; on the shared ring, "data flowing with no IPC call" is normal - [[qnx-hypervisor]] —— Three-level addressing(guest virtual → guest physical/IPA → host physical);vdev makes the guest DT ≠ the board DT;shmem vdev factory page (
name/size/shmem/vector/status, writingsizetriggers creation, so guest boot order is irrelevant) and control page (status/idx/notify/detach);intr passvsintr vdev; a passthrough device allows only one resident at a time - [[jailhouse]] —— Configuration matters more than the inmate ELF(CPU/memory/IRQ/PCI ownership); unauthorized access → CPU gets parked(not a guest crash);
JAILHOUSE_MEM_LOADABLEmapping is revoked when the cell starts(reinstallation goes through Cell Set Loadable);ivshmem does not maintain pending semantics(MSI-X PBA is always 0; event state is read from the shared memory protocol); not all VM memory can be scanned in the hypervisor VA - [[acrn]] —— Service VM / pre-launched / post-launched tri-state and three I/O service paths;guest BAR ≠ physical BAR(EPT mapping, the MSI-X table page must be trapped); posted interrupt reduces VM-exits;ivshmem's dm-land and hv-land never interconnect(prefix
dm:/vshv:/, and dm-land has no doorbell) - [[bao]] —— Static partitioning(CPU/memory/interrupt exclusivity, vCPU to pCPU 1:1, no scheduler);shared object identity is
shmem_idrather than an address; IPC'ssize ≤ 对应 shmem 大小;cache coloring suppresses interference but at a cost (sacrificing huge pages, increasing TLB pressure) and is truncated by the platform's color count - [[hyperv-vmbus]] —— VSC / VSP over VMBus(a bidirectional channel = two ring buffers; high-speed devices may use multiple channels);GPADL is a buffer descriptor handle, not an address(the shared total has an upper limit);SR-IOV lets the data plane switch paths mid-flight(VF passthrough bypasses VMBus and the hypervisor, while the control plane remains on VMBus)
- [[xtratum]] —— XM_CF configuration-driven(partitions/memory/interrupts/ports/channels/scheduling are all in the configuration; no dynamic objects at runtime);cyclic plan (MAF + time slots) and fixed-priority plan;Plan 0 = initialization, Plan 1 = maintenance, switching must wait for the current plan's remaining slots to finish;at most 8 IPVIs per partition
- [[lynxsecure]] —— Static separation kernel: hardware partitions defined at boot and immutable; kernel functionality is limited toresource partitioning / data flow between control partitions / mediating entry points for system state changes;I/O and application support are all exported to the guest(there is no driver or I/O stack in the kernel). Includesreverse-engineering actionable layer: configuration takes precedence over code, the boot phase is the only window where "configuration taking effect" can be seen,use the three responsibilities to infer the kernel boundary in reverse, find controlled channels across partitions rather than shared pages, use official constraints to verify the isolation between DMA/hypervisor and guest
- [[questv]] —— multi-kernel sandbox + one monitor per sandbox: the monitor intervenes only during booting/faults/shadow page tables/channel establishment; EPT is used formemory isolationrather than CPU virtualization; interrupts are delivered directly to the sandbox;EPT violation → VM-exit to the corresponding monitor(force a trap when necessary); local/remoteonline recovery;no global clock. Includes the locating criterion for "which section is the monitor"
cross-domain federation
- [[re-evasion]]: anti-virtualization detection/bypass framework (CPUID hook, timing countermeasures); VM detection behavior analysis of malicious samples
- [[re-kernel]]: hypervisor driver/kernel module analysis foundation (DriverEntry, IRP, kernel debugging coordination)
- [[re-windbg]]: Windows host/guest kernel debugging (
!cpuidcheck CPUID leaves, driver load observation) - [[re-ghidra]] / [[re-ida]]: VMCS/VMCB related code decompilation and structure annotation (build enums from SDM Appendix B)
- [[re-sandbox]] / [[re-analyze/platform-tips]]: the highest principle of QEMU/KVM experimental environment isolation; nested VM is the default sandbox form
- [[re-triage]]: quick judgment during initial survey of "whether inside a VM / CPU virtualization capability" (
virt-whatapproach)
common pitfalls and traps
- hardware virtualization debugging environments are complex: symptom - the hypervisor sample cannot run in an ordinary VM/crashes directly, the debugger fails to attach, and the sample behaves abnormally after detecting a nested environment; cause - nested virtualization requires host CPU support + explicit enabling (KVM nested / Hyper-V / VMware option), and the sample detects whether it is "really at the bottom layer"; countermeasure - first accumulate information purely statically (the CPUID and VMCS analysis in steps 1-2 does not require running the sample), and before going dynamic confirm the three-layer capability: host vmx/svm flag (
grep vmx /proc/cpuinfo) → nested switch (/sys/module/kvm_intel/parameters/nested)→ QEMU-cpu hostpassthrough; all experiments are conducted in an isolated sandbox (the highest principle of [[re-analyze/platform-tips]]) - VMCS kernel object reverse engineering has a high threshold: symptom - in the decompilation there are a bunch of magic numbers for VMREAD/VMWRITE, and you do not know which fields are being read or written, and the VM exit dispatch logic is incomprehensible; cause - VMCS is a hardware-defined format (field encodings are not symbols), and the layouts of different CPU architectures (VT-x vs SVM) are completely different; countermeasure - build the field encoding table from Intel SDM Appendix B into the decompiler (Ghidra enum/struct), decode the VMREAD/VMWRITE operands one by one; organize the analysis into the three blocks guest-state / host-state / control; for AMD targets switch to the VMCB fixed-offset layout (APM Volume 2)
- EPT makes memory breakpoints ineffective: Symptom—memory breakpoints/page-guard breakpoints set by the debugger inside the guest do not trigger, or behave erratically after triggering (registers do not match); Cause—the EPT accessed/dirty bits are independent of the guest page tables, and the VMM controls the memory view the guest sees (including hidden pages) through EPT, while ordinary debugger breakpoints are based on the guest page table perspective; Countermeasure—distinguish EPT violation (VM exit reason 48) from guest page fault (reason 14); to observe the EPT layer you must look at the EPT page table structure itself (expanded along the VMCS EPTP field) rather than the guest page tables; under [[re-kernel]] kernel debugging, compare the host/guest memory views on both sides
- CPU feature differences (VT-x vs SVM): Symptom—VMCS offsets/exit reason numbers worked out on an Intel machine do not match at all on an AMD machine, or vice versa; Cause—VT-x and SVM are two independent implementations: VMCS (VMREAD/VMWRITE encoding) vs VMCB (fixed offsets), and the exit reason numbering systems differ; Countermeasure—first confirm the target platform (CPUID vendor + vmx/svm flags, step 1), choose the corresponding manual by platform (Intel SDM Vol 3C / AMD APM Vol 2), annotate the target platform and CPU model in analysis notes, and do not reuse field tables across platforms
- Sample changes behavior after detecting a VM (affects conclusions): Symptom—malicious logic that is very clear in static analysis is completely invisible at dynamic runtime (the sample runs "normally"); Cause—when the sample detects that it is in a VM/debugging environment it takes a "harmless branch" (a common anti-sandbox/anti-debugging technique); Countermeasure—follow step 1 to first determine the virtualization state from the sample's perspective; dynamic verification must be consistent with the sample's detection conditions (or bypass the detections item by item); conclusions must be based on "restoration of the detection point + behavior after bypass"; a single run is not credible for drawing conclusions
- AMD NPT cannot directly copy the EPT approach: Symptom—the "execute-only page" trick available on Intel (shadow page X-only) fails on the AMD platform; Cause—NPT and EPT are two independent implementations,NPT cannot set execute-only attributes independently(the read and execute bits are bound together); Countermeasure—before implementing a cross-platform hook/hiding, first confirm whether the target is Intel (EPT) or AMD (NPT); AMD requires other means (such as combining NX + data view)
- "VT traceless read/write" is a false proposition: Symptom—assuming EPT can tracelessly read/write any data segment; Cause—shadow page tables can only tracelessly rewrite the "code segment" (instruction-fetch view switching); a data segment cannot simultaneously satisfy both views (read original vs write shadow); Countermeasure—traceless writes are limited to code segment scenarios (hook scenarios); reading data segments still relies on walking the four-level page tables (GVA→GPA→HPA two-stage translation) to directly read physical pages, which is not essentially different from the ordinary driver approach
- RDTSC detection of VM-exit overhead can be compensated: Symptom—the sample uses
__rdtsc/__rdtscpto measure instruction latency differences to identify the hypervisor; Cause—VM-exit has a fixed overhead that can be measured; Countermeasure—record at the earliest point in the VM-exit assembly entryexit_tsc,ReadVirtualTscand compensate using a bounded smooth estimate (do not treat all interrupt/scheduling long tails as exit cost), and ensure per-vCPU monotonicity (max(value, last_guest_tsc+1)); principle: wherever the sample detects, handle it there in the VM-exit - Treating a local handle across VMs as a global identifier: Symptom—the same integer appears in two domains/VMs in a trace and is treated as the same object; Cause—a grant ref is an entry index, an event-channel port is a notification port, and only shmem_id is the shared object identity; the local port also changes after reconnection and can be reused; Countermeasure—align by their respective tables/configurations (grant table entries, shared_info bitmask, shmem_id), see [[xen]] / [[bao]]
- Treating a virtual address as a physical address: Symptom—the MMIO address in the guest has no corresponding location on real hardware, so the driver is judged to have written the wrong thing or the wrong device is blamed; Cause—PV devices have no register window; under a partitioned hypervisor, the guest's BAR/GPA and the physical BAR/HPA are separated by EPT/NPT or a passthrough mapping; the vdev's address is manufactured by configuration; Countermeasure—first determine "passthrough or vdev/emulated," then discuss whether the addresses should match, see [[qnx-hypervisor]] / [[acrn]] / [[jailhouse]]
- Mistaking the hypervisor's protective action for a guest crash: Symptom—the program "suddenly stops" after accessing a certain address, but no ordinary page fault/bus error evidence can be found; Cause—a static partitioned hypervisor will park the CPU/cell of an unauthorized access park it(Jailhouse); Countermeasure—check the hypervisor-side logs (Unhandled trap / Parking CPU), and compare against the resource ownership in the cell/VM configuration ([[jailhouse]])
- When cross-VM shared memory cannot communicate, look in the device model: Symptom—both sides' ivshmem look "normal" but they cannot communicate at all; Cause—ACRN's dm-land and hv-land are two separate implementations, and if the prefixes (
dm:/andhv:/) differ they will never interoperate, and dm-land has no doorbell notification; Countermeasure—first verify whether the implementation and prefix match, then look at the data plane ([[acrn]]) - Treating a descriptor such as a GPADL as a memory address: Symptom—a small integer in the trace is used as an address or offset in calculations; Cause—a GPADL is a handle that "describes and maps a guest buffer," and the host has a limit on the total amount it can share; Countermeasure—recover the buffer it points to according to handle semantics ([[hyperv-vmbus]])
- Network traffic "disappears" midway yet functionality is normal: Symptom—VMBus is very busy for a while, then packets can no longer be captured but the network is still up; Cause—under SR-IOV the data plane can switch from the synthetic path to VF passthrough(the data plane bypasses VMBus and the hypervisor, while the control plane remains on VMBus); Countermeasure—first determine which path the current data plane takes ([[hyperv-vmbus]])
- Analyzing resource ownership without obtaining the configuration file: Symptom—no answer can be found to "why this partition cannot access it"; Cause—configuration-driven separation systems (XtratuM, LynxSecure, Jailhouse, Bao, etc.) put all resource ownership in configuration; Countermeasure—obtain the configuration first, then discuss the code ([[xtratum]] / [[lynxsecure]])
- Taking "lack of centralized management code/few VM-exits" as absence of isolation: Symptom—no central scheduling or reconfiguration logic can be found in the disassembly; Cause—static configuration models and multi-kernel sandboxes simply do not do it that way; Countermeasure—interpret according to that system's model ([[lynxsecure]] / [[questv]])
- Comparing timestamps across sandboxes: Symptom—the times of the same event do not match on the two sides; Cause—some multi-kernel designshave no global clock, and each core uses a local timer and TSC; Countermeasure—factor in clock skew and do not make assertions based on a single timeline ([[questv]])
- Expect drivers and the I/O stack in the kernel region: Symptom—a certain image block contains everything (drivers, I/O, application APIs) yet is treated as the kernel; Cause—an extreme statically separated design exports all I/O and application support to the guest, and the kernel only performs resource partitioning/data flow control/state change mediation; Countermeasure—use these three responsibilities to reverse-check the true kernel boundary ([[lynxsecure]])
- Look for "reconfiguration" logic at runtime: Symptom—no dynamic MMU/resource reconfiguration engine can be found; Cause—under the static configuration model, mappings and ownership are all determined at startup; Countermeasure—move the work of tracing mappings to the startup phase ([[lynxsecure]])