Last active
August 26, 2025 14:48
-
-
Save thedroidgeek/80c379aa43b71015d71da130f85a435a to your computer and use it in GitHub Desktop.
Nokia/Alcatel-Lucent router backup configuration tool
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python3 | |
# | |
# Nokia/Alcatel-Lucent router backup configuration tool | |
# | |
# Features: | |
# - Unpack/repack .cfg files generated from the backup and restore functionnality | |
# in order to modify the full router configuration | |
# - Decrypt/encrypt the passwords/secret values present in the configuration | |
# | |
# Blog post: https://0x41.cf/reversing/2019/10/08/unlocking-nokia-g240wa.html | |
# | |
# Released under the MIT License (http://opensource.org/licenses/MIT) | |
# Copyright (c) Sami Alaoui Kendil (thedroidgeek) | |
# | |
import sys | |
import zlib | |
import struct | |
import base64 | |
import binascii | |
import datetime | |
big_endian = True | |
encrypted_cfg = False | |
def u32(val): | |
return struct.unpack('>I' if big_endian else '<I', val)[0] | |
def p32(val): | |
return struct.pack('>I' if big_endian else '<I', val) | |
def checkendian(cfg): | |
if (cfg[0:4] == b'\x00\x12\x31\x23'): | |
return True | |
elif (cfg[0:4] == b'\x23\x31\x12\x00'): | |
return False | |
else: | |
return None | |
class RouterCrypto: | |
def __init__(self): | |
from Crypto.Cipher import AES | |
# key and IV for AES | |
key = '3D A3 73 D7 DC 82 2E 2A 47 0D EC 37 89 6E 80 D7 2C 49 B3 16 29 DD C9 97 35 4B 84 03 91 77 9E A4' | |
iv = 'D0 E6 DC CD A7 4A 00 DF 76 0F C0 85 11 CB 05 EA' | |
# create AES-128-CBC cipher | |
self.cipher = AES.new(bytes(bytearray.fromhex(key)), AES.MODE_CBC, bytes(bytearray.fromhex(iv))) | |
def decrypt(self, data): | |
output = self.cipher.decrypt(data) | |
# remove PKCS#7 padding | |
return output[:-ord(output[-1:])] | |
def encrypt(self, data): | |
# add PKCS#7 padding for 128-bit AES | |
pad_num = (16 - (len(data) % 16)) | |
data += chr(pad_num).encode() * pad_num | |
return self.cipher.encrypt(data) | |
# | |
# unpack xml from cfg | |
# | |
if (len(sys.argv) == 3 and sys.argv[1] == '-u'): | |
# line feed | |
print('') | |
# read the cfg file | |
cf = open(sys.argv[2], 'rb') | |
cfg_data = cf.read() | |
# check cfg file magic (0x123123) and determine endianness | |
big_endian = checkendian(cfg_data) | |
if big_endian == None: | |
# check if config is encrypted | |
decrypted = None | |
try: | |
# decrypt and check validity | |
decrypted = RouterCrypto().decrypt(cfg_data) | |
big_endian = checkendian(decrypted) | |
except ValueError: | |
pass | |
# if decryption failed, or still invalid, bail out | |
if big_endian == None: | |
print('invalid cfg file/magic :(\n') | |
exit() | |
# set decrypted cfg buffer and encryption flag | |
print('-> encrypted cfg detected') | |
cfg_data = decrypted | |
encrypted_cfg = True | |
# log endianness | |
if big_endian: | |
print('-> big endian CPU detected') | |
else: | |
print('-> little endian CPU detected') | |
# get fw_magic (unknown, could be fw version/compile time, hw serial number, etc.) | |
fw_magic = u32(cfg_data[0x10:0x14]) | |
print('-> fw_magic = ' + hex(fw_magic)) | |
# get the size of the compressed data | |
data_size = u32(cfg_data[4:8]) | |
# get the compressed data | |
compressed = cfg_data[0x14 : 0x14 + data_size] | |
# get the checksum of the compressed data | |
checksum = u32(cfg_data[8:12]) | |
# verify the checksum | |
if (binascii.crc32(compressed) & 0xFFFFFFFF != checksum): | |
print('\nCRC32 checksum failed :(\n') | |
exit() | |
# unpack the config | |
xml_data = zlib.decompress(compressed) | |
# output the xml file | |
out_filename = 'config-%s.xml' % datetime.datetime.now().strftime('%d%m%Y-%H%M%S') | |
of = open(out_filename, 'wb') | |
of.write(xml_data) | |
print('\nunpacked as: ' + out_filename) | |
print('\n# repack with:') | |
print('%s %s %s %s\n' % (sys.argv[0], ('-pb' if big_endian else '-pl') + ('e' if encrypted_cfg else ''), out_filename, hex(fw_magic))) | |
cf.close() | |
of.close() | |
# | |
# generate cfg from xml | |
# | |
elif (len(sys.argv) == 4 and (sys.argv[1][:3] == '-pb' or sys.argv[1][:3] == '-pl')): | |
fw_magic = 0 | |
try: | |
# parse hex string | |
fw_magic = int(sys.argv[3], 16) | |
# 32-bit check | |
p32(fw_magic) | |
except: | |
print('\ninvalid magic value specified (32-bit hex)\n') | |
exit() | |
big_endian = sys.argv[1][:3] == '-pb' | |
encrypted_cfg = sys.argv[1][3:] == 'e' | |
out_filename = 'config-%s.cfg' % datetime.datetime.now().strftime('%d%m%Y-%H%M%S') | |
# read the xml file | |
xf = open(sys.argv[2], 'rb') | |
xml_data = xf.read() | |
xf.close() | |
# compress using default zlib compression | |
compressed = zlib.compress(xml_data) | |
## construct the header ## | |
# magic | |
cfg_data = p32(0x123123) | |
# size of compressed data | |
cfg_data += p32(len(compressed)) | |
# crc32 checksum | |
cfg_data += p32(binascii.crc32(compressed) & 0xFFFFFFFF) | |
# size of xml file | |
cfg_data += p32(len(xml_data) + 1) | |
# fw_magic | |
cfg_data += p32(fw_magic) | |
# add the compressed xml | |
cfg_data += compressed | |
# encrypt if necessary | |
if encrypted_cfg: | |
cfg_data = RouterCrypto().encrypt(cfg_data) | |
# write the cfg file | |
of = open(out_filename, 'wb') | |
of.write(cfg_data) | |
of.close() | |
print('\npacked as: ' + out_filename + '\n') | |
# | |
# decrypt/encrypt secret value | |
# | |
elif (len(sys.argv) == 3 and (sys.argv[1] == '-d' or sys.argv[1] == '-e')): | |
decrypt_mode = sys.argv[1] == '-d' | |
if decrypt_mode: | |
# base64 decode + AES decrypt | |
print('\ndecrypted: ' + RouterCrypto().decrypt(base64.b64decode(sys.argv[2])).decode('UTF-8') + '\n') | |
else: | |
# AES encrypt + base64 encode | |
print('\nencrypted: ' + base64.b64encode(RouterCrypto().encrypt(sys.argv[2].encode())).decode('UTF-8') + '\n') | |
else: | |
print('\n#\n# Nokia/Alcatel-Lucent router backup configuration tool\n#\n') | |
print('# unpack (cfg to xml)\n') | |
print(sys.argv[0] + ' -u config.cfg\n') | |
print('# pack (xml to cfg)\n') | |
print(sys.argv[0] + ' -pb config.xml 0x13377331 # big endian, no encryption, fw_magic = 0x13377331') | |
print(sys.argv[0] + ' -pl config.xml 0x13377331 # little endian, ...') | |
print(sys.argv[0] + ' -pbe config.xml 0x13377331 # big endian, with encryption, ...') | |
print(sys.argv[0] + ' -ple config.xml 0x13377331 # ...\n') | |
print('# decrypt/encrypt secret values within xml (ealgo="ab")\n') | |
print(sys.argv[0] + ' -d OYdLWUVDdKQTPaCIeTqniA==') | |
print(sys.argv[0] + ' -e admin\n') |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Nokia Windows decrypt tool
https://drive.google.com/file/d/1rbXrwxtVOGvXmg4usAOj_VvgvDL91srC/view?usp=sharing
<X_CT-COM_TeleComAccount. n="ctcAccount" t="staticObject">
<DebugDyPass. n="DyPass" t="staticObject">
<X_Authentication. n="Authentication" t="staticObject">
<X_ASB_COM_NormalUserHelpInfo ml="128" rw="RW" t="string" v=""></X_ASB_COM_NormalUserHelpInfo>
<Account. n="Account" t="staticObject">
X_ALU-COM_SwMgnt. n="SwMgnt" t="staticObject">
<X_ALU-COM_Enable rw="RW" t="boolean" v="true"></X_ALU-COM_Enable>
<X_ALU-COM_Polling_URL ml="1024" rw="RW" t="string" v="https://swupd.wifi.nokia.com"></X_ALU-COM_Polling_URL>
<X_ALU-COM_Polling_Time ml="128" rw="RW" t="string" v="0 2 * * *"></X_ALU-COM_Polling_Time>
<X_ALU-COM_Client_Auth_Key ml="1024" rw="RW" t="string" v="slEksSx4015s9j6K3rvN78QeCGiq4q187AAbZmED"></X_ALU-COM_Client_Auth_Key>
if any one could decrypt That would be the password for these users
ONTUSER:$1$2TKiGGwh$6ulkF8D9DsL0RIBK1cgYQ
AdminGPON:$1$M.jBzxHe$jMtIpXxwMTeOg/1JPE6pP0
AdminGPON:$1$J4VA3VCi$y2c4Byu0m5PZ79eRDjHnr0
ONTUSER:$1$.7SbDVPl$lvlevMX.wgvaFyxwN5Ij8/
Proc-Type: 4,ENCRYPTED
DEK-Info: DES-EDE3-CBC,F659D1CBA9992077
aQuaeZEU2UwTDiuvQeFU2xuZG80PO0LKFSDGM5ehGlOyHopOFHCVCyIZ4AE7oYdv
fT2abi6f060gWP/+E6+knsaTdKwgzVCec+75+7/DCIQD9cWRD2XzxKlu8lMTay4a
dY/U1NN1ufjCV2eDnv7sfoimB+zzcMO7/oCKGCKvVHib9cb4vvfY4ujstxkWXz7+
T7Tcqpm6cLHxMjzqnJcgDmP+fd5RYDr3JhESPJB54ll5oBSFoFt1jNAWAgV5rGba
zvQJVMQ06MZk2vqS653Qyi+pMdUvyjqxRVKs/hHRF6ZR4JRET1s908xBZpfos6tm
asTQ6zMhDSY4KBmpPwDhpE6Zy3aJ7f8THbGGQ+kSE9v/ofHq1WI7k3VyxXtCfgJG
9CReT060JQLJn+Yd+so+bims60K162hP/JwFBGW2JAGrd7+BE9RNq50LZ+KjQZe6
rVQiC8fk6JxKxsRbacFhl/DQZyBKuOjpLtErq+K0hO9ZsGjf2YXg7wnTlxqeoy1h
IMypylUpyzFLtlBPmQ962ybG9cyjuL07YdLGLo9EIxb5ugHEOIMFYVZWWod2HmBN
ahZnLjmA9fHC9PW1uSFoXx4OC8CiUHwC67re+RwpTyLWbs+DpxROJUWyIz2QlumP
rQ0PUByfVAYiLO5Er3btCXZSRONYRlopaf6fp54s3TXSLktwYiTY3SFmNhoXAaW5
eMbXUq+oGDZwDF3j+WukNsWS3kMSeXepAu3oevzy3uQJnkM2LVQ5AmfiRRhusCnn
/wYi+Tx2bfM1yK6LNwZWK/IPZN3D6gOgjXttkaseogUGuBnNm3qHHezfmjZOcbZa
79R0NQ2CodE2i+nRzIdwU2q0DECSoUjlm3VqgbsoauJhcUxTfnWAzxb08KIqayx6
6ntjoE5XnzLYegkoiTWK1aZHepudMz2bliZhZ6dotTGP3N4qtnCj++6vRIFbShIB
oRj8kcZ02y/vqgW78nRLJzM9e4p4xgsrbS+NNKCgh5m8WloSSqSA7LSzK/LttOBi
GR9MiTy69AL3u9aR0miGBYxvt/kxogVuuuuUME8lbwWhyM3vYddWsNXQ3hAniS4I
d+XuKTQiB0YjVCuF4DnYq6lRlmCuuYGEolktQ0E3+Ke6F3fpgtlZSc24SmDTUjGA
84w/fOG5UmVEBqfTDyYXS3Jp6sllJJH93vYb52rWXlOPXbx4abQ31EP1BgNr0ciR
xhf+ngxC5gG2S8tCDAjmL6k4FJzFIGevYi8OYKZsJfw+PdYghTA4I8VICDEHPm0m
g/n49qgtLuwHx9MXUkhYqY4V2QDCmVji6AMAloEAqc2PZFzyfb9Hb9R8Mb2879e0
v063+0vSFW3oBE+RJhad1nF0rNWtKsLFBtbBz2HzqBJRKTeJ0oUukwzYnVCc5Ps6
L+oPNPh1w61fl3tgd5eW172nWYr1cHW+GVT6xyqWDcPAPxjFmk1/Gin9dVu/3W8q
iONdPUWXcubLb53MnGcT77z2JLRpeeDXybqyjb8wCxo8+m1iHPDGz2i0fxB2vH2o
l2+jKYvevb5gZztfvASuMBKJQY9gEURwdJv5HjBb0E4pcz1o9MleCA==
-----END RSA PR-