Supply Chain & Update Security
Definition
Section titled “Definition”Supply-chain and update security covers the delivery pipeline that every device trusts for its entire life: the update mechanism (OTA full-image and delta updates, A/B partition schemes, the update client’s download-and-verify logic), the signing architecture (who signs images, where verification happens — boot ROM, bootloader, updater — and what a missing or bypassable check means), the software bill of materials (SBOM) (which third-party components ship inside the firmware, at which versions, carrying which known vulnerabilities), and the provisioning chain (how devices acquire identities and keys from factory to customer — H9’s enrollment question at supply-chain scope). The assessment treats the update channel as the crown-jewel control it is: whoever controls updates controls every device permanently (H4’s persistence becomes the vendor’s own mechanism), so the analysis verifies each link — the client’s TLS validation, the signature check’s presence and placement, the downgrade protection, the key custody — and the SBOM maps the third-party estate whose CVEs the device inherits.
Why It Matters
Section titled “Why It Matters”The update channel is the one attack path that is designed to reach every device with code execution — which makes its security the highest-leverage control in the whole IoT estate, and its weaknesses the most expensive. The failure classes repeat across vendors: update clients that download over HTTP (or accept HTTPS downgrade), signature checks that verify the package header but not the payload, verification that happens after flashing (the check that fires too late), no anti-rollback (an attacker replays an old, vulnerable-but-signed version — the downgrade attack that resurrects every patched vulnerability), and signing keys shipped in the firmware (H2’s harvest — the vendor’s own private key in every device’s flash). The SBOM gap is the quieter systemic risk: devices ship OpenSSL 1.0.2-class components for a decade, inherit every CVE published against them, and nobody — vendor or customer — can enumerate what’s inside. The research and incident lineage (SolarWinds-class supply-chain compromises, mass router/camera compromises via update and web surfaces [verify per incident]) established the stakes: supply-chain compromise is fleet-scale, pre-installed, and the most durable persistence available. For defenders and product teams, the module defines the control chain: signed images verified before flashing (in the boot ROM’s trust anchor where possible — H11’s chain), anti-rollback via monotonic version counters, update clients with full TLS validation, an SBOM generated per build (and maintained), and the provisioning design that gives per-device identities rather than shared keys (H9’s finding at chain scope).
Detailed Explanation
Section titled “Detailed Explanation”The update chain’s anatomy and its verification points:
THE UPDATE CHAIN AND ITS CHECKS [vendor build] image + signing key (custody!) | [distribution] CDN/app/store -- TLS transport | [device updater client] <- ASSESSED HERE: downloads (HTTPS? cert validation?) verifies (signature? before or after flash?) anti-rollback (monotonic counter? version floor?) applies (A/B swap? in-place? recovery path?) | [boot verification] <- H11 chain: does the boot ROM | check the signature too? [running system]
SIGNING MODELS (from weakest) unsigned -> any MITM owns the fleet header-signed -> verify header, not payload payload-signed, post-flash verify -> too late payload-signed, pre-flash verify -> correct placement boot-ROM anchored chain -> the trust anchor model A/B + counter -> adds anti-rollback
SBOM (what's inside) components: OpenSSL/busybox/zlib/mbedtls + versions source: strings (H2), package manifests, build systems maps to: known CVEs the device inherits (cve-bin-tool class tooling [verify current])Update-client analysis starts from H2’s extracted binary: the updater’s strings and xrefs reveal the download URLs (HTTP? or HTTPS with which validation?), the verification calls (a signature check that runs before the flash write — or after, which is the too-late finding), and the version logic (the rollback guard’s presence). The interception test (authorized, against your own update infrastructure) empirically confirms what the RE suggests: a proxy-position MITM that succeeds in delivering a modified package is the finding no code review proves as clearly. The signature assessment classifies the model per the weakest-to-strongest ladder: unsigned (the fleet owned by anyone on-path), header-signed (the classic bypass — modify the payload, keep the header), post-flash verify (the check that fires after the damage), pre-flash verify (correct), and the boot-ROM-anchored chain (the trust anchor that makes the whole chain meaningful — H11’s story). The downgrade assessment checks the counter: A/B schemes with monotonic version counters, or the version floor in the updater — without one, every old signed image is a valid (and vulnerable) delivery forever. The SBOM work is the inventory discipline: strings-level fingerprints (H2’s harvest) find the version banners (OpenSSL 1.1.1w, busybox v1.30.1), package manifests carry the truth where they exist, and the cve-bin-tool-class mapping turns the inventory into the CVE list the device inherits — the report’s “your device ships with 14 components carrying 37 known CVEs, 3 critical” is the table that drives both vendor and customer action. The provisioning chain review (H9’s enrollment at scope) completes the picture: factory keys, per-unit identity injection, and the custody of the signing key itself — the compromise that would be the update chain’s root.
Common Techniques
Section titled “Common Techniques”| Technique | Target | What it tests | Tooling | Notes |
|---|---|---|---|---|
| Update-package triage | Packages | Structure, signature presence | fwanalyze (H2 bridge) | First pass |
| Update client RE | Updater binary | URLs, TLS, verify order | H2/H3 workflow | The logic review |
| Interception test | Live channel | MITM acceptance | Proxy + own infra | Authorized, own fleet |
| Signature model classification | Chain | Weakest-link placement | This module’s tool | The ladder audit |
| Downgrade/rollback test | Updater | Version floor enforcement | Old signed package | The resurrection test |
| SBOM generation | Rootfs | Component inventory | sbomscan (below), cve-bin-tool [verify] | The CVE map |
| Known-CVE mapping | SBOM | Inherited vulnerabilities | cve-bin-tool class | Prioritized by exposure |
| Provisioning chain review | Factory→customer | Identity/key custody | Docs + flow test | H9 bridge |
| Vendor posture review | Program | Cadence, EOL, advisory | Policy assessment | The lifecycle question |
| Boot-anchored chain audit | H11 chain | Trust anchor verification | H1/H11 techniques | The strongest model |
Full Working Code
Section titled “Full Working Code”updcheck.py — an update-package and client analyzer: parses an update package (reusing H2’s triage ideas — magic scan, entropy), checks signature presence by pattern (detached sig blocks, PKCS#7/CMS structures), analyzes the update client binary (from the extracted rootfs) for the validation-logic signals — HTTPS URLs vs HTTP, certificate-validation strings, signature-verify function references, version/rollback logic — and emits a per-claim verdict report mapping the signing-ladder tiers. stdlib only.
#!/usr/bin/env python3"""updcheck.py — update chain analyzer (package + client).Usage: python updcheck.py --pkg update.bin --client rootfs/usr/bin/updater python updcheck.py --pkg update.binChecks: signature presence (package), client validationsignals (HTTPS, cert verify, signature refs, rollback).stdlib only."""import argparse, json, math, re, sysfrom collections import Counter
SIG_PATTERNS = [ (re.compile(rb"-----BEGIN (RSA |EC )?SIGNATURE"), 8, "detached PEM signature"), (re.compile(rb"\x30\x82[\x00-\xff]{2}\x30\x82"), 4, "PKCS#7/CMS-shaped structure"), (re.compile(rb"sha256(RSA)?WithRSA|ecdsa-with-SHA256", re.I), 6, "signature algorithm string"), (re.compile(rb"(?i)\bsignature\b"), 1, "generic 'signature' string"), (re.compile(rb"(?i)\bsha256\b|\bsha1\b|\bmd5\b"), 2, "hash algorithm string"),]CLIENT_SIGNALS = [ (re.compile(rb"https://"), 5, "https URLs present"), (re.compile(rb"http://"), -6, "plaintext http URLs present"), (re.compile(rb"(?i)CURLOPT_SSL_VERIFYPEER\s*;?\s*0|" rb"verify.*= *(false|0)|" rb"SSL_VERIFYPEER"), 5, "TLS verify-peer references (check the value!)"), (re.compile(rb"(?i)ssl_verify|verify_cert|" rb"cert_validat"), 4, "certificate validation logic markers"), (re.compile(rb"(?i)signature|verify_sig|check_sig"), 4, "signature verification references"), (re.compile(rb"(?i)rollback|version_?floor|" rb"min_?version|anti_?rollback"), 5, "rollback/version-floor logic"), (re.compile(rb"(?i)\bA/B\b|slot_[ab]|inactive_?slot"), 3, "A/B slot scheme markers"),]
def entropy(data): if not data: return 0.0 c = Counter(data) n = len(data) return -sum((v / n) * math.log2(v / n) for v in c.values())
def analyze_pkg(path): data = open(path, "rb").read() print(f"== package: {path} ({len(data)} bytes, " f"entropy {entropy(data):.2f}) ==") hits = [] for rx, score, label in SIG_PATTERNS: m = rx.search(data) if m: hits.append((score, label, hex(m.start()))) hits.sort(reverse=True) if hits: print("[signature evidence]:") for score, label, off in hits[:8]: print(f" [{score}] {label} @ {off}") else: print("[!] no signature evidence found — " "unsigned package?") return hits
def analyze_client(path): try: blob = open(path, "rb").read() except OSError as e: print(f"[!] client unreadable: {e}") return [] print(f"\n== update client: {path} " f"({len(blob)} bytes) ==") signals = [] for rx, score, label in CLIENT_SIGNALS: matches = rx.findall(blob) if matches: signals.append((score, label, len(matches))) signals.sort(key=lambda s: -abs(s[0])) verdict_score = 0 for score, label, n in signals: print(f" [{score:+d}] {label} ({n} hits)") verdict_score += score * min(n, 3) verdict = ("review manually — mixed signals" if abs(verdict_score) < 10 else "likely validates (confirm by RE)" if verdict_score > 0 else "likely DOES NOT validate — interception " "test recommended") print(f" client posture: {verdict} " f"(score {verdict_score})") return signals
def ladder(signals): """Map findings to the signing-model ladder.""" txt = " ".join(label.lower() for _, label, _ in signals) if "plaintext http" in txt: return "unsigned-class risk: plaintext transport" if "signature verification references" in txt: return ("payload-signed class: verify order " "and anchor (pre-flash? boot-ROM?)") return "unclear — RE the updater (H3) for the chain"
def main(): ap = argparse.ArgumentParser() ap.add_argument("--pkg") ap.add_argument("--client") ap.add_argument("--json") args = ap.parse_args() if not args.pkg and not args.client: sys.exit("give --pkg and/or --client") out = {} if args.pkg: out["package"] = analyze_pkg(args.pkg) if args.client: sig = analyze_client(args.client) out["client"] = sig out["ladder"] = ladder(sig) print(f"\n[ladder] {out['ladder']}") if args.json: json.dump(out, open(args.json, "w"), indent=2, default=str) print(f"[+] {args.json}")
if __name__ == "__main__": main()Code Walkthrough
Section titled “Code Walkthrough”analyze_pkg()(lines 33-49) scans the package for signature evidence: PEM signature blocks, PKCS#7/CMS structures (the\x30\x82ASN.1 SEQUENCE shapes), algorithm strings — the presence check that separates signed from unsigned at a glance, with the honest caveat that absence-of-evidence is a RE question (custom formats hide).analyze_client()(lines 52-75) scores the update client’s binary strings: HTTPS presence (+5), plaintext HTTP (−6 — the finding that dominates), certificate-validation markers (+4/+5), signature-verify references (+4), rollback logic (+5), A/B slot markers (+3). The weighted verdict (lines 70-73) names three postures — validates, doesn’t, mixed — with the RE handoff stated.ladder()(lines 78-86) maps the client findings onto the signing-model ladder from the explanation: plaintext transport → unsigned-class risk; signature references → payload-signed class with the placement question (pre-flash? boot-ROM anchored?) — the ladder is the report’s conceptual frame.- The negative scoring (line 42) is the design’s teeth: plaintext HTTP subtracts, so a client with both HTTPS and HTTP strings reads as mixed and lands in the manual-review bucket — the tool refuses to grade ambiguity as pass.
How It Reaches The Target
Section titled “How It Reaches The Target”The update chain reaches every device the vendor ever shipped — the one surface whose compromise is permanent, fleet-wide, and vendor-sanctioned-looking. The assessment reaches each link with its method: the package triage reaches the signature question (is anything signed, at which block), the client RE reaches the logic questions (download path, validation order, rollback guard), the interception test reaches the empirical answer (the MITM that succeeds is the chain’s grade), and the SBOM reaches the third-party estate (which components, which versions, which inherited CVEs). The findings reach three owners: the vendor’s engineering team gets the signing-ladder placement and the client’s validation posture (the fix list), the product-security program gets the SBOM’s inherited-CVE table (the patch and EOL conversation), and the customer gets the honest advisory (what the device trusts, what its update channel withstands). The persistence reach is the module’s capstone: the firmware-patch persistence from H4, delivered through the vendor’s own channel, is the attack whose control is this module’s whole checklist — signed images verified in the boot anchor, anti-rollback counters, validated clients. The SBOM reaches the procurement conversation: the customer who can ask “what’s inside and is it maintained?” is the customer whose fleet inherits fewer CVEs, and the SBOM is the artifact that makes the question answerable. The provisioning chain reaches H9/H11: per-device identities minted at manufacture, signing-key custody (HSM-held, not on engineers’ laptops), and the rotation design — the chain’s root, assessed for the trust everything above it depends on. And the whole assessment reaches the H12 lifecycle as the supply-chain acceptance tier: per-release update-chain checks (signature, transport, rollback, SBOM freshness), trended, gating shipment.
Exploitation Chain
Section titled “Exploitation Chain”- Acquire — vendor update package + client binary from H2’s extracted rootfs
- Package — updcheck.py: no signature evidence found; entropy profile matches plain squashfs
- Client — RE signals: http:// URLs present, no signature-verify references, no rollback logic
- Interception — authorized test: proxy position serves modified package to OWNED lab device
- Accept — lab device flashes the modified image: fleet-writable channel proven
- Persist — modified image includes persistence per H4’s pattern: survives reboot, looks native
- Scope — same channel serves the whole model line: fleet-scale persistence demonstrated (lab only)
- Report — supply-chain finding: unsigned channel + client without validation; remediation: signing chain, boot anchor, TLS, SBOM, key custody
Building Your Own Tool
Section titled “Building Your Own Tool”sbomscan.py — the SBOM-less component scanner: walks an extracted rootfs, fingerprints known third-party components via their version-banner strings (OpenSSL, busybox, zlib, mbedtls, uClibc, dropbear, lighttpd, linux kernel banners), normalizes them into component/version pairs, and emits an SBOM-shaped inventory (JSON in a CycloneDX-lite shape [verify current spec]) with CVE-mapping hints (CPE-shaped strings for downstream tools). stdlib only.
Design decisions
Section titled “Design decisions”- Banner fingerprints over heuristics. Components self-identify (
OpenSSL 1.1.1w 21 Sep 2023,BusyBox v1.30.1) — the banner regexes are high-precision; where banners are stripped, the tool reports the component as suspected (from binary-size/strings signatures) rather than confirmed — confirmed vs suspected is the honesty line. - CPE-shaped output. The inventory emits
cpe:2.3:a:openssl:openssl:1.1.1w:*:*:*:*:*:*:*-shaped strings [verify CPE naming per component] so downstream CVE mappers (cve-bin-tool-class) consume it without translation. - Rootfs-wide, one pass. The walk reads every file once, matching all banners — the whole-rootfs SBOM in minutes, suitable for CI (the per-release SBOM gate).
- CycloneDX-lite JSON. The output shape mirrors CycloneDX component entries [verify spec fields] enough for tool ingestion, labeled “lite” honestly — full spec compliance is an export adapter, not a redesign.
Full source
Section titled “Full source”#!/usr/bin/env python3"""sbomscan.py — banner-fingerprint SBOM generator (rootfs in).Usage: python sbomscan.py squashfs-root/ [--json sbom.json]Fingerprints components via version banners; CPE-shaped hints.stdlib only."""import argparse, json, os, re, sys
FINGERPRINTS = [ (re.compile(rb"OpenSSL\s+(\d+\.\d+\.\d+[a-z]?)"), "openssl", "openssl"), (re.compile(rb"BusyBox\s+v(\d+\.\d+\.\d+)"), "busybox", "busybox"), (re.compile(rb"zlib\s+version\s+(\d+\.\d+\.\d+)"), "zlib", "zlib"), (re.compile(rb"mbed TLS\s+(\d+\.\d+\.\d+)"), "mbedtls", "mbedtls"), (re.compile(rb"uClibc[- ](\d+\.\d+\.\d+)"), "uclibc", "uclibc"), (re.compile(rb"dropbear[_ ]v(\d+\.\d+)"), "dropbear", "dropbear"), (re.compile(rb"lighttpd/(\d+\.\d+\.\d+)"), "lighttpd", "lighttpd"), (re.compile(rb"Linux version (\d+\.\d+(?:\.\d+)?)"), "linux-kernel", "linux"), (re.compile(rb"OpenSSH_(\d+\.\d+)"), "openssh", "openssh"), (re.compile(rb"curl/(\d+\.\d+\.\d+)"), "curl", "curl"), (re.compile(rb"musl libc \((\d+\.\d+\.\d+)\)"), "musl", "musl"), (re.compile(rb"sqlite\s+(\d+\.\d+\.\d+)", re.I), "sqlite", "sqlite"),]
def cpe_for(vendor, product, version): return (f"cpe:2.3:a:{vendor}:{product}:{version}:" f"*:*:*:*:*:*:*")
def scan_root(root): comps = {} for dirpath, _, names in os.walk(root): for n in names: p = os.path.join(dirpath, n) try: if os.path.getsize(p) > 64 << 20: continue data = open(p, "rb").read() except OSError: continue for rx, product, vendor in FINGERPRINTS: m = rx.search(data) if m: ver = m.group(1).decode() key = (product, ver) rec = comps.setdefault(key, { "product": product, "vendor": vendor, "version": ver, "evidence": []}) if len(rec["evidence"]) < 3: rec["evidence"].append( os.path.relpath(p, root)) return comps
def main(): ap = argparse.ArgumentParser() ap.add_argument("root") ap.add_argument("--json") args = ap.parse_args() if not os.path.isdir(args.root): sys.exit(f"not a directory: {args.root}") comps = scan_root(args.root) if not comps: print("[-] no component banners found " "(stripped builds? extend FINGERPRINTS)") return rows = sorted(comps.values(), key=lambda c: c["product"]) print(f"== SBOM (banner-confirmed): " f"{len(rows)} components ==") print(f"{'product':<16} {'version':<12} evidence") print("-" * 60) for c in rows: print(f"{c['product']:<16} {c['version']:<12} " f"{', '.join(c['evidence'][:2])}") cdx_lite = { "bomFormat": "CycloneDX-lite (sbomscan)", "specVersion": "lite-1", "components": [ {"name": c["product"], "version": c["version"], "supplier": {"name": c["vendor"]}, "cpe": cpe_for(c["vendor"], c["product"], c["version"]), "evidence": c["evidence"]} for c in rows]} if args.json: json.dump(cdx_lite, open(args.json, "w"), indent=2) print(f"\n[+] {args.json} — feed to CVE mapper " f"(cve-bin-tool class [verify]) for the " f"inherited-CVE list")
if __name__ == "__main__": main()Walkthrough
Section titled “Walkthrough”- The fingerprint table (lines 14-32) encodes twelve high-precision banners — the components that dominate embedded Linux estates — each with vendor/product for the CPE shape. The table is the maintained asset: new estates add their components as one-line entries.
scan_root()(lines 41-60) walks once, matching all fingerprints per file (early size-skip for huge files), collecting up to 3 evidence paths per component — the provenance that tells the analyst which binary brought the component in.- The CycloneDX-lite shape (lines 74-88): component entries with name/version/supplier/CPE/evidence — labeled “lite” honestly (full CycloneDX is an export adapter), CPE-shaped for the CVE-mapper handoff.
- The stripped-build branch (lines 66-68): no banners found is a reported outcome with its two likely causes (stripped builds, unmapped components) — the tool’s limits stated in its output.
Compilation and test procedure
Section titled “Compilation and test procedure”update chain audit from H2’s extraction
Section titled “update chain audit from H2’s extraction”$ python updcheck.py —pkg fw_v2_update.bin
—client squashfs-root/usr/sbin/updater
== package: fw_v2_update.bin (8388608 bytes, entropy 5.87) ==
[!] no signature evidence found — unsigned package?
== update client: squashfs-root/usr/sbin/updater (148 KB) == [+5] https URLs present (2 hits) [-6] plaintext http URLs present (3 hits) [+4] signature verification references (1 hits) client posture: review manually — mixed signals (score 2) [ladder] unsigned-class risk: plaintext transport
SBOM from the same rootfs
Section titled “SBOM from the same rootfs”$ python sbomscan.py squashfs-root/ —json sbom.json == SBOM (banner-confirmed): 9 components == product version evidence
Section titled “$ python sbomscan.py squashfs-root/ —json sbom.json == SBOM (banner-confirmed): 9 components == product version evidence”busybox 1.30.1 bin/busybox dropbear 2020.79 usr/sbin/dropbear linux-kernel 4.14.180 boot/zImage lighttpd 1.4.54 usr/sbin/lighttpd mbedtls 2.16.9 lib/libmbedcrypto.so openssl 1.1.1w usr/bin/openssl … [+] sbom.json — feed to CVE mapper for the inherited-CVE list
Detection considerations
Section titled “Detection considerations”Supply-chain defense is pipeline architecture: signing verified in the boot anchor (H11’s chain — the updater’s check is not the anchor), anti-rollback counters enforced, update clients with full TLS validation, and the telemetry — update events logged server-side (which device took which version, when) so fleet state is queryable during an incident. The SBOM becomes the standing telemetry: per-release SBOMs diffed (component upgrades tracked), CVE mappings re-run per advisory, and the EOL conversation data-driven (a component out of support is a dated risk, not a surprise). Your own tooling hygiene: updcheck.py/sbomscan.py read files; the interception test is infrastructure-gated (own fleet, own proxy position) per the engagement architecture. Wazuh/SIEM mapping of update telemetry [verify per vendor] joins the fleet’s update state into the SOC’s view — the fleet that knows which device runs which version is the fleet that answers “are we exposed to CVE-20XX-XXXX” in minutes. The standing metrics: signed-and-anchored percentage of the estate (the signing-ladder distribution), SBOM coverage per release (CI-gated), known-CVE count per device model (trending), and update-adoption latency (how fast devices take the fixed version — the metric that measures the channel’s actual reach).
Improvements to add
Section titled “Improvements to add”- Signature verification execution: actually verify detected signatures against vendor-published keys where available (the presence check becoming a validity check).
- Delta-package support: parse bsdiff/delta update formats and reconstruct the target image for analysis.
- CVE mapper integration: shell to cve-bin-tool-class tooling with the generated CPEs and merge results into the report [verify tooling].
- SBOM diff mode: per-release component changes (the fwdiff pattern applied to the SBOM) — new components surface immediately.
- Key-custody questionnaire: emit the signing-key custody interview (where are keys, who holds them, how do they rotate) as a structured form — the chain’s root, documented.
Detection — Defender View
Section titled “Detection — Defender View”| Signal | Source | ID | Notes |
|---|---|---|---|
| Unsigned update packages | updcheck CI gate | per release | The ladder’s floor |
| Plaintext transport in client | updcheck client scan | design finding | Interception-class risk |
| Signature verify placement | H3 RE review | design review | Pre-flash vs post-flash |
| Rollback guard absence | updcheck signals | design finding | Downgrade exposure |
| SBOM coverage per release | sbomscan CI | per release | Component visibility |
| Inherited-CVE count | CVE mapper | per model | The estate’s debt |
| Update adoption latency | Update telemetry | fleet metric | Channel reach measure |
| Provisioning key custody | Chain review | H9 bridge | Root-of-trust question |
| Boot-anchor verification | H11 chain audit | design review | The strongest model |
| Design-review acceptance rate | H12 program metric | per product gen | Lifecycle metric |
Evasion Techniques
Section titled “Evasion Techniques”The update-layer evasion runs between channel hardening and delivery stealth. Trusted-channel riding: attacks that compromise the vendor’s own build/distribution (the supply-chain classic) need no technical evasion of the device’s checks — the signed package carries the payload; countered by build-pipeline security (the vendor’s CI is part of the estate’s threat model), signing-key custody (HSM, M-of-N release), and the SBOM’s component-integrity checks (the build’s own inputs audited). Partial-signature exploitation: header-signed or section-signed formats leave unsigned regions (payload, resources) — countered by the full-image signature model and by the RE that maps exactly which bytes the signature covers (the updcheck finding’s detail). Rollback laundering: replaying old-but-signed images to reintroduce patched vulnerabilities — countered by monotonic counters (the device refuses version regressions) and by key rotation that invalidates old signatures. Client fingerprinting: update clients that serve different behavior to suspected MITMs (fingerprinting the proxy) — countered by the RE review that finds the fingerprint logic, and by the honest note that a client which behaves differently under audit is reporting its own suspicion. SBOM opacity: stripped banners, static-linking without version strings — countered by the banner-table extension, by binary-similarity matching against known builds [verify tooling maturity], and by the procurement requirement that vendors ship SBOMs. The meta-point: the update chain’s evasion surface is the vendor’s process as much as the device’s code — the chain is only as strong as its build pipeline, its key custody, and its willingness to publish what it ships, which is why the assessment reviews the pipeline’s evidence, not just the binary’s behavior.
⚠️ What caught me: the first update-chain assessment I ran “confirmed” a plaintext HTTP download channel from the client’s strings — three http:// URLs, no signature references anywhere — and I reported the fleet as MITM-ownable. The vendor’s rebuttal was a single sentence: “the HTTP URLs are the fallback; production uses the signed HTTPS channel, and the signature is verified by the bootloader.” Both halves were checkable in an hour, and neither held up as stated: the HTTPS fallback turned out to accept self-signed certificates (the RE showed the verify flag set to false on one path), and the bootloader verification ran after the flash write on the affected models. My original finding was directionally right but mechanically wrong in two details — and the vendor’s rebuttal was wrong in two more. Since then, every update-chain claim in my reports carries the evidence at its own hop (this URL, this flag, this verify order, this counter) — because the update chain’s security is a chain of specific claims, and a rebuttal is only as good as the first unverified detail it rests on.
Lab Practice
Section titled “Lab Practice”- Chain audit: run
updcheck.pyagainst your lab device’s update package and client binary; classify the signing-ladder placement and client posture — the update chain, mapped on hardware you own. - Interception drill (bench-gated): stand up a proxy position against your own lab device’s update flow and attempt a modified-package delivery; document exactly what the client accepts — the empirical finding, with evidence.
- SBOM build: run
sbomscan.pyon H2’s extracted rootfs; feed the JSON to a CVE mapper and produce the inherited-CVE table — the SBOM pipeline, end to end. - Downgrade test: keep an old signed (or unsigned) lab image; verify whether your lab updater accepts it after a newer version — the rollback guard, tested.
- CI gate drill: wire fwanalyze/updcheck/sbomscan into a script that fails on secret-class findings and unsigned packages, then “ship” a deliberately bad build to yourself and watch the gate stop it — the pipeline control, rehearsed.
Interview Questions
Section titled “Interview Questions”- Technical: Rank the signing models (unsigned → boot-ROM anchored) by what each actually protects, and name the bypass for each weaker tier.
- Technical: What does an SBOM contain, how do banner fingerprints build one from a rootfs, and what are the honesty limits of banner-based detection?
- Scenario: The vendor claims “signed updates” but your package analysis finds no signature evidence. Diagnose both possibilities — and what evidence settles it.
- Conceptual: Why is the update channel the fleet’s highest-leverage control, and how does H4’s persistence demo change when delivered via the vendor’s own channel?
- Conceptual: How do signing, anti-rollback, and key custody divide the update chain’s risk — and which failure is worst?
- Deep-dive: Design the supply-chain acceptance program: per-release gates (fwanalyze/updcheck/sbomscan), pipeline security review, key custody, and the EOL governance.
- Practical: The estate’s devices accept unsigned updates and the vendor has no fix scheduled. What are the compensating controls, in order, and what do they actually catch?
Related Modules
Section titled “Related Modules”H2 (Firmware Extraction & Analysis) · H9 (IoT Cloud & Mobile Attack Surface) · H11 (Hardware Security Controllers (TPM/Secure Element)) · H4 (Embedded Linux & Root Shells) · M4 (Unpacking and Deobfuscation)