Reversing TerraMaster NAS Devices Decryption Routine

tl;dr A few days ago, Canberk and Murat made their RCE research on TerraMaster NAS devices public. In this post, I reversed the flow and wrote a program that uses the decryption routine that is used by the device to get the plain text versions of PHP files in its filesystem.

Furkan Göksel
5 min readFeb 18, 2021

When you want to analyze a TerraMaster NAS device, you will realize that the device stores PHP files in an encrypted form after extracting its firmware (I am not rewriting the firmware extraction part because it is clearly explained in the post I linked above. You may want to take a look at that post before continuing to read.). You can see the content of the Enter.php file as an example below.

Encrypted content of Enter.php

After seeing the encrypted files, I started browsing the device’s file system and found the php.ini file under the “etc” directory. In the content of the file, the names of the Shared Object files used and the directory containing these files were written. So I started to examine these files.

A part of the php.ini file

After examining the files, I noticed that the decryption routine started in the pm9screw_ext_fopen function in the php_terra master.so. In short, this function takes a FILE pointer argument pointing to the encrypted file, and it returns a FILE pointer for the plain text version of this file. Let’s look at the decompiled version of the function by using GHidra in more detail.

First of all, the function gets the MD5 hash value of a string whose value of “GH65Hws2jedf3fl3MeK” using the md5 function.

md5 function call

You can see the decompiled version of the md5 function below. Since MD5 hash values ​​can be encoded to 32 characters in total, the function first allocates a space of 32 bytes. After calculating the hash value using MD5Init, MD5Update, MD5Final functions, the function copies the hash value into the memory space it allocated earlier and returns a pointer to that area.

Decompiled version of md5 function

After getting the hash value, it reads the encrypted file pointed by its argument with the help of fread and fstat functions. Then, it writes the content of the file to the reserved memory area.

Copying the content

The one point here is the teg_yek function. This function calls the readlink function and uses its output value for encryption and decryption routines.
However, as we will see in the rest of the post, we can write our decryption code without the need to use the return value of this function.

In the rest of the code, I first saw the first 16 bytes of the encrypted file compared to the first 16 bytes of our hash value using the memcmp function, and I realized that it decrypts the file only if these two values are equal. In fact, if we look at the first part of the Enter.php function I put above, we can see that half of our hash value is written at the beginning of the file. We can think of it as a kind of verifying the encrypted file.

In addition to the 16 bytes appended to the beginning of the file, the following 16 bytes indicate the size of the decrypted file, as shown below.
Therefore, the following while loop is responsible for extracting the size information and shifting the encrypted file content.

Lastly, it gets the decrypted content by calling the screw_aes function, writes the decrypted content to a temp file, and returns a FILE pointer to that file. Therefore, the decryption routine is implemented in the screw_aes function.

Rest of the pm9screw_ext_fopen function

At the beginning of the screw_aes function, it is first calculated how many blocks of 16 bytes the encrypted content consists of because the decryption process is applied to the content in blocks of 16 bytes. Then, the byte values ​​are taken according to the result returned from the teg_yek function.

Beginning of the screw_aes function

And finally, the decryption routine starts. By the way, since the screw_aes function is used for both encryption and decryption, the first parameter of the function determines the operation to be performed by acting as a flag.

As you can see below, decryption takes place in AES CBC 256 bits mode. While half of the hash value is used as the IV value, the whole hash value is given as the key. In the decryption loop, every 16-byte block is sent to the decrypt loop by XORing the byte value received according to the result of the teg_yek function. After adding the decrypted blocks to each other, the decryption routine is finished.

Decryption flow

After observing the whole routine, the only thing missing was the return value from the teg_yek function because we had to xor the blocks with a value of one byte that we get based on this value. Since one byte can only take values ​​between 0 and 255, I was able to find the correct byte using brute force. The logic I used was that if the decrypted form of the first 16 bytes with a certain byte value contains “<?php”, that byte is the byte we were looking for. The whole C code that decrypts these files is below. For the AES functions, I used PolarSSL in my code.

Decrypter code

And the decrypted output is below.

Decrypted content

See you in the next write-up!

--

--