Jacob Baines pointed out that over 90 different binaries that implement network services can be reached by speaking the Winbox protocol (see The Real Attack Surface in his excellent blog post).

We were not aware of all that reachable functionality when we started poking around with RouterOS and did not invest the time to reverse engineer the binaries that spoke Winbox, so we just went ahead and looked at the few binaries that were explicitly listening on the network.

Most (all?) services in RouterOS seem to have been implemented from scratch, so there are thousands of lines of custom low level code waiting to be audited.

Our objective was to achieve unauthenticated remote code execution, and on a first look the binaries for common services such as FTP or Telnet did not provide much reachable functionality without providing credentials. This made us turn to other services that might not be enabled by default but require the implementation of a rich feature set. The fact that these services are not enabled by default means they might have been neglected by other attackers wanting to maximize their ROI on vulnerabilities that affect default installations of RouterOS and are therefore much more valuable.

By following this rationale and inspecting the available services we decided to take a look at the SMB implementation.

Finding the vulnerability

We know we want to find vulnerabilities in the SMB service. We have the virtual machine setup, the service running, we have full shell access to the device and we can debug any processes. How do we find a vulnerability?

One option would be to disassemble the binary and look for insecure coding patterns. We would identify interesting operations such as strcpy, memcpy, etc. and see if the correct size checks are in place. We would then see if those code paths can be reached with user controlled input. We could combine this with dynamic analysis and use our ability to attach to a running process with GDB to inspect registers at runtime, memory locations, etc. However, this can be time consuming and it is easy to feel frustrated if you do not have experience doing reverse engineering, especially if it is a large binary.

Another option is to fuzz the network service. This approach consists of sending data to the remote service and checking if it causes an unexpected behavior or a crash. This data will contain malformed messages, invalid sizes, very long strings, etc.

There are different ways to conduct the fuzzing process. The two most popular strategies are generation and mutation-based fuzzing. Generation-based fuzzing requires knowledge of the protocol to build test cases that comply with the format specified by the protocol and will (most likely) result in a more thorough coverage. More coverage means more chances of hitting vulnerable code paths and therefore more bugs. On the other hand, mutation-based fuzzing assumes no prior knowledge of the protocol being fuzzed and takes much less effort at the cost of potentially poor code coverage and additional difficulties in protocols that need to compute checksums to ensure data integrity.

We decided to try our luck with a dumb fuzzer and chose the Mutiny Fuzzer tool that had been released a few months earlier by the Cisco Talos team. Mutiny takes a sample of legitimate network traffic and replays it through a mutational fuzzer. In particular, Mutiny uses Radamsa to mutate the traffic.

Radamsa mutation example

Performing this kind of fuzzing has the benefit of being very quick to get up running and, as we will see, might provide great results if we have a good selection of test cases that stress various features.

Putting this together, the steps to fuzz a network service are:

Capture legitimate traffic

Create a Mutiny fuzzer template from the resulting PCAP file

Run Mutiny to mutate the traffic and replay it to the service

Observe what happens with the running service

Mutiny does provide a monitoring script that can be used to (d’oh!) monitor the service and identify weird behavior. This can be accomplished by implementing the monitorTarget function as described in https://github.com/Cisco-Talos/mutiny-fuzzer/blob/master/mutiny_classes/monitor.py. Sample checks could be pinging the remote service or connecting to it to assess its availability, monitoring the process, logs, or whatever else might signal weird behavior.

In this case, the SMB service will take a while to restart after a crash and log a stack trace message, so we decided it was not worth scripting any monitoring actions. Instead, we just captured the traffic throughout the fuzz process with Wireshark and relied on the default behavior of Mutiny, which is to exit when the request fails due to a connection refused error, meaning that the service is down. This is rather rudimentary and leaves a lot of room for improvement, but it was enough for our tests.

It is important to enable full logging before we initiate the fuzzing process. This could prove useful to track any crashes that might occur, as the full stack trace will be included in the logs that are located in /rw/logs/backtrace.log. This can be configured from RouterOS’ web interface.

Enable all logs to be written to the disk

Another thing that proved useful was running the binary in an interactive console to get the debug output in real time. This can be achieved by killing the running process and relaunching it from the full-fledged terminal. Errors and general status of processed requests will be printed.

Get debug output as requests are processed

Now that we have a high level overview of the steps involved, let’s recap and actually fuzz the SMB service.

First we clone https://github.com/Cisco-Talos/mutiny-fuzzer.git and follow the setup instructions.