Reverse Engineering Testo Saveris2 firmware

Jon Cederqvist
6 min readJan 22, 2021

The Testo Saveris2 line of products are pretty neat IIOT-devices. Industrial grade, WiFi-enabled, battery-powered data loggers with plenty of sensors to chose from. And they’re relatively cheap (for an industrial grade logger).

Testo Saveris 2 T1

The devices include a basic license for Testo’s cloud service, where you can view the devices recorded data and set simple alarm levels which can generate incidence reports which can be sent via e-mail.

The only problem with these devices is that the cloud service isn’t optional. It’s the only way to access the data points recorded by the devices. On top of this the basic license is very limited, if you want any real functionality you need to upgrade to a paid subscription per device. And sadly enough the cloud service UI is pretty clunky to work with.

So what can we do?

The goal here is to find a way to redirect or mirror the sensor measurements to a local MQTT broker to record temperatures in influx. We want the most practical way to jailbreak about a dussin devices without having to open them up.

Initial investigation of the network traffic shows that the device uses encrypted MQTT to send measurement data. The device also makes some HTTPS calls to Testo’s servers. Attempts to redirect traffic through mitm-proxy didn’t work, the device refused to accept the self-signed certificate which means we’re probably dealing with certificate-pinning in the client. Port-scans didn’t reveal any open ports that we can probe. So the network side didn’t yield any results, lets see what we can find on the hardware side.

These devices are actually quite simple. They consist mainly of some circuitry for the measurements, a display driver IC, and USB-Serial chip (FTDI FT121) and what looks like a SoC microcontroller with an antenna connection.

Even though the SoC doesn’t have any clear branding or type code on it, searching for the FCC ID did give us some results: https://fccid.io/N8NLSD4WF0459

Here we can find a pretty rudimentary datasheet which includes the pinout but not much more. There are RX and TX pins and connecting a USB-Serial TTL cable outputs debug-information about the loggers operation, such as measurement value, connection to saveris servers, current date-time and NTP status and DHCP status. It doesn’t seem like the debug-interface accepts any input so it doesn’t look like we can get a shell here (if a shell even exists). Since I’m a novice on the hardware related RE I’m not really comfortable probing around on the pins without knowing more about what they are connected to. Time to remove the RF shield.

Removing the shield shows us a TI CC3200R1 SoC MCU (https://www.ti.com/lit/pdf/SWAS032) and a 4MB MX23L3233F serial flash chip (https://www.macronix.com/Lists/Datasheet/Attachments/7426/MX25L3233F,%203V,%2032Mb,%20v1.6.pdf). Let’s see if we can dump the data from flash memory. The memory chip operates on 3.3V and unfortunately Arduino uses 5V even for SPI, but then I remembered that I had an ESP32 which works on 3.3V. And apart from a really crappy soldering job, connecting the pins was pretty straight forward:

Adafruit Huzzah32 < — > Flash memory

3V: — Pin 8 (VCC)
GND: — Pin 4 (GND)
Pin 5 (SCK): — Pin 6 (SCLK)
Pin 18 (MOSI): — Pin 5 (SI)
Pin 19 (MISO): — Pin 2 (SO)
Pin 21 (GPIO): — Pin 1 (CS# active low)

Software used for the dumping was inspired by this library https://github.com/Bob131/spi-dump but rewritten in dotnet core.

And it worked. We now have a 4MB memory dump.

The first thing to do is do a binwalk of the dump to see what we have.

So it appears that we’re not dealing with a Linux system, and looking through the binary indicates that the MCU application is based on FreeRTOS. Reading through the docs for the CC3200 tells us that the MCU uses a proprietary file system that can be read and written to. The actual application should be located in a file named mcuimg.bin.

Since were not working with the MCU, let’s instead see if we can figure out the structure of the file system. The first entry from the binwalk shows us what looks like paths and file names. These are concatenated and there’s no obvious references to offsets or locations. Some of the entries do look like files so let’s see what we can discern from them.

To make a long story short, most of these are indeed html and png and certificate files and so forth. This can easily be verified using hexdump.

$ hexdump testo.bin -C -n 64 -s 659480
000a1018 3c 68 74 6d 6c 20 6c 61 6e 67 3d 22 65 6e 22 3e |<html lang="en">|
000a1028 0a 3c 68 65 61 64 3e 0a 20 20 3c 6d 65 74 61 20 |.<head>. <meta |
000a1038 63 68 61 72 73 65 74 3d 22 75 74 66 2d 38 22 3e |charset="utf-8">|
000a1048 0a 20 20 3c 6d 65 74 61 20 68 74 74 70 2d 65 71 |. <meta http-eq|

The big realization however came when looking at the file in a hex editor.

The file data are all preceded by the bytes 0x00 0x4C 0x53 “.LS”. Searching through the dump file for those bytes and taking note of what kind of content we find we end up with the following table:

000000 UNKNOWN 46 00 4C 53
005000 UNKNOWN F8 BF 03 00 01 00 4C 53
042000 UNKNOWN FC BF 03 20 02 00 4C 53
07F000 UNKNOWN 74 27 00 00 01 00 4C 53
0A1000 HTML 28 23 00 00 01 00 4C 53
0A8000 CSS 7E 24 01 00 01 00 4C 53
0BE000 PNG D5 0A 00 00 01 00 4C 53
0C1000 JAVASCRIPT D3 0A 00 00 01 00 4C 53
0C4000 JQUERY 9C 49 01 00 01 00 4C 53
0DE000 JQUERY VALIDATION PLUGIN
0E7000 BOOTSTRAP
0F4000 HTML
0F7000 HTML
0FA000 PDF
12D000 DER CERT
12F000 DER CERT
131000 BINARY
133000 BINARY
....

It seems like all of the files fall on 4K boundaries with the first 8 bytes containing information about the file. And remember that first entry from the binwalk? Split up based on what a valid file path would look like produces this:

/sys/mcuimg.bin
/sys/servicepack.ucf
/www/index.html
/www/css/main.css
/www/img/logo.png
/www/js/main.js
/www/js/vendor/jquery.min.js
/www/js/vendor/jquery.validate.min.js
/www/js/vendor/bootstrap.min.js
/www/result.html
/www/errorlog.html
wificonf.pdf
/cert/129.der
/cert/130.der
/cert/131.der
/sys/macadd.bin
/sys/mode.cfg
/sys/ap.cfg
/sys/devname.cfg
/sys/ipcfg.ini
/sys/stacfg.ini
/sys/pref.net
/tmp/fcon.frm
/tmp/fcon.ssid

Looks like the file data we found corresponds to the string with file names. What about the file length? Well, since the files all are multiples of 4K they need to be padded, and looking at the hex editor shows that all of our files are preceded by either 0xFF or 0x00. So from a glance we should be able to tell where a file ends based on when the padding starts, at least for text files. So lets take a look at the previous html file.

This data has a length of 91 (0x5B) bytes before the padding starts. And whats the first byte of the file descriptor? Checking the other files confirms this. Each preamble begins with the length of the file with the LSB first, which means we’re working with a little-endian architecture. This should give us enough information to extract the files. Our next area of interest is the /sys/mcuimg.bin file, which should contain the MCU application. But that will have to wait until part 2 of this article.

--

--

Jon Cederqvist

Programming and electronics enthusiast who works in the food manufacturing industry.