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

UART on the PCB

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 (also available at https://gist.github.com/andyboeh/d295c80a57d62379b926640762f3d5dd), 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”.

  1. #!/usr/bin/env python
  2.  
  3. import os
  4. import sys
  5. import subprocess
  6. import tempfile
  7. import struct
  8. import shutil
  9. import hashlib
  10.  
  11. class SSHEnabler(object):
  12. def __init__(self, filepath, directory):
  13. self.openssl = None
  14. self.filepath = filepath
  15. self.directory = directory
  16. self.check_openssl()
  17.  
  18. def decrypt_file(self):
  19. if not os.path.exists(self.filepath):
  20. print(f"Input file does not exist: {self.filepath}")
  21. return False
  22. 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"])
  23. if ret.returncode != 0:
  24. print("Error decrypting file")
  25. return False
  26. ret = subprocess.run(["tar", "zxf", self.directory + os.path.sep + "decrypted.tar.gz", "-C", self.directory])
  27. if ret.returncode != 0:
  28. print("Error extracting file")
  29. return False
  30. if not os.path.exists(self.directory + os.path.sep + "configure.bin"):
  31. print("Extracted file configure.bin does not exist")
  32. return False
  33. os.remove(self.directory + os.path.sep + "decrypted.tar.gz")
  34. with open(self.directory + os.path.sep + "configure.bin", "rb") as f:
  35. head = f.read(45) # The first 45 bytes contain ALCATEL BACKUP HEAD, the filename of the extracted file and the lenght of it
  36. if head[0:24] != b"ALCATEL BACKUP FILE HEAD":
  37. print("Head does not start with ALCATEL BACKUP FILE HEAD")
  38. return False
  39. length, = struct.unpack(">h", head[43:45])
  40. 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)])
  41. 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"])
  42. 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)])
  43. os.remove(self.directory + os.path.sep + "configure.bin")
  44. os.remove(self.directory + os.path.sep + "configure.md5")
  45. ret = subprocess.run(["tar", "zxf", self.directory + os.path.sep + "backup.tar.gz", "-C", self.directory])
  46. if ret.returncode != 0:
  47. print("Error extracting backup.tar.gz")
  48. return False
  49. if not os.path.exists(self.directory + os.path.sep + "backup_dir" + os.path.sep + "sysconfig.tar.gz"):
  50. print("sysconfig.tar.gz in backup file not found")
  51. return False
  52. os.remove(self.directory + os.path.sep + "backup.tar.gz")
  53. 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"])
  54. if ret.returncode != 0:
  55. print("Error extracting sysconfig.tar.gz")
  56. return False
  57. if not os.path.exists(self.directory + os.path.sep + "backup_dir" + os.path.sep + "etc" + os.path.sep + "rc.local"):
  58. print("rc.local not found")
  59. return False
  60. os.remove(self.directory + os.path.sep + "backup_dir" + os.path.sep + "etc" + os.path.sep + "rc.local")
  61. with open(self.directory + os.path.sep + "backup_dir" + os.path.sep + "etc" + os.path.sep + "rc.local", "w") as f:
  62. f.write("# Put your custom commands here that should be executed once\n")
  63. f.write("# the system init finished. By default this file does nothing.\n")
  64. f.write("\n")
  65. f.write("/etc/init.d/dropbear start\n")
  66. f.write("\n")
  67. f.write("exit 0\n")
  68. os.remove(self.directory + os.path.sep + "backup_dir" + os.path.sep + "sysconfig.tar.gz")
  69. 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"])
  70. if ret.returncode != 0:
  71. print("Error creating sysconfig.tar.gz")
  72. return False
  73. shutil.rmtree(self.directory + os.path.sep + "backup_dir" + os.path.sep + "etc")
  74. ret = subprocess.run(["tar", "zcf", self.directory + os.path.sep + "backup.tar.gz", "-C", self.directory, "backup_dir"])
  75. if ret.returncode != 0:
  76. print("Error creating backup.tar.gz")
  77. return False
  78. shutil.rmtree(self.directory + os.path.sep + "backup_dir")
  79. size = os.path.getsize(self.directory + os.path.sep + "backup.tar.gz")
  80. with open(self.directory + os.path.sep + "configure.bin.head", "ab") as f:
  81. f.write(struct.pack(">h", size))
  82. with open(self.directory + os.path.sep + "configure.bin", "wb") as f:
  83. with open(self.directory + os.path.sep + "configure.bin.head", "rb") as g:
  84. f.write(g.read())
  85. with open(self.directory + os.path.sep + "backup.tar.gz", "rb") as g:
  86. f.write(g.read())
  87. with open(self.directory + os.path.sep + "configure.bin.tail", "rb") as g:
  88. f.write(g.read())
  89. os.remove(self.directory + os.path.sep + "configure.bin.head")
  90. os.remove(self.directory + os.path.sep + "configure.bin.tail")
  91. os.remove(self.directory + os.path.sep + "backup.tar.gz")
  92. md5 = hashlib.md5(open(self.directory + os.path.sep + "configure.bin", "rb").read()).hexdigest()
  93. with open(self.directory + os.path.sep + "configure.md5", "w") as f:
  94. f.write(md5 + "\n")
  95. ret = subprocess.run(["tar", "zcf", self.directory + os.path.sep + "decrypted.tar.gz", "-C", self.directory, "configure.bin", "configure.md5"])
  96. if ret.returncode != 0:
  97. print("Error creating final .tar.gz file")
  98. return False
  99. 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"])
  100.  
  101.  
  102. def which(self, program):
  103. def is_exe(fpath):
  104. return os.path.isfile(fpath) and os.access(fpath, os.X_OK)
  105.  
  106. fpath, fname = os.path.split(program)
  107. if fpath:
  108. if is_exe(program):
  109. return program
  110. else:
  111. for path in os.environ["PATH"].split(os.pathsep):
  112. path = path.strip('"')
  113. exe_file = os.path.join(path, program)
  114. if is_exe(exe_file):
  115. return exe_file
  116.  
  117. return None
  118.  
  119. def check_openssl(self):
  120. self.openssl = self.which("openssl")
  121. if self.openssl:
  122. ret = subprocess.run([self.openssl, "version"], stdout = subprocess.PIPE,
  123. universal_newlines = True)
  124. if ret.returncode == 0:
  125. version = ret.stdout.replace('\n', '')
  126. return version
  127.  
  128. return False
  129.  
  130. if len(sys.argv) < 2:
  131. print("Usage: enable_sshd.py configure.bin")
  132. sys.exit(1)
  133.  
  134. with tempfile.TemporaryDirectory() as tempdir:
  135. enabler = SSHEnabler(sys.argv[1], tempdir)
  136. enabler.decrypt_file()

Once you have SSH access, you can proceed with installin OpenWrt.

This website uses cookies. By using the website, you agree with storing cookies on your computer. Also you acknowledge that you have read and understand our Privacy Policy. If you do not agree leave the website.More information about cookies