Reverse engineering embedded firmware means pulling a binary image out of a microcontroller, baseband processor, UEFI SPI flash, or secure enclave and reconstructing what it actually does without any vendor documentation. The work sits next to static disassembly, dynamic instrumentation, emulation, fault injection, and glitching. On this blog, that means treating every vendor claim about “secure boot” or “hardware root of trust” as a hypothesis to test against silicon, not a marketing slide to believe. If you are here, you probably already know the datasheet is often a work of fiction and the real specification lives in the silicon bugs.
This walkthrough is not a generic “use Ghidra and hope” tutorial. It is a field note on the sequence that works when the target is a locked-down automotive ECU, a baseband image with a custom RTOS, or a UEFI capsule that refuses to parse. The steps assume you have a way to get the image out — JTAG, SPI dump, eMMC read, or fault-injected boot — and that you are willing to treat every abstraction layer as suspect.

Step 1: Acquire the Image Without Trusting the Vendor’s Tool
Vendor update tools are convenient and wrong. They often decrypt, decompress, or re-sign the image before it ever hits your disk, which means you are reversing a transformed artifact, not the firmware the device actually runs. The correct first move is to read the flash directly. For SPI NOR, that means a SOIC-8 clip and a programmer that does not try to be clever. For eMMC, that means a low-level reader that can dump the boot partitions, not just the user area. For baseband or SEP targets, that usually means finding a debug UART or a test point that exposes the internal bus.
If the device has a hardware root of trust that gates the flash, you have two choices: fault injection to bypass the read protection, or emulation of the boot ROM to extract the decryption keys. I have had more luck with the second approach on ARM64 targets where the boot ROM is small enough to emulate in an afternoon. The first approach works better on older microcontrollers where the read-out protection is a single fuse and a well-timed voltage glitch is enough to make the CPU skip the check.
Step 2: Identify the Architecture and Load Address Before You Disassemble
Loading a raw binary into Ghidra or IDA without knowing the base address is a waste of time. The disassembler will happily produce nonsense, and you will spend hours chasing branch targets that point into the middle of nowhere. The first thing to do is look for the vector table. On ARM Cortex-M, that is the first 32-bit word, which is the initial stack pointer, followed by the reset vector. On ARM64, the exception vectors are at a fixed offset from the base of the image, usually 0x0 or 0x200000. On x86-64 UEFI, the image is a PE32+ binary, and the load address is in the optional header.
If the image is encrypted or compressed, you need to find the loader. That is usually a small stub at the beginning of the image that sets up the memory controller, decrypts the payload, and jumps to it. The stub is often not encrypted, because the CPU has to execute it before the decryption engine is initialized. That is your entry point. Emulate the stub, dump the decrypted payload, and then disassemble that.
Step 3: Map the Memory and Peripherals From the Silicon, Not the Datasheet
Datasheets lie. They omit undocumented registers, mislabel interrupt numbers, and sometimes describe a completely different chip revision. The only reliable way to map peripherals is to read the silicon itself. If you have a working device, use a debugger to read the peripheral registers and compare them to the datasheet. If you do not have a working device, use the firmware’s own initialization code as the map. The code that writes to a specific address to configure a UART or a timer is telling you what that address does, even if the datasheet calls it “reserved.”
For baseband and SEP targets, the peripheral map is often split across multiple security domains. The application processor sees one view, the baseband sees another, and the secure enclave sees a third. You need to reconstruct all three views to understand the full attack surface. That means emulating the memory protection unit, not just the CPU.

Step 4: Reconstruct the Boot Flow and Trust Chain
Every locked-down device has a boot flow. The CPU starts in ROM, verifies the first-stage bootloader, which verifies the second-stage, which verifies the OS or application. The verification is usually a hash or a signature check. The bug is usually in the implementation, not the algorithm. Common failure points include:
- Hash comparison that stops after the first mismatch, allowing a timing side channel.
- Signature check that uses a key embedded in the same flash you can read.
- Boot mode pins that bypass verification entirely when strapped a certain way.
- Fault injection that skips the branch instruction after the verification call.
Reconstructing the boot flow means finding the verification code, identifying the key or hash, and then determining whether the check is actually enforced or just decorative. On one automotive ECU I worked on, the “secure boot” checked a CRC32 of the first 4 KB and then jumped to the rest of the image without any further validation. The vendor’s security whitepaper described a multi-stage chain of trust with ECC signatures. The silicon did not care.
Step 5: Disassemble With a Purpose, Not a Prayer
Once you have the image loaded at the correct base address with the correct architecture, you can start disassembling. But do not just scroll through the listing looking for strings. That is how you waste a week. Instead, work backward from the behavior you want to understand. If you want to find the update mechanism, look for the code that writes to flash. If you want to find the crypto, look for the AES S-box or the SHA constants. If you want to find the debug interface, look for the UART initialization and the command parser.
Use the cross-references. Every function that is called from the boot flow is part of the boot flow. Every function that is called from an interrupt handler is part of the interrupt handling. Every function that is called from a command parser is part of the command interface. The call graph is the map. The disassembly is just the terrain.
Step 6: Emulate the Interesting Parts Instead of the Whole System
Full-system emulation of a baseband or SEP is a multi-month project. Emulating a single function is an afternoon. The trick is to extract the function you care about, provide the minimal set of memory and peripheral stubs it needs, and run it in a controlled environment. For ARM64, that means QEMU in user mode or a custom Unicorn script. For x86-64 UEFI, that means a small EFI shell environment. For older ARM or MIPS targets, that means a custom emulator that implements only the instructions and peripherals the function actually touches.
The goal is not to boot the whole system. The goal is to answer a specific question: What does this function do with this input? Does it check a signature? Does it decrypt a payload? Does it write to a protected register? Emulation lets you answer that question without fighting the rest of the firmware.
Step 7: Validate Your Findings Against the Hardware
Reverse engineering is not complete until you have validated your findings against the actual device. That means taking the behavior you reconstructed and testing it on the silicon. If you think a certain command unlocks the debug interface, send that command and see if the UART responds. If you think a certain fault injection point bypasses the signature check, glitch the device and see if it boots your modified image. If you think a certain register controls the memory protection, write to it and see if the device crashes.
Validation is where most reverse engineering projects die. It is easy to produce a plausible-looking disassembly. It is hard to prove that your understanding matches the silicon. The difference is the difference between a blog post and a working exploit.

Common Pitfalls That Are Not in the Vendor’s App Note
Here are the mistakes I see repeatedly, including my own early ones:
- Trusting the string table. Strings are hints, not facts. A string that says “secure boot failed” does not mean the boot is secure. It means someone wrote an error message.
- Assuming the image is not encrypted. Many vendors encrypt the firmware but leave the decryption key in the boot ROM. If you can read the boot ROM, you can decrypt the image. If you cannot read the boot ROM, you need fault injection or emulation.
- Ignoring the memory protection unit. The CPU may be able to execute code from a region that it cannot read as data. That means your disassembler sees garbage, but the CPU sees instructions. You need to model the MPU to get a correct disassembly.
- Forgetting about the second core. Many baseband and automotive SoCs have multiple cores with shared memory. The code you are reversing may be running on a core you are not emulating, and the behavior you are seeing is the result of an inter-core race condition.
FAQ
What is the first step in reverse engineering embedded firmware?
The first step is to acquire the firmware image directly from the flash or storage medium, not through the vendor’s update tool. Vendor tools often transform the image, so a direct read gives you the actual bytes the device executes. For SPI flash, use a clip and programmer. For eMMC, use a low-level reader that can access boot partitions. For protected targets, you may need fault injection or emulation of the boot ROM.
How do I know the correct load address for disassembly?
Look for the vector table or the executable header. On ARM Cortex-M, the first word is the initial stack pointer and the second is the reset vector. On ARM64, the exception vectors are at a fixed offset. On x86-64 UEFI, the image is a PE32+ binary with the load address in the optional header. If the image is encrypted or compressed, find the loader stub, emulate it, and dump the decrypted payload.
What is the most common mistake when reversing firmware?
Trusting the vendor’s documentation or the string table. Datasheets omit registers and mislabel interrupts. Strings are hints, not facts. The only reliable map is the firmware’s own initialization code and the silicon itself. Validate every assumption against the hardware before you build an exploit or a patch.
Do I need to emulate the entire system to reverse a function?
No. Emulate only the function you care about, with minimal stubs for the memory and peripherals it touches. Full-system emulation is a multi-month project. A single function can be emulated in an afternoon with QEMU user mode, Unicorn, or a custom emulator. The goal is to answer a specific question, not to boot the whole device.
Next up on the bench: a teardown of a baseband boot ROM that uses a custom hash function the vendor calls “proprietary.” Spoiler: it is a truncated SHA-256 with a broken IV. If you have a target you want me to look at, send the image and a scope trace. No vendor whitepapers, please.