So there was a rabbithole I had to go through.
Step 1: Download the firmware - https://www.tripleoxygen.net/files/devices/zte/f670l/v1/firmware/stock/?SD
Step 2: Extract with binwalk - it has a jffs2-root of the file system
Step 3: Interact with the file system enough to see the httpd service - js files associated and the httpd ELF file that is the server binary
Step 4: load httpd into IDA - See that it uses libdb.so to manage the database. Load libdb.so into IDA - it uses libharcode.so
Step 5: libhardcode.so uses /etc/hardcode as input to decrypt files in /etc/hardcodefile - See CspHardCodeParamGet - both key and IV are derived from SHA256 of parts of /etc/hardcodefile
Step 6: RE the implementation to decrypt the file /etc/hardcodefile/dataprotocol to reveal
DefAESCBCKey=L04&Product@5A238dc79b15726d5c06
DefAESCBCIV=ZTE%FN$GponNJ025678b02a85c63c706
AESENCRYKey=
userkey=608158c36497b00221db14afb845c9e3
for the files attached in the question
Step 7: With - https://github.com/mkst/zte-config-utility
$ python examples/auto.py --key "L04&Product@5A238dc79b15726d5c06" --iv 'ZTE%FN$GponNJ025678b02a85c63c706' /tmp/f670/user_cfg.bin /tmp/f670/some.xml
and voila you can decrypt the file too
Script to decrypt the harcodefiles
from struct import unpack
import glob
from Crypto.Cipher import AES
from Crypto.Hash import SHA256
def decrypt():
key_file = "./etc/hardcode"
config_paths = glob.glob("./etc/hardcodefile/*")
with open(key_file, "rb") as f:
key_data = f.readline().strip()
offset_bytes = lambda data, offset: bytes(b + offset for b in data)
key = SHA256.new(offset_bytes(key_data[5:21], 3) + key_data[64:]).digest()
iv = SHA256.new(offset_bytes(key_data[7:39], 1)).digest()[:16]
for path in config_paths:
cipher = AES.new(key, AES.MODE_CBC, iv)
try:
with open(path, "rb") as config:
print(f"Decrypting {path}")
header = config.read(8)
magic1, magic2 = unpack(">II", header)
if magic1 != 0x01020304 or magic2 != 0x00000003:
print(f"{path} is not a valid config file, skipping")
continue
config.read(52)
with open(f"{path}.decrypted.txt", "wb") as output:
while True:
chunk_header = config.read(12)
if not chunk_header:
break
plain_len, cipher_len, eof = unpack(">III", chunk_header)
plaintext = cipher.decrypt(config.read(cipher_len))[:plain_len]
output.write(plaintext)
if not eof:
break
except FileNotFoundError:
print(f"File {path} not found, skipping")
if name == "main":
decrypt()