What is this?
The Module is basically a Cortex-M3 MCU (GigaDevice GD32F150G8) which measures voltage and current with a INA226. It features two Type-C ports (male and female), a micro-USB port for serial communication, a bluetooth module, and an OLED display.
The Firmware
The MCU runs a boot-loader which enables you to flash new firmware as described here.
When I take a look at the firmware binaries provided by RDTech, it seems that they are manipulated in a way (encrypted, compressed, ...) because comparing it with the Instruction-Set of armv7-m does not get me valid instructions.
So I have to take a deep dive into the binaries...
All firmware binaries are exactly 51840 bytes in size, and as the flash size of the MCU is 64 kB, I assume that there are about 12 kB for e.g. boot-loader. But I also assume that these binaries are bigger than the asm-code they hold because the size doesn't change with different firmware versions.

Figure 1
Looking at this entropy-graph I would guess that there is no compression in the binary. Also it seems that there are some blocks with no information at all (Entropy is 0.5). This graph looks similar for every file.
-$ ent TC66_V1.17.bin Entropy = 7.850424 bits per byte. Optimum compression would reduce the size of this 51840 byte file by 1 percent. Chi square distribution for 51840 samples is 16149.98, and randomly would exceed this value less than 0.01 percent of the times. Arithmetic mean value of data bytes is 131.9124 (127.5 = random). Monte Carlo value for Pi is 3.055092593 (error 2.75 percent). Serial correlation coefficient is -0.004224 (totally uncorrelated = 0.0).
So I'm comparing the sections with low entropy:
-$ binwalk -W TC66_V1.15.bin TC66_V1.17.bin TC66_V1.17.bin

Figure 2
Figure 2 shows the comparison of three firmware files for the second low-entropy peak as previously shown in the entropy graph in Figure 1 (red: different, blue: two files the same, green: all files the same). The firmware seems to have "empty" blocks with repeated information. You can also see that the firmware is growing into that block from version 1.15 (left) to 1.16 (middle) and 1.17 (right). This also makes sense for empty blocks.
Note: "Empty" block does not necessarily mean that all bytes in this block are zero but that there is no information stored in these blocks. If they are not used it doesn't matter whether they are zero.
No lets take a look at the repetition of the sequence [33 94 D6 ... 2B F8 A7]. Its length is exactly 16 bytes. So whatever has happened with the firmware seems to be done in blocks of 16 bytes. A random sequence of the code (shown below) is showing that a change always affects a block of 16 bytes.
The easiest solution would be to just do an XOR on all blocks with the sequence, as this would result in zeros in all empty blocks. However doing this does not generate me a readable firmware file as it does not contain readable ascii strings.

Figure 3
Note: In Figure 2 it seems that there are single bytes matching between two of the files, this is simply caused by statistics. This would also occur in two randomly generated files as a byte has only 255 possible values.
In Figure 3 it is visible that changes always affect a whole block of 16 bytes this speaks against a simple XOR operation. The green areas show that other blocks of data can stay the same so changes do not affect more than 16 bytes. But with Figure 2 in mind we know that the operation is the same for every block of data without a rolling code or something like that.
Bjarne
Florian Baumgartner
Jesse
TinLethax