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()