This is an old revision of the document!
Linux/OpenWrt on the Alcatel HH40V
I needed a cheap LTE router for some quick tests and bought a used Alcatel HH40V for cheap. It's based on a QCA9531 SoC with 128MiB RAM and 16MiB flash and it's running an old, customized version of OpenWrt on this SoC. The LTE module is based on a Qualcomm module and is running its own, customized Android stack.
Getting access to the LTE module is documented here: https://github.com/froonix/HH40V/wiki
However, getting access to the SoC is neither documented nor easily possible - up to now.
Getting UART access
There are two pads for UART on the PCB - orange is Rx, green is Tx and blue is GND (that's not a pad):
The serial console is then available with 115200 baud and no password.
Getting SSH access
Getting SSH access is required in order to install OpenWrt without having to solder to the pads. I poked around in the filesystem and on the Wiki and found out that it is possible to download a configuration backup - unfortunately, it is encrypted. Someone in the Wiki mentioned it's based on OpenSSL, so I searched for OpenSSL in the file core_app
, one of the biggest applications of the OEM firmware. It contained the hardcoded password for the for the backup file - yeah!
Long story short, with the information in this core_app
, I could come up with a Python script that modifies the backup in a way that it enabled the Dropbear SSH server upon startup.
This is the script, run it as enable_sshd.py configure.bin
. Then, upload the modified backup file (configure.bin_mod.bin
) to your router, reboot and you have SSH access. Credentials: User “root”, Password “oelinux123”.
#!/usr/bin/env python import os import sys import subprocess import tempfile import struct import shutil import hashlib class SSHEnabler(object): def __init__(self, filepath, directory): self.openssl = None self.filepath = filepath self.directory = directory self.check_openssl() def decrypt_file(self): if not os.path.exists(self.filepath): print(f"Input file does not exist: {self.filepath}") return False ret = subprocess.run([self.openssl, "aes-128-cbc", "-d", "-k", "oacgnahsnauyilil", "-base64", "-in", self.filepath, "-out", self.directory + os.path.sep + "decrypted.tar.gz", "-md", "md5"]) if ret.returncode != 0: print("Error decrypting file") return False ret = subprocess.run(["tar", "zxf", self.directory + os.path.sep + "decrypted.tar.gz", "-C", self.directory]) if ret.returncode != 0: print("Error extracting file") return False if not os.path.exists(self.directory + os.path.sep + "configure.bin"): print("Extracted file configure.bin does not exist") return False os.remove(self.directory + os.path.sep + "decrypted.tar.gz") with open(self.directory + os.path.sep + "configure.bin", "rb") as f: head = f.read(45) # The first 45 bytes contain ALCATEL BACKUP HEAD, the filename of the extracted file and the lenght of it if head[0:24] != b"ALCATEL BACKUP FILE HEAD": print("Head does not start with ALCATEL BACKUP FILE HEAD") return False length, = struct.unpack(">h", head[43:45]) ret = subprocess.call(["dd", "if="+self.directory + os.path.sep + "configure.bin", "of="+self.directory + os.path.sep + "backup.tar.gz", "bs=1", "skip=45", "count=" + str(length)]) ret = subprocess.call(["dd", "if="+self.directory + os.path.sep + "configure.bin", "of="+self.directory + os.path.sep + "configure.bin.head", "bs=1", "count=43"]) ret = subprocess.call(["dd", "if="+self.directory + os.path.sep + "configure.bin", "of="+self.directory + os.path.sep + "configure.bin.tail", "bs=1", "skip=" + str(45 + length)]) os.remove(self.directory + os.path.sep + "configure.bin") os.remove(self.directory + os.path.sep + "configure.md5") ret = subprocess.run(["tar", "zxf", self.directory + os.path.sep + "backup.tar.gz", "-C", self.directory]) if ret.returncode != 0: print("Error extracting backup.tar.gz") return False if not os.path.exists(self.directory + os.path.sep + "backup_dir" + os.path.sep + "sysconfig.tar.gz"): print("sysconfig.tar.gz in backup file not found") return False os.remove(self.directory + os.path.sep + "backup.tar.gz") ret = subprocess.run(["tar", "zxf", self.directory + os.path.sep + "backup_dir" + os.path.sep + "sysconfig.tar.gz", "-C", self.directory + os.path.sep + "backup_dir"]) if ret.returncode != 0: print("Error extracting sysconfig.tar.gz") return False if not os.path.exists(self.directory + os.path.sep + "backup_dir" + os.path.sep + "etc" + os.path.sep + "rc.local"): print("rc.local not found") return False os.remove(self.directory + os.path.sep + "backup_dir" + os.path.sep + "etc" + os.path.sep + "rc.local") with open(self.directory + os.path.sep + "backup_dir" + os.path.sep + "etc" + os.path.sep + "rc.local", "w") as f: f.write("# Put your custom commands here that should be executed once\n") f.write("# the system init finished. By default this file does nothing.\n") f.write("\n") f.write("/etc/init.d/dropbear start\n") f.write("\n") f.write("exit 0\n") os.remove(self.directory + os.path.sep + "backup_dir" + os.path.sep + "sysconfig.tar.gz") ret = subprocess.run(["tar", "zcf", self.directory + os.path.sep + "backup_dir" + os.path.sep + "sysconfig.tar.gz", "-C", self.directory + os.path.sep + "backup_dir", "etc"]) if ret.returncode != 0: print("Error creating sysconfig.tar.gz") return False shutil.rmtree(self.directory + os.path.sep + "backup_dir" + os.path.sep + "etc") ret = subprocess.run(["tar", "zcf", self.directory + os.path.sep + "backup.tar.gz", "-C", self.directory, "backup_dir"]) if ret.returncode != 0: print("Error creating backup.tar.gz") return False shutil.rmtree(self.directory + os.path.sep + "backup_dir") size = os.path.getsize(self.directory + os.path.sep + "backup.tar.gz") with open(self.directory + os.path.sep + "configure.bin.head", "ab") as f: f.write(struct.pack(">h", size)) with open(self.directory + os.path.sep + "configure.bin", "wb") as f: with open(self.directory + os.path.sep + "configure.bin.head", "rb") as g: f.write(g.read()) with open(self.directory + os.path.sep + "backup.tar.gz", "rb") as g: f.write(g.read()) with open(self.directory + os.path.sep + "configure.bin.tail", "rb") as g: f.write(g.read()) os.remove(self.directory + os.path.sep + "configure.bin.head") os.remove(self.directory + os.path.sep + "configure.bin.tail") os.remove(self.directory + os.path.sep + "backup.tar.gz") md5 = hashlib.md5(open(self.directory + os.path.sep + "configure.bin", "rb").read()).hexdigest() with open(self.directory + os.path.sep + "configure.md5", "w") as f: f.write(md5 + "\n") ret = subprocess.run(["tar", "zcf", self.directory + os.path.sep + "decrypted.tar.gz", "-C", self.directory, "configure.bin", "configure.md5"]) if ret.returncode != 0: print("Error creating final .tar.gz file") return False ret = subprocess.run([self.openssl, "aes-128-cbc", "-e", "-k", "oacgnahsnauyilil", "-base64", "-in", self.directory + os.path.sep + "decrypted.tar.gz", "-out", self.filepath + "_mod.bin", "-md", "md5"]) def which(self, program): def is_exe(fpath): return os.path.isfile(fpath) and os.access(fpath, os.X_OK) fpath, fname = os.path.split(program) if fpath: if is_exe(program): return program else: for path in os.environ["PATH"].split(os.pathsep): path = path.strip('"') exe_file = os.path.join(path, program) if is_exe(exe_file): return exe_file return None def check_openssl(self): self.openssl = self.which("openssl") if self.openssl: ret = subprocess.run([self.openssl, "version"], stdout = subprocess.PIPE, universal_newlines = True) if ret.returncode == 0: version = ret.stdout.replace('\n', '') return version return False if len(sys.argv) < 2: print("Usage: enable_sshd.py configure.bin") sys.exit(1) with tempfile.TemporaryDirectory() as tempdir: enabler = SSHEnabler(sys.argv[1], tempdir) enabler.decrypt_file()
Once you have SSH access, you can proceed with installin OpenWrt.