“Good enough” emulation: Fuzzing a single thread to uncover vulnerabilities
A Talos researcher used targeted emulation of the Socomec DIRIS M-70 gateway’s Modbus thread to uncover six patched vulnerabilities, showcasing efficient tools and methods for IoT security testing.
“Good enough” emulation: Fuzzing a single thread to uncover vulnerabilities
Wednesday, February 18, 2026 06:00
- A Cisco Talos researcher worked around the limitations of hardware-level Code Read-out Protection (RDP) on the Socomec DIRIS M-70 gateway by pivoting from physical debugging to a "good enough" emulation approach.
- By focusing on emulating only the single thread responsible for Modbus protocol handling rather than the entire system, the author demonstrates how a streamlined emulation strategy can effectively surface vulnerabilities in complex industrial Internet of Things (IoT) devices.
- The post highlights the integration of the Unicorn Engine and AFL for coverage-guided fuzzing, as well as the use of the Qiling framework to visualize code coverage and perform root cause analysis on crashes.
- This research led to the discovery of six CVEs related to denial-of-service vulnerabilities, all of which have been patched by the manufacturer through Cisco’s Coordinated Disclosure Policy.
This blog describes efforts at emulating functionality of the Socomec DIRIS M-70 gateway to discover vulnerabilities. In vulnerability research, knowing which tool to use for the job at hand is crucial. This post will highlight multiple emulation tools and approaches used, detail the benefits and drawbacks of each, and reveal how a "good enough" approach can really pay off.
Project background
The M-70 gateway facilitates data communication over both RS485 and Ethernet networks, supporting a wide array of industrial communication protocols, including Modbus RTU, Modbus TCP, BACnet IP, and SNMP (v1, v2, and v3). This gateway is vital for energy management in sectors like critical infrastructure, data centers, healthcare, and the general energy sector. However, as an industrial Internet-of-Things (IIoT) device, vulnerabilities in the M-70 or similar gateways can lead to severe consequences, including operational disruption, financial losses, and manipulation of industrial processes. These risks are severe, especially in critical infrastructure where a compromised gateway could lead to widespread outages or equipment damage.
This large attack surface, the impact of vulnerabilities, and the fact that the M-70 gateway runs the real-time operating system (RTOS) µC/OS-III, made it an attractive research target. There was an expectation that prior familiarity with this RTOS, gained through previous work, would offer an advantage in understanding the device’s intricacies.
Why emulate? The debugging roadblock
Having insight into the system is critical to performing root cause analysis of any discovered vulnerabilities. Ideally, one would have real hardware and the ability to debug the software running on that hardware. The presence of an unpopulated JTAG header on the board was an exciting initial find.
Figure 1. Unpopulated JTAG header.
However, the presence of a JTAG header does not always guarantee debug access. There are a variety of reasons for this, but in the case of the M-70 gateway, code read-out protection (RDP) Level 1 is enabled. This is a feature of STM32 microcontrollers, which provides flash memory protection. There are three possible levels (0 – 2) of this protection. Level 1 prevents flash memory reads while debugger access is detected (e.g., JTAG). When attached via JTAG, no access to Flash memory is permitted, essentially preventing debugging of the running software. The intention behind this protection is to prevent third parties (like myself) from dumping the contents of flash via JTAG.
This was bad news. It was not possible to step through the code processing malicious network messages to determine the cause of device disruption. The address for the $pc register (see Figure 2) indicates that the MCU has entered a core lock-up state.
Figure 2. RDP Level 1 debug output.
In this project, two significant opportunities arose regarding code and memory access. First, an unencrypted firmware update file was available, providing the code that would be written to flash and eliminating the need to read it directly from memory. The second is that the ability to access SRAM while a debugger is attached is allowed with RDP Level 1 enabled (see Figure 2). This made it feasible to dump the contents of SRAM during the device’s execution and capture a snapshot of dynamic data.
Figure 3. RAM dumping script.
While it was not possible to have fine-grained control over the processor’s state when dumping the SRAM contents, some influence could be exerted (e.g., opening a TCP connection with the device before dumping the SRAM contents). The objects and data created as a result of this connection would be present when the CPU was halted for the SRAM dump.
Emulating with Unicorn
Emulation is one solution to this inability to debug the software natively. If the processing code of interest can be emulated, it is possible to gain visibility into the effects of a malicious message on the state of the M-70. When emulating software, it’s important to recognize that the emulated code might not behave exactly like it would on the physical device. Full system emulation aims to mitigate this by mimicking device behavior as closely as possible, but it requires deep knowledge of system internals and significant development to accurately emulate peripherals. The focus for this project was on vulnerabilities within the Modbus protocol handling code, which ran in a single thread of the M-70 application. Rather than spending the time required for full system emulation, the decision was made to emulate only the Modbus thread. Admittedly, emulating this single thread would not be true to the device’s real-world operation. However, this deliberate time trade-off was made with the hope that it would still be “good enough” to find vulnerabilities in the Modbus protocol handling code.
The first step in this process involved utilizing the Unicorn Engine, a powerful CPU emulation framework supporting various architectures. It provided the core capability to run the Modbus thread’s code in a controlled software environment where I could then inspect the system state when processing network data.
The emulator was implemented with an entry point in the Modbus processing thread, positioned after network data had been received. Before emulating this code, the argument registers $r2 and $r3 which originally contained a pointer to network data and its length were modified to reference data originating from the emulator, along with it’s corresponding length. Once the argument registers were updated, emulation could begin and continue until that thread returned from the message processing function.
The need to fuzz
Manual inspection of network processing code is sometimes sufficient; however, this Modbus thread supports over 700 unique message types, defined by supported register values and something referred to as service identifier. The combination of these two values within a Modbus message influenced the code path of data processing, and with so many code paths to investigate, automation was clearly necessary.
Unicorn’s AFL integration made it simple to fuzz using the emulator, automatically exploring these many execution paths. AFL uses coverage-guided test case generation to maximize the number of different code paths explored. This tool provided precisely the type of automation that was necessary. It was simple integrating AFL fuzzing into the Unicorn script, requiring only the addition of the place_input_callback function and a call to unicorn_afl_fuzz (see Figure 5).
Figure 5. Unicorn AFL integration.
Triage and debugging
[...]