Skip to content

Embedded Linux & Root Shells

Embedded Linux assessment covers the operating system layer of extracted and emulated devices: the BusyBox world (one binary providing a hundred applets), the init systems (SysV rc scripts, procd, custom rcS), the service architecture (inetd/telnetd/httpd/upnpd), the configuration systems (NVRAM, UCI, /etc configs, vendor blobs), and the shell-acquisition paths that turn device access into system control — from H1’s unauthenticated console to web-injection command execution to firmware-patched shells. The “root shell” is the goal state: a command prompt running as UID 0 on the device, from which everything else follows — config extraction, service enumeration, network pivoting into the device’s other interfaces, and persistent modification (which turns the assessment’s access into the estate’s problem). The craft spans H2/H3’s analysis (what runs, where the holes are) into operating the system: knowing BusyBox’s limitations, navigating init without systemd, reading vendor config systems, and maintaining access in a way the device’s own logging records.

Embedded Linux runs the world’s infrastructure fringe — routers, cameras, NAS, PLC gateways, building controllers — and its security posture differs from server Linux in every dimension that matters: BusyBox applets replace real tools, everything runs as root (no privilege separation worth the name), telnetd and world-readable configs ship by default, and the patch story is “replace the device” (vendor firmware updates are annual at best, discontinued models never). A root shell on such a device is therefore not one host — it is a position: the device’s network position (internal interfaces, the segment it bridges), its credential store (Wi-Fi keys, VPN secrets, the admin credential the web UI uses), its pivot potential (the device often sits between zones), and its persistence (firmware modification survives reboots and looks like the device working normally). For defenders and product teams, the module’s checks are the hardening basics that embedded estates chronically miss: no unauthenticated consoles, no telnetd, services running as non-root where possible (rare but possible), config secrets out of world-readable files, and logging that records shell access — each verified by the same techniques the assessor uses. For the assessor, embedded fluency is the productivity multiplier: the analyst who knows BusyBox’s applet set, the init flow, and the vendor config idioms moves through a strange device in an hour instead of a day.

The embedded system’s anatomy and the shell-acquisition ladder:

EMBEDDED LINUX ANATOMY (typical)
[BusyBox] one binary: sh, ls, ps, ifconfig, wget,
telnetd, httpd, crond, init ... (applets)
[init] /etc/inittab -> /etc/init.d/rcS (SysV-ish)
or procd (OpenWrt) or vendor rcS
[services] telnetd (legacy), httpd/lighttpd (web),
upnpd, dropbear/sshd (rare), vendor daemons
[config] NVRAM (nvram_get/set), UCI (OpenWrt),
/etc/*.conf, vendor XML/JSON blobs
[storage] squashfs (ro rootfs) + jffs2 (rw overlay)
-> config lives on the overlay; rootfs RO
SHELL ACQUISITION LADDER (each rung = H-series link)
1. unauthenticated console (H1) -- free
2. web/CGI command injection -- common (W8)
3. default/weak creds on telnet/ssh -- census (PE3)
4. firmware-patched shell (H2/H3: edit image,
inject authorized_keys, reflash) -- persistent
5. emulated-service RCE -> same on device (H3)
WHAT ROOT UNLOCKS (the position, not the host)
- config store: wifi keys, vpn secrets, admin creds
- network position: bridge interfaces, zone crossing
- persistence: overlay edits, cron, startup injection
- telemetry blind spot: device logs what you allow

BusyBox literacy is the productivity layer: the applet set differs from server Linux (ps has different flags, netstat may be absent — cat /proc/net/tcp instead, find may lack -printf), the shell is ash not bash, and the applet list (busybox --help) tells you which tools you actually have. The init flow determines persistence: SysV-style /etc/init.d/rcS runs the startup scripts on the read-only rootfs, but the overlay (jffs2) layers on top — so persistent modification lands on the overlay (a cron file, an authorized_keys entry, an init script shadowing a rootfs one) and survives reboots while appearing normal. The config systems are the secrets layer: NVRAM (the classic — nvram_show dumps everything including Wi-Fi keys and admin passwords; the nvram_get calls H3’s shim emulated are now real), UCI (OpenWrt’s sane system — uci show), and vendor blobs (XML/JSON in /etc — grep for credential-shaped strings per H2’s harvest). The service architecture is the attack-surface map: netstat -l (or /proc/net/tcp) shows what listens, ps shows what runs, and the cross-reference to H3’s static analysis confirms which binaries own which ports — the surface where web/CGI injection (W8) and unauthenticated services live. The acquisition ladder ranks the paths: the console (free, if unlocked), the web injection (the common remote path — W8’s system() sinks in CGI handlers), the credential census (PE3’s techniques against telnet/dropbear), the firmware patch (the persistent path — edit the image per H2, inject an authorized_keys entry or an init script, reflash or MITM the update — turning access into survival), and the emulated-to-hardware transfer (prove the injection on QEMU, then confirm on the device). The position mindset closes the module: the root shell is valuable for what the device is — a bridge between zones, a credential store, a trusted insider — and the assessment reports that position, not just the shell.

TechniquePathWhat it yieldsPrerequisiteNotes
Console shellH1 UARTImmediate rootUnlocked consoleThe free win
CGI command injectionWeb surface (W8)Remote rootInjection sinkMost common remote
Credential censustelnet/dropbearLogin as adminWeak/default credsPE3 bridge
Firmware-patch persistenceH2 image editSurvives rebootReflash accessAssessment-only
NVRAM dumpRoot shellAll device secretsAny root pathThe secrets layer
Overlay persistenceRoot shellBoot-surviving accessRW overlay presentPosition holding
BusyBox enumerationAny shellTool inventoryShellKnow your applets
Service surface mapRoot shellWhat listens whereShell/proc/net/tcp
Zone pivotDevice positionCross-segment reachShell + interfacesThe position value
Log-checkRoot shellWhat the device recordsShellYour telemetry exposure

rootrecce.py — the post-shell device reconnaissance script (designed to run on the embedded device via BusyBox sh): enumerates system state with BusyBox-safe commands only (no bashisms, no GNU flags), catalogs services/interfaces/config stores, extracts credential-shaped strings from the config systems, and emits a compact recon report — the first five minutes after any shell, automated. stdlib-safe (it’s a shell script generator + runner; Python builds and optionally executes it over SSH/telnet).

#!/usr/bin/env python3
"""rootrecce.py — embedded device post-shell recon generator.
Usage:
python rootrecce.py --gen recce.sh # emit script
python rootrecce.py --run --host 10.0.0.21 # via ssh
The script is BusyBox-safe (no bashisms, no GNU flags).
stdlib only.
"""
import argparse, os, subprocess, sys
RECCE = r"""#!/bin/sh
# rootrecce.sh — BusyBox-safe device recon (5 minutes)
echo "=== SYSTEM ==="
uname -a 2>/dev/null
cat /proc/version 2>/dev/null | head -1
cat /proc/cpuinfo 2>/dev/null | grep -i -m1 -E "model|system"
echo "=== UPTIME/MEM ==="
uptime 2>/dev/null
free 2>/dev/null | head -3
echo "=== BUSYBOX APPLETS ==="
busybox 2>/dev/null | tail -n +1 | head -5
echo "=== INIT/RC ==="
ls /etc/init.d/ 2>/dev/null
echo "--- inittab ---"
cat /etc/inittab 2>/dev/null | grep -v "^#" | head -8
echo "=== SERVICES LISTENING ==="
cat /proc/net/tcp 2>/dev/null | awk 'NR>1 {print $2}' | \
while read a; do
p=${a##*:}
printf "%d\n" 0x$p
done | sort -n | uniq | head -20
echo "=== PROCESSES (root-owned) ==="
ps 2>/dev/null | head -25
echo "=== INTERFACES ==="
ifconfig 2>/dev/null | grep -E "^[a-z]|inet " | head -20
echo "=== CONFIG STORES ==="
if command -v nvram >/dev/null 2>&1; then
echo "-- nvram (credential-shaped keys only) --"
nvram show 2>/dev/null | grep -i -E \
"pass|key|secret|token|psk" | head -15
fi
if command -v uci >/dev/null 2>&1; then
echo "-- uci --"
uci show 2>/dev/null | grep -i -E \
"pass|key|secret|psk" | head -15
fi
echo "=== WORLD-READABLE SENSITIVE FILES ==="
for f in /etc/passwd /etc/shadow /etc/config/* \
/etc/wpa_supplicant.conf /etc/dropbear/*; do
[ -f "$f" ] && echo "-- $f" && head -5 "$f"
done
echo "=== CRON ==="
cat /etc/crontabs/* 2>/dev/null | head -10
ls /var/spool/cron/crontabs/ 2>/dev/null
echo "=== OVERLAY/MOUNTS (persistence candidates) ==="
mount 2>/dev/null | grep -E "jffs2|overlay|ubi" | head -6
echo "=== RECON COMPLETE ==="
"""
def gen(path):
open(path, "w").write(RECCE)
print(f"[+] wrote {path} — copy to device "
f"(wget/tftp/serial) and `sh recce.sh`")
def run_ssh(host, user, key, script_path):
cmd = ["ssh", "-o", "StrictHostKeyChecking=no",
"-o", "UserKnownHostsFile=/dev/null"]
if key:
cmd += ["-i", key]
cmd += [f"{user}@{host}",
f"sh -s < {script_path}"]
r = subprocess.run(" ".join(cmd), shell=True,
capture_output=True, text=True,
timeout=120)
return r.stdout + r.stderr
def main():
ap = argparse.ArgumentParser()
ap.add_argument("--gen")
ap.add_argument("--run", action="store_true")
ap.add_argument("--host")
ap.add_argument("--user", default="root")
ap.add_argument("--key")
ap.add_argument("--script", default="recce.sh")
args = ap.parse_args()
if args.gen:
gen(args.gen)
return
if args.run:
if not args.host:
sys.exit("--run needs --host")
if not os.path.exists(args.script):
gen(args.script)
out = run_ssh(args.host, args.user, args.key,
args.script)
print(out)
return
print(__doc__)
if __name__ == "__main__":
main()
  • The recce script’s BusyBox discipline (lines 10-70): every command chosen for the BusyBox applet set — cat /proc/net/tcp with awk (instead of netstat, often absent), ps without flags, command -v (POSIX) instead of which, grep -i -E (BusyBox grep supports both), and head-bounded outputs (serial consoles and tiny buffers). The credential-shaped greps (pass|key|secret|psk) on nvram/uci are the secrets layer — output-capped so the report stays a report.
  • The listening-ports decode (lines 27-34): /proc/net/tcp’s hex little-endian ports decoded with printf — the BusyBox-safe netstat substitute, capped at 20 entries.
  • The overlay check (lines 65-67): mounts filtered for jffs2/overlay/ubi — the persistence candidates, because the read-only squashfs rootfs can’t hold changes and the overlay is where they’d land.
  • The Python wrapper (lines 73-99): --gen emits the script (the normal path — copy via the console/tftp/wget you already have), --run executes over SSH for dropbear-equipped devices, generating the script first if missing so the run path is one command.

The shell reaches the device through the ladder’s rungs — console (H1), injection (W8’s sink in the CGI binary H3 identified), credentials (PE3’s census against dropbear/telnet) — and each path’s telemetry differs (console leaves nothing, injection leaves a web log line, credentials leave auth failures) which is part of the assessment’s detection story. The recon script reaches the system as its own five-minute inventory: what runs, what listens, what the config stores hold, where the overlay is — the device’s truth from inside, complementing H2’s offline view. The findings reach the estate’s risk register with the position framing: the NVRAM dump’s Wi-Fi key is the wireless perimeter’s key (WI1/WI4), the admin credential is the web estate’s (W-series), the zone-bridging interfaces are the segmentation story (BT13), and the overlay-persistence path is the incident-response question (“what would persistence look like here?”). For the defender, the hardening checks reach the design review: console locked (H1’s checklist), telnetd absent, services non-root where feasible, config secrets out of world-readable paths, and logging that records shell access — the device that logs its own console sessions and web admin actions turns the assessor’s recon into SOC-visible events, which is the difference between a device that was “accessed” and one that was “accessed and no one knows.” The firmware-patch path reaches the IR conversation as its most sobering artifact: the demo that a modified image survives reboots and indistinguishably serves traffic is the argument for signed firmware and secure boot (H11) — the control that makes persistence require the vendor’s key.

  1. Shell — web CGI injection (W8 sink from H3 RE) on lab device: root command execution confirmed
  2. Recce — rootrecce.sh staged via wget; BusyBox inventory, services, interfaces cataloged
  3. Secrets — nvram show: wifi WPA2 PSK, admin cred, VPN pre-shared key extracted
  4. Position — device bridges LAN and guest zones: guest-side pivot reaches LAN via device
  5. Persist — overlay write: cron entry + dropbear authorized_keys injected (reboot survives)
  6. Verify — reboot device; shell returns via injected key: persistence confirmed
  7. Log-check — device’s own logs record nothing about steps 1-6: telemetry gap finding
  8. Report — position report (secrets + pivot + persistence + blind logging); remediation per control

busyboxmap.py — the BusyBox applet mapper: parses a BusyBox binary’s applet list (from the device’s busybox output or from the extracted binary’s strings) and maps the assessment-relevant applets against what you’d want them for — netstat-equivalents, wget/tftp (staging), telnetd (persistence), chroot/devmem (advanced), crond — producing the “what can I do from this shell” cheat sheet per device. stdlib only.

  • Applet-list driven, version-agnostic. BusyBox prints its applet list (busybox --list or the help tail) — parsing that output (or the strings from the binary) gives the actual applet set, not an assumed one; vendor builds strip applets.
  • Capability framing over inventory. The map answers capability questions: “can I stage files?” (wget/tftp/nc), “can I persist?” (crond/telnetd/init access), “can I poke hardware?” (devmem/i2c-tools), “can I pivot?” (route/iptables) — grouped by assessment intent, not alphabet.
  • Missing-applet implications. The absences matter as much: no wget means staging via echo/printf base64; the map notes the workaround per missing capability — the difference between a list and a cheat sheet.
  • Two input modes. Live (SSH the busybox --list output back) or offline (strings from the H2-extracted binary) — the same pipeline as every H-series tool, deployment-flexible.
#!/usr/bin/env python3
"""busyboxmap.py — BusyBox applet capability mapper.
Usage:
python busyboxmap.py --list applets.txt
python busyboxmap.py --binary busybox.bin (strings mode)
stdlib only.
"""
import argparse, json, re, sys
CAPS = {
"staging": {
"wget": "HTTP fetch (stage tools/configs)",
"tftp": "TFTP fetch",
"nc": "netcat transfer",
"ftpget": "FTP fetch",
"curl": "HTTP fetch (rare in BusyBox)"},
"persistence": {
"crond": "cron persistence",
"telnetd": "network shell (persistence/exit)",
"httpd": "serve payload files",
"init": "startup script control",
"setuidgid": "identity manipulation"},
"hardware": {
"devmem": "memory-mapped register poke",
"devmem2": "memory-mapped register poke (alt)",
"i2cdetect": "I2C bus enumeration",
"flashcp": "flash write",
"flash_eraseall": "flash wipe (destructive)"},
"pivot": {
"route": "routing table (zone view)",
"ip": "addr/rule/route (iproute2 applet)",
"iptables": "firewall manipulation",
"brctl": "bridge control",
"arping": "L2 neighbor probing"},
"recon": {
"netstat": "sockets (or /proc/net/tcp)",
"ps": "process list",
"top": "live processes",
"strings": "binary strings (RE on device)",
"find": "file search"},
"shell-life": {
"chroot": "filesystem jailing (or escape)",
"passwd": "credential change",
"su": "identity switch",
"shadow": "(not applet: /etc/shadow access)"},
}
def parse_applets_text(text):
"""busybox --list or help-tail -> applet set."""
applets = set()
for line in text.splitlines():
for tok in re.split(r"[,\s]+", line.strip()):
tok = tok.strip()
if re.fullmatch(r"[a-z0-9\-_]{2,20}", tok):
applets.add(tok)
return applets
def strings_applets(binary_path):
"""Fallback: applet-name candidates from binary strings."""
data = open(binary_path, "rb").read()
strs = re.findall(rb"[a-z0-9\-_]{2,20}", data)
candidates = set(s.decode() for s in strs)
known = set()
for caps in CAPS.values():
for a in caps:
if a in candidates:
known.add(a)
return known
def render(applets):
lines = ["# BusyBox capability map", ""]
for cap, tools in CAPS.items():
have = [a for a in tools if a in applets]
lack = [a for a in tools if a not in applets]
lines.append(f"## {cap}")
if have:
for a in have:
lines.append(f"- [x] {a}: {tools[a]}")
else:
lines.append("- (no relevant applets)")
if lack:
lines.append(f"- missing: {', '.join(lack)}")
lines.append("")
# workaround notes for common absences
if "netstat" not in applets:
lines.append("note: no netstat -> use "
"cat /proc/net/tcp|udp")
if "wget" not in applets and "tftp" not in applets:
lines.append("note: no wget/tftp -> stage via "
"printf/base64 over the shell")
if "telnetd" in applets:
lines.append("note: telnetd present -> "
"persistence path available "
"(and a finding for the report)")
return "\n".join(lines) + "\n"
def main():
ap = argparse.ArgumentParser()
ap.add_argument("--list")
ap.add_argument("--binary")
ap.add_argument("--json")
args = ap.parse_args()
if args.list:
text = open(args.list, encoding="utf-8",
errors="replace").read()
applets = parse_applets_text(text)
elif args.binary:
applets = strings_applets(args.binary)
else:
sys.exit("give --list or --binary")
print(f"[*] {len(applets)} applets recognized")
text = render(applets)
print(text)
if args.json:
json.dump(sorted(applets),
open(args.json, "w"), indent=2)
if __name__ == "__main__":
main()
  • The capability table (lines 13-45) is the mapper’s knowledge: six assessment intents (staging, persistence, hardware, pivot, recon, shell-life) with the applets that serve each — the questions an assessor asks a shell, pre-mapped to BusyBox’s answers.
  • parse_applets_text() (lines 48-55) tokenizes the applet list with a strict lowercase-name regex — BusyBox’s help output mixes usage text with applet names, and the token filter extracts the names without the noise.
  • The workaround notes (lines 82-89) convert absences into next moves: no netstat → /proc/net/tcp (the rootrecce script already uses it), no wget/tftp → base64-over-shell staging, telnetd present → a report finding — the last one being the tool’s reminder that the capability map doubles as the hardening review.
  • The strings fallback (lines 58-65) works from the H2-extracted binary when no shell exists yet: applet-name candidates from the binary’s strings — the pre-shell capability assessment (can this device’s BusyBox stage files if I get a shell?).

generate and stage the recce script (device shell from H1/W8)

Section titled “generate and stage the recce script (device shell from H1/W8)”

$ python rootrecce.py —gen recce.sh [+] wrote recce.sh — copy to device and sh recce.sh $ cat recce.sh | ssh root@10.0.0.21 sh -s === SYSTEM === Linux device 4.14.180 #1 SMP … armv7l === SERVICES LISTENING === 80 443 23 9999 === CONFIG STORES === — nvram (credential-shaped keys only) — wlan_wpa_psk=LabWiFiSecret1 http_admin_password=secret123 vpn_psk=SharedPSK2026

capability map from the extracted busybox binary

Section titled “capability map from the extracted busybox binary”

$ python busyboxmap.py —binary squashfs-root/bin/busybox [*] 38 applets recognized

  • wget: HTTP fetch (stage tools/configs)
  • missing: tftp, nc note: no wget/tftp -> stage via printf/base64 over the shell
  • telnetd: network shell (persistence/exit) note: telnetd present -> persistence path available

Embedded detection is the device’s own logging — and the recon script’s final section makes the finding concrete: most BusyBox devices log almost nothing (no syslog remote, no auth logging, no web-admin audit), so the root shell is invisible to every SOC surface. The design-review checks: syslog remote-forwarding enabled (even BusyBox syslogd can remote), web-admin actions audited, shell/auth events logged, and — the architectural control — the device’s management plane behind the network controls that do log (a device whose telnetd is reachable only from the management VLAN inherits that VLAN’s telemetry). Your own tooling hygiene: the recce script is read-only; the persistence demonstrations (cron, authorized_keys) are lab-gated with cleanup per the manifest discipline. Wazuh/SIEM ingestion of forwarded device syslog [verify per vendor] is the join that puts IoT telemetry in the SOC — the standing gap the logging check measures. The standing metrics: logging-coverage percentage per device class (which devices forward syslog), unauthenticated-service count per estate (telnetd census), and the firmware-patch feasibility note per model (can persistence survive reboot? — the H11 secure-boot question).

  1. SSH/telnet driver: implement the --run path over telnet (stdin piping) for devices without dropbear.
  2. Credential-store diff: snapshot nvram/uci state across time — config-drift detection on the device.
  3. Persistence-cleanup verifier: manifest-driven removal check (the T3 model, on-device).
  4. BusyBox version fingerprint: map applet sets to BusyBox versions and CVE classes [verify per release notes].
  5. Zone-pivot tracer: from the shell, enumerate reachable subnets per interface and emit the position graph (BT13’s evidence, device-side).
SignalSourceIDNotes
Shell/auth events (absent!)Device logging auditcoverage findingThe core gap
telnetd runningbusyboxmap / port scanservice censusPersistence-capable
Web-admin actions unloggedWeb server configaudit findingHarvest invisibility
Device syslog forwarding offsyslog config audittelemetry gapSOC blindness
NVRAM credential exposurerootrecce outputsecrets findingWi-Fi/VPN/admin keys
Overlay persistence feasibilitymount auditdesign findingSigned-firmware argument
Zone-bridging interfacesInterface censussegmentation findingBT13 evidence
Default/weak creds on servicesCredential censusPE3 bridgeLogin-path risk
Firmware signature absenceH11 reviewdesign findingPersistence unpreventable
Wazuh ingestion of device syslogHIDS/SIEM[verify rule IDs per deployment]The IoT telemetry join

The post-shell evasion question — how does an adversary hold the device quietly — has embedded-specific answers. Overlay discipline: persistence that lives on the jffs2 overlay (cron, ssh keys, a modified init script) survives reboots and reads as normal config — countered by signed firmware (the overlay can’t forge rootfs files) and by overlay-integrity monitoring (the diff that flags new cron/keys). Log avoidance: the device logs little by default, and the adversary avoids enabling anything — countered by remote syslog (the logs leave the device before the adversary can edit them) and by network-side telemetry (the device’s traffic pattern is visible even when its logs aren’t). Tool-free operation: capabilities from existing applets (no staged binaries to detect), /proc-based recon (no netstat process signature), shell one-liners over existing sessions — countered by baseline-diffing the device’s behavior (traffic shape, process set) rather than its files. Named-process camouflage: running the adversary’s listener under a plausible name (the BusyBox applet argv trick) — countered by /proc examination from a position the adversary doesn’t control (the boot-time integrity check, H11) and by network-side detection of the listener regardless of its name. Firmware-image patience: no runtime persistence at all — the adversary who owns the update channel (H10) simply ships persistence in the next legitimate-looking update — countered by signed updates, which is the control that closes the highest door. The meta-point: embedded evasion exploits the device’s blindness (no logs, no integrity checks, no EDR), and the defenses that matter are the ones that remove the blindness — remote logging, secure boot, signed updates — not the ones that add endpoint agents the platform can’t run.

⚠️ What caught me: my first embedded persistence demo was too good — the injected cron job survived three reboots, the dropbear key held, and I presented “persistent access demonstrated” with satisfaction. Then the client’s engineer asked the question that reframed everything: “so how would we have ever removed that?” I opened the device’s own tools and realized neither of us had a clean answer — the overlay file had no manifest, the cron entry looked native, and the vendor’s recovery procedure was “reflash via TFTP,” which would also have wiped the client’s configuration. My persistence demo had silently become a data-destruction risk for the responder. Since then, every persistence demo ships with its own removal procedure tested in the same session, and the report’s persistence finding carries a mandatory “IR handling” section: how to detect it, how to remove it, and what the removal costs. An access you can’t cleanly revoke is a finding about your process, not just their device.

  1. Shell-to-recon: from your H1 console (or H3 emulated shell), run the generated recce.sh and reconcile every section against the device’s known config — the five-minute inventory, verified against ground truth.
  2. BusyBox map: run busyboxmap.py against the device’s applet list and the extracted binary; compare the two (the live device’s applet set vs the image’s) — vendor stripping, observed.
  3. Persistence drill (lab-gated): inject a cron entry + authorized_keys on the overlay, reboot, confirm survival, then execute your own removal procedure and verify absence — the full persistence lifecycle with its IR section.
  4. NVRAM secrets exercise: dump nvram/uci on the lab device, classify each credential found (Wi-Fi/admin/VPN), and trace which perimeter each key opens — the position framing, made concrete.
  5. Logging-gap measurement: attempt every action in the chain again while watching the device’s own logs and your SIEM; count how many actions produced any telemetry — the blindness finding, quantified.
  • Technical: How does the squashfs-plus-overlay storage model work, and why does it make persistence both easy for attackers and hard for responders?
  • Technical: What BusyBox-specific constraints change recon scripting versus server Linux, and what are the /proc substitutes for missing applets?
  • Scenario: Your persistence demo works, but the client asks how to remove it without losing config. What do you deliver alongside the finding?
  • Conceptual: Why is a root shell on an embedded device a “position” rather than a host — and which three positions does a typical device hold?
  • Conceptual: How do signed firmware and secure boot change the persistence calculus, and what attacker path remains?
  • Deep-dive: Design the embedded hardening baseline: services, logging, config storage, firmware signing — and the acceptance tests for each.
  • Practical: The device’s syslog can’t be enabled (flash constraints). What network-side compensating telemetry do you deploy, and what can it still miss?

H1 (Hardware Hacking Fundamentals (UART/JTAG/SPI/I2C)) · H2 (Firmware Extraction & Analysis) · H3 (Firmware Analysis & Emulation) · PE1 (Linux Privilege Escalation) · F2 (Linux Internals)