Skip to content

Physical and MMIO memory

How the runtime reaches memory the normal process APIs will not hand over: SoC control registers, the fuse block, and eMMC blocks the storage manager hides. Windows CE 6 runs user code with the MMU on and maps none of these apertures into a process, so each is reached from kernel context through the runtime's own primitives. The addresses here are for Pavo firmware 4.5; kernel and XIP addresses are fixed per firmware, runtime state (handles, driver structs) is per-boot.

Kernel calls and reads

Everything below runs through the kernel read/write/call interface the runtime exposes (kerncore): kcall(fn, args...) invokes a kernel-mode function, kread and kwrite touch kernel memory, and a kernel scratch buffer comes from NvOsAlloc (import thunk 0xC088D2B0). Host tooling drives these over the listener; a patched process does the same in-process. How that kernel access is obtained and kept is the kerncore and exploit-chain reference.

Mapping a physical register page

A physical aperture such as the fuse controller at 0x7000F800 is not addressable from a process, and user-mode VirtualCopy with PAGE_PHYSICAL is rejected on CE 6 (ERROR_INVALID_PARAMETER); physical mapping is kernel-only. The kernel path is two calls:

  1. NvRmPhysicalMemMap (forwarder 0xC094ABF4) maps a physical page to a virtual address: kcall(0xC094ABF4, phys_base, 0x1000, 3, out_ptr), with the resulting VA written to out_ptr. phys_base is page-aligned; the constant 3 in the third slot is taken from working tooling, its meaning unconfirmed [unverified].
  2. KMEMCPY_F (0x80072318), a u32-atomic kernel memcpy, copies the register out of the mapped VA into a scratch buffer, which kread then returns.

The read width matters. KMEMCPY_F faults on a 4-byte read of these controllers and succeeds on an 8-byte (two-word) aligned read, so a single register is fetched as the wanted word of an 8-byte read. Not every aperture is reachable: a region whose module clock is gated at idle faults on access (the UARTs behave this way), and the clock has to be ungated through the clock-and-reset controller at 0x60006000 first. APB_MISC (0x70000000), CAR, and the fuse block read without that step.

Host tool: read-tegra-mmio.py. The mapping primitive is general/tegra_mmio.py.

Reading the Tegra fuses

The SoC chip-ID register APB_MISC GP_HIDREV (0x70000804) reads 0x00031617: chip 0x16, the AP16 part. The fuse cache is memory-mapped from 0x7000F800; the bootloader itself reads individual rows with plain loads rather than a sense sequence, so the same rows read back through the map above.

The fuse rows the bootloader references for its key material (0x7000F9A4..0x7000F9B4) read 0xFFFFFFFF, the all-ones of read-locked secure fuses. The bootloader is encrypted and a zero-key pass over it yields noise, so a real key is burned and unreadable, and the bootloader cannot be decrypted on the device. Which rows are the secure-boot versus the device key is inferred from the bootloader's references, not a fuse map [unverified]. The payload side is storage secure boot. Host tool: read-tegra-fuses.py.

Reaching the eMMC front region

The 215.5 MiB reserved region at the front of the eMMC (the boot and recovery firmware, in the storage reference) is not addressable through DSK1:: the store IOCTL bounds-checks against the store size, and no CreateFile path reaches the block driver below the partition layer.

It is read by patching the SDMMC driver's command entry. NvDdkHsmmcSendCommand (0xc08e47b4) is hooked with a PC-relative branch to a heap trampoline (the code-patching primitive, a page-table permission flip over retail .text). While armed, the trampoline subtracts a fixed delta from a read command's native block address when it falls in a tight window, so a normal DSK1: read of a low logical sector is remapped down into the front region and served by the driver's own clocked DMA. The delta is chosen per target block; the mapping arithmetic (DSK1: logical sector N reaches native block 441344 + 4*N) is in the storage reference.

The patch touches only the command path, not the store: with the hook removed a DSK1: read of sector 0 still returns the MBR. Reading the driver's entry hook mid-command or poking the controller's clock registers by hand both wedge the controller, so the remap works at the software command level and lets the driver drive the hardware. Host tool: read-emmc-front.py; trampoline source hsmmc_remap.asm.