This post explores a controlled method for using legitimately uploaded PLC programs to extract otherwise unavailable, memory-mapped firmware images from industrial controllers. Using only the device's own programming interface, and without touching the hardware, we recovered a complete copy of the running firmware. No specialized equipment, no physical access, nothing more than maintenance access level. The same channel vendors provide for uploading control logic was sufficient to read the device's memory.
The focus is a firmware extraction strategy: native code execution with no memory isolation can eliminate the boundary between legitimate use and unauthorized access.
Separately, while validating the approach, we came across a firmware protection mechanism that appeared robust, an apparently encrypted opaque binary blob, but it turned out to be a thin layer of custom encoding that dissolved under modest scrutiny. This reinforces a broader, well-known point about the limits of relying on obscurity to protect firmwares.
When firmware is distributed as opaque blobs, analyzing device behavior requires a cleartext copy of the running firmware image. Physical flash access is destructive or impractical in many environments. Instead, this controlled approach uses the legitimate program download and execution path to run a small, read-only routine that streams flash contents back over the management channel.
This technique presumes a researcher with the ability to upload and execute code via the vendor's management protocol but requires no hardware tampering or invasive probing.
All code and assembly examples are intentionally sanitized; the purpose is to inform defenders and aid responsible vendor remediation.
TL;DR
Got a PLC to analyze. Firmware from the vendor website looked encrypted. The unit runs raw PowerPC blobs. Reversed the upload protocol, reversed the blobs format, pushed a tiny flash dumper, dumped flash, and, surprise, the firmware was not encrypted; it was just lightly encoded with a custom transform.
First Look at the Firmware
The latest firmware was available on the device vendor's website, so we downloaded a fresh firmware image to begin our analysis.
The firmware file was ASCII text with CRLF line terminators, it looked like base64 encoding. We removed the CRLFs and tried to base64 decode it. base64 -d spat out invalid input. Taking another look at the file, we saw the offending character, the "-". Maybe the blob was base64URL encoded? We decided to decode it with Python.
It worked! The output was binary data with no readable strings. Binwalk couldn't carve anything, entropy was high, it looked encrypted.
Dumping the flash the hard-ware way was not an option; we don't want to harm any device here, I mean, not that soon.
Reversing the Network Protocol and the Program Format
We already received some pcaps by sniffing a PLC program upload from the vendor tool. To reverse the network protocol, iterative tests did most of the heavy lifting. Passive packet captures were analyzed to discover packet framing and sequence patterns. Two things stood out quite fast:
- The vendor tool seemed to upload raw PPC machine code. No VM, no bytecode wrapper.
- There was a management channel to transmit back PLC program output.
We reimplemented the tiny subset of the protocol needed to upload, execute code and read the PLC program output. After that, we started to analyze the packets containing compiled code.
After a fair amount of differential testing, we came up with a possible executable format and figured out which parts were code, address, and checksum. Function discovery was done by scanning for the standard PPC prologue. This worked reliably on the samples. We simply treated each prologue hit as a function start and the next hit as the previous function's end. The extracted executable images were not pure PowerPC blobs: they began with an image header, followed by PPC big-endian code, metadata, a symbol/name table, and a short fixed footer.
Once the format was understood well enough, we were able to craft PPC payloads the PLC would accept. That meant the PLC would run our custom code if it looked like a legitimate program.
Firmware Extraction Strategy
- Identify the memory-mapped flash region (via register inspection from user code).
- Optional, reimplement the minimal vendor upload protocol to push native PLC programs and collect their output.
- Upload a compact, read-only payload that iterates flash and streams reads over the management channel.
- Collect the streamed data.
Where is the Flash?
The technique is presented using as an example a PLC equipped with a Freescale MPC852T PowerQUICC CPU, and a parallel NOR flash memory like the Spansion S29AL032D. This flash is typically used as memory-mapped external memory through the MPC8xx memory controller.
This means that our PLC wires the flash to one of the MPC852T external memory banks; the CPU sees it at some fixed address, determined by how the board is designed.
Once the base address was known, we would be able to read the flash contents using standard PPC load instructions. The idea was to upload and execute a crafted PLC program that would dump the entire flash memory.
The MPC852T supports multiple memory banks. The address depends on which bank the board designer used and how BRx/ORx were set.
BRx (Base Register x) and ORx (Option Register x) are memory controller registers, BRx tells the bank's base address and attributes, ORx defines the size/mask and timing. They are memory-mapped internal registers, to read them, we must know the Internal Memory Map Register (IMMR) base address, then add the proper offset for the memory controller register. The IMMR is a register used to define the base address of the chip's internal memory-mapped register block.
So, the procedure is:
- Read IMMR
- Inspect BR0..BR7
- Find a bank marked valid with a plausible base address and size
- Validate by performing some reads at the candidate base.
We needed to mask the value returned by the mfspr instruction because IMMR does not hold a pure base address. It holds the internal module mapping value, and the on-chip register block is positioned only on a 64 KiB boundary, so only the upper bits are the base. Memory-controller registers are fixed offsets from that internal block; the manual defines BR0 at offset 0x100, OR0 at 0x104, BR1 at offset 0x108 and so on.
We had to check each memory controller bank register to identify which bank was our flash by looking at each BRx/ORx pair and finding the bank that matched the flash.
What to check in BRx:
- V = valid. Ignore banks that are not enabled.
- MS = GPCM. Parallel NOR/EPROM-style devices are typically put on the General-Purpose Chip-Select Machine, not UPM/SDRAM-style interfaces.
- BA = base address. This is the flash base.
A good reference to parse BR registers values was U-Boot source mpc8xx.h, this was for sure much clearer than checking bit-numbering convention in the NXP manual. No need to thank me.
Our BR0 already looked exactly like a plausible parallel NOR bank, 0x40000801, which gives:
- V = 1
- MS = 0x00000000 = GPCM
- BA = 0x40000000
Writing a Flash Dumper
Now that the base flash address was found, we had to code a little dumper program to read memory starting from this address and stream the flash content via the vendor tool management channel, then save the output to file.
Using the vendor tool, we compiled a dummy PLC program that just printed out a static 1024-byte string each PLC program loop. We managed to apply a small same-size patch to the generated executable image, meaning no code sections were resized, and only the image header checksum had to be recalculated. The patch repurposed the existing printing path, but each iteration it read from our base address, populated the 1024 bytes output buffer and returned data through the existing output mechanism, incrementing at each PLC program loop iteration.
Safety constraints:
- Read operations only
- Rate-limited and timed to avoid upsetting the management protocol
The PLC executed the routine and streamed back the full flash dump.
Analyzing the Firmware
The blobs from the vendor website looked opaque, high-entropy and with no obvious headers. Analyzing the dumped firmware, we uncovered the actual decode routine: the base64 alphabet differed from RFC 4648 in two ways, letter order was inverted: a-z=0-25, A-Z=26-51 and the last symbol was "-" (0x2d) instead of "/" (0x2f). Digits (0-9=52-61) and "+" (62) were identical to standard. No padding ("=" was never used). Lines were 64 chars wide, CRLF terminated.
This one was a small, satisfying unraveling: the vendor tried to hide firmware under a custom encoding. If the goal is obscurity, expect it to be peeled.
Technique Limitations
This workflow is applicable in scenarios where the PLC program compiles to native code and runs without memory isolation. The PLC lacks separation between application code and firmware memory. The technique requires an executable upload path, sufficient privileges and is dependent on device architecture and memory controller design.
Closing Notes
Using legitimate PLC program uploads to read back firmware is a practical, non-destructive method for obtaining firmware images for research and triage. This technique supports reproducible security analysis while minimizing operational risk.
The lack of enforced separation between application logic and firmware memory could be dangerous: when a PLC program is compiled to native code and executed without enforced memory isolation, meaning application code and firmware memory share the same trust boundary, a flaw in the PLC program, or the ability to influence it, can potentially affect the integrity of the underlying firmware and the environment itself.
For asset owners and defenders, the broader lesson is that industrial environments need visibility into how engineering workstations, management protocols, and controllers interact. Monitoring legitimate maintenance channels, understanding device behavior, and correlating unusual activity across OT assets are essential steps toward reducing risk in environments where traditional IT security assumptions do not always apply.


.webp)
.webp)


