const {P,H2,H3,Callout,Code,Ul,Related,PostHead}=window;
function Article(){
return <>
<PostHead cat="/FIRMWARE" date="2026-07-31" title="Unpacking firmware without becoming the dump" lede="binwalk is not the finding. unblob is not the finding. The finding is which extracted binary actually speaks on the wire, and what it still trusts."/>
<P>A vendor “firmware.bin” is usually a container: bootloader, kernel, one or more filesystems, maybe a signature trailer. Analysts who dump the whole tree into a zip and call it reverse engineering have produced a copy, not a map.</P>
<Callout>Work from a copy. Hash the original. Do not flash anything back to a device unless the engagement says so.</Callout>
<H2>What the tools are</H2>
<H3>binwalk</H3>
<P>Signature scan plus entropy. Tells you “gzip at 0x80000, squashfs at 0x200000.” False positives are normal. Treat the map as leads.</P>
<H3>unblob</H3>
<P>Recursive extractors with fewer “carve everything” accidents than classic binwalk -e. Prefer it when the image is a matryoshka of vendor formats. Still not magic on unknown crypto blobs.</P>
<H3>unsquashfs, ubireader, jefferson, yaffshiv</H3>
<P>Filesystem-specific. Once you know it is squashfs / UBI / JFFS2 / YAFFS, use the tool that speaks that, not another round of carving.</P>
<H3>sasquatch, jefferson forks</H3>
<P>Vendor squashfs that stock unsquashfs hates. Use when the extractor errors and the entropy still looks like a filesystem. This is plumbing, not cleverness.</P>
<H2>Combination we actually run</H2>
<Code lang="sh">{`cp firmware.bin firmware.bin.orig
sha256sum firmware.bin.orig
unblob firmware.bin -o out/
# then inventory, do not Ghidra yet
find out -type f | head
# typical: squashfs-root/usr/sbin/httpd
#          squashfs-root/etc/shadow
#          squashfs-root/bin/busybox`}</Code>
<P>Inventory first:</P>
<Ul items={[
'Init: which binary is PID 1, which services start.',
'Remote surfaces: dropbear, httpd, vendor d-bus, custom UDP.',
'Secrets in configs: default passwords, Wi-Fi PSKs, cloud URLs, update keys sitting in world-readable files.',
'Update path: signed or just a tarball over HTTP.'
]}/>
<P>Only then pick <em>one</em> binary that is in scope — usually the parser that faces the network or the updater — and load it in Ghidra. The rest of the tree is context.</P>
<H2>What we report from an unpack</H2>
<P>Default credentials in the image. An update channel with no signature. An outdated dropbear. A web UI that still has the vendor debug endpoint. These are findings because they are in the bits you shipped.</P>
<P>We do not turn an unpacked httpd into a public exploit write-up. Layout notes and a risk register go to the customer. If they want a patch-diff later, that is a different SOW.</P>
<H2>When unpacking fails</H2>
<P>Encrypted outer envelope. Then the question is key storage (OTP, header, companion file), not “try more extractors.” If we cannot unwrap it under the authorization, we say so. Pretending binwalk will decrypt AES is how you fake a report.</P>
<Related items={[
['post-re-stack.html','The reverse-engineering desk'],
['post-stripped.html','What “stripped” actually means'],
['post-re.html','Offset oracle']
]}/>
</>;
}
window.Article=Article;
mountPost();
