Skip to content

Instantly share code, notes, and snippets.

@thatsn0tmysite
Created December 24, 2021 21:01
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save thatsn0tmysite/c20602289d2cdc9ab7484851eefd92ba to your computer and use it in GitHub Desktop.
Save thatsn0tmysite/c20602289d2cdc9ab7484851eefd92ba to your computer and use it in GitHub Desktop.
Tool to unscrew Terramaster encrypted PHP files.
#!/usr/bin/env python3
#
# This script decrypts the PHP files for the web UI of TerraMaster NAS devices.
# The technique used for the encryption is a slighly modified version of the
# "screw-plus" tool version 1.5. The TerraMaster variant differs only by an
# additional XOR scrambling pass on the ciphertext on top of the AES-CBC
# encryption step. The XOR mask used is the ASCII code of the last character
# of the PHP file name (extension excluded).
# Files are decrypted in place.
#
# Author: @bloodyshell:matrix.org
# License
# This is free and unencumbered software released into the public domain.
#
# Anyone is free to copy, modify, publish, use, compile, sell, or
# distribute this software, either in source code form or as a compiled
# binary, for any purpose, commercial or non-commercial, and by any
# means.
#
# In jurisdictions that recognize copyright laws, the author or authors
# of this software dedicate any and all copyright interest in the
# software to the public domain. We make this dedication for the benefit
# of the public at large and to the detriment of our heirs and
# successors. We intend this dedication to be an overt act of
# relinquishment in perpetuity of all present and future rights to this
# software under copyright law.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
# IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
# OTHER DEALINGS IN THE SOFTWARE.
#
# For more information, please refer to <http://unlicense.org/>
from cryptography.hazmat.primitives.ciphers.modes import CBC
from cryptography.hazmat.primitives.ciphers import Cipher
from cryptography.hazmat.primitives.ciphers.algorithms import AES
from hashlib import md5
import os
# root@a:/usr/www# strings /usr/lib/php/php_terra_master.so | grep -A1 /proc/self/fd/ | tail -n1
# GH65Hws2jedf3fl3MeK
TERRAMASTER_PASSWORD = "GH65Hws2jedf3fl3MeK"
def unscrew(infilename, outfilename, password, descramble=False):
# The hex representation of the MD5 hash of the password is used as the
# AES encryption key
key = bytes(md5(bytes(password, "utf-8")).hexdigest(), "ascii")
# The first half of the key is used as the IV for CBC
iv = key[:16]
with open(infilename, "rb") as inf:
body = inf.read()
# The IV is stored in the first 16 bytes of the encrypted file
if body[:16] != iv:
return False
# The next 16 bytes contain the ASCII representation of the decimal value
# of the plaintext length padded with null bytes
plaintext_len = 0
for b in body[16:31]:
if ord('0') <= b <= ord('9'):
plaintext_len = plaintext_len * 10 + b - ord('0')
else:
break
# Descramble the ciphertext if necessary
if descramble:
if descramble == True:
# The TerraMaster scramble mask is the ASCII value of the last
# character of the filename before the ".php" extension:
# Ex.: "index.php" will have a scramble mask of ord('x') (120)
scramble_key = ord(infilename[-5])
else:
scramble_key = descramble
ciphertext = bytes([x ^ scramble_key for x in body[32:]])
else:
ciphertext = body[32:]
# Regular AES256-CBC decryption
decryptor = Cipher(AES(key), CBC(iv)).decryptor()
plaintext = decryptor.update(ciphertext)
plaintext += decryptor.finalize()
with open(outfilename, "wb") as outf:
outf.write(plaintext[:plaintext_len])
return True
def do_unscrew(path, password, descramble):
if not os.path.exists(path):
return
elif os.path.isfile(path):
r = unscrew(path, path, password, descramble)
if r:
print(f"{path} unscrewed successfully")
elif os.path.isdir(path):
for base, dirs, files in os.walk(path):
for fn in files:
fpath = os.path.join(base, fn)
if not os.path.exists(fpath):
continue
r = unscrew(fpath, fpath, password, descramble)
if r:
print(f"{fpath} unscrewed successfully")
if __name__ == "__main__":
import argparse
parser = argparse.ArgumentParser(description="Unscrew PHP source files")
parser.add_argument("files_or_dirs", nargs="+")
parser.add_argument("--password", "-p", default=TERRAMASTER_PASSWORD)
parser.add_argument("--descramble", "-d", default="terramaster")
args = parser.parse_args()
d = args.descramble.lower()
if d in ("terramaster", "yes", "true"):
descramble = True
elif d in ("none", "no", "false"):
descramble = False
else:
descramble = int(descramble)
for fn in args.files_or_dirs:
do_unscrew(fn, args.password, descramble)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment