-
-
Save rajkosto/e2b2455d457cc2be82dbb5c85e22d708 to your computer and use it in GitHub Desktop.
| #!/usr/bin/env python3 | |
| # | |
| # Nokia/Alcatel-Lucent router backup configuration tool | |
| # G2425 support added by rajkosto on 20/11/2022 | |
| # XS-2426G-B support added by rajkosto on 28/02/2023 | |
| # | |
| # 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 | |
| # | |
| # Original author 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) | |
| # Copyright (c) Rajko Stojadinovic (rajkosto) | |
| # | |
| import io | |
| import sys | |
| import zlib | |
| import struct | |
| import base64 | |
| import binascii | |
| import datetime | |
| import hashlib | |
| import secrets | |
| 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) | |
| # verify and remove PKCS#7 padding | |
| padLen = ord(output[-1:]) | |
| if padLen <= 0 or padLen > 16: #cannot be 0 or > blocksize | |
| return None | |
| padBytes = output[-padLen:] | |
| validPad = all(padByte == padLen for padByte in padBytes) | |
| if validPad: | |
| return output[:-padLen] | |
| else: | |
| return None | |
| 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) | |
| class PKCSPassCrypto(RouterCrypto): | |
| def __init__(self, pkcsPass, pkcsSalt): | |
| from Crypto.Cipher import AES | |
| from hashlib import pbkdf2_hmac | |
| keyLen = 32 #AES-256 | |
| ivLen = 16 #AES blocksize | |
| if not isinstance(pkcsPass, bytes): | |
| pkcsPass = pkcsPass.encode() | |
| pkcs = pbkdf2_hmac('sha256', pkcsPass, pkcsSalt, 10, dklen=keyLen+ivLen) | |
| keyBytes = pkcs[:keyLen] | |
| ivBytes = pkcs[keyLen:] | |
| self.cipher = AES.new(keyBytes, AES.MODE_CBC, ivBytes) | |
| #G2425 and newer config pkcs password | |
| PKCSPasswords = ["S23l7nZm47XyMGs6y6oJpN9CR4nbfIZHJ4VRwp7HcdV6o2YvUmeNYFlz08Otwz78"] | |
| # | |
| # 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 the size of the compressed data | |
| data_size = u32(cfg_data[0x04:0x08]) | |
| large_header = False | |
| if data_size == 0: | |
| data_size = u32(cfg_data[0x08:0x0C]) | |
| large_header = True | |
| if data_size == 0: | |
| print('\nERROR: config data size is 0!\n') | |
| exit() | |
| # get fw_magic (unknown, could be fw version/compile time, hw serial number, etc.) | |
| fw_magic = 0 | |
| if large_header: | |
| fw_magic = u32(cfg_data[0x20:0x24]) | |
| else: | |
| fw_magic = u32(cfg_data[0x10:0x14]) | |
| print('-> fw_magic = ' + hex(fw_magic)) | |
| # get the compressed data | |
| compressed = [] | |
| if large_header: | |
| compressed = cfg_data[0x28 : 0x28 + data_size] | |
| else: | |
| compressed = cfg_data[0x14 : 0x14 + data_size] | |
| # get the checksum of the compressed data | |
| checksum = 0 | |
| if large_header: | |
| checksum = u32(cfg_data[0x10:0x14]) | |
| else: | |
| checksum = u32(cfg_data[0x08:0x0C]) | |
| # verify the checksum | |
| if (binascii.crc32(compressed) & 0xFFFFFFFF != checksum): | |
| print('\nCRC32 checksum failed :(\n') | |
| exit() | |
| uncomp_size = 0 | |
| if large_header: | |
| uncomp_size = u32(cfg_data[0x18:0x1C]) | |
| else: | |
| uncomp_size = u32(cfg_data[0x0C:0x10]) | |
| # unpack the config | |
| xml_data = None | |
| try: | |
| xml_data = zlib.decompress(compressed) | |
| pkcsPass = None | |
| except zlib.error: | |
| encData = None | |
| pkcsSalt = None | |
| tryPasswords = [] | |
| if compressed[0] == 0xFF: #pkcs encrypted payload | |
| tryPasswords = PKCSPasswords | |
| with io.BytesIO(compressed) as payload: | |
| payload.seek(1) | |
| pkcsSalt = payload.read(8) | |
| encData = payload.read() | |
| for currPass in tryPasswords: | |
| decryptor = PKCSPassCrypto(currPass,pkcsSalt) | |
| compressed = decryptor.decrypt(encData) | |
| if compressed is None: #pkcs padding verification failed, key is wrong | |
| continue | |
| try: | |
| xml_data = zlib.decompress(compressed) | |
| pkcsPass = currPass | |
| except zlib.error: | |
| pass | |
| if xml_data is None: | |
| if len(tryPasswords): | |
| raise RuntimeError('Exhausted all known encryption passwords') | |
| else: | |
| raise | |
| if len(xml_data) != uncomp_size: | |
| print('WARNING: uncompressed size does not match value in header!') | |
| # output the xml file | |
| out_filename = 'config-%s.xml' % datetime.datetime.now().strftime('%d%m%Y-%H%M%S') | |
| if xml_data[0] != ord('<'): | |
| out_filename = out_filename.replace('.xml','.ini') | |
| of = open(out_filename, 'wb') | |
| of.write(xml_data) | |
| print('\nunpacked as: ' + out_filename) | |
| recompInfo = ('-pb' if big_endian else '-pl') | |
| if large_header: | |
| recompInfo += '64' | |
| if encrypted_cfg or pkcsPass: | |
| recompInfo += 'e' | |
| if pkcsPass: | |
| recompInfo += pkcsPass | |
| print('\n# repack with:') | |
| print('%s %s %s %s\n' % (sys.argv[0], recompInfo, 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' | |
| large_header = False | |
| param_len = 3 | |
| if sys.argv[1][3:5] == '64': | |
| large_header = True | |
| param_len += 2 | |
| elif sys.argv[1][3:5] == '32': | |
| large_header = False | |
| param_len += 2 | |
| encrypted_cfg = False | |
| if len(sys.argv[1]) > param_len and sys.argv[1][param_len] == 'e': | |
| encrypted_cfg = True | |
| param_len += 1 | |
| pkcsPass = None | |
| if encrypted_cfg and len(sys.argv[1]) > param_len: | |
| pkcsPass = sys.argv[1][param_len:] | |
| encrypted_cfg = False | |
| 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) | |
| # pkcs encrypt the inner data if needed | |
| extraDecompLen = 1 #non pkcs encrypted configs have +1 to decomp len | |
| if pkcsPass is not None: | |
| extraDecompLen = 0 | |
| with io.BytesIO() as payload: | |
| payload.write(b'\xFF') | |
| pkcsSalt = secrets.token_bytes(8) | |
| payload.write(pkcsSalt) | |
| cryptor = PKCSPassCrypto(pkcsPass,pkcsSalt) | |
| payload.write(cryptor.encrypt(compressed)) | |
| compressed = payload.getvalue() | |
| ## construct the header ## | |
| # magic | |
| cfg_data = p32(0x123123) | |
| if large_header: | |
| cfg_data += p32(0) | |
| # size of compressed data | |
| cfg_data += p32(len(compressed)) | |
| if large_header: | |
| cfg_data += p32(0) | |
| # crc32 checksum | |
| cfg_data += p32(binascii.crc32(compressed) & 0xFFFFFFFF) | |
| if large_header: | |
| cfg_data += p32(0) | |
| # size of xml file | |
| cfg_data += p32(len(xml_data) + extraDecompLen) | |
| if large_header: | |
| cfg_data += p32(0) | |
| # fw_magic | |
| cfg_data += p32(fw_magic) | |
| if large_header: | |
| cfg_data += p32(0) | |
| # add the compressed xml | |
| cfg_data += compressed | |
| # encrypt overall file 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') |
hey i need help, i just came here, they have removed the backup and restore menu from the gateway, and i am all stuck, they can not even allow their users to use local static ip's i hate airtel now, but i am stuck for some time, is their some workaround
anyone got working on 3FE49568LJLJ63 (1.2402.663) ??? i got this error
-> little endian CPU detected
-> fw_magic = 0xfffffffe
Traceback (most recent call last):
File "C:\Users\Administrador\Desktop\modem\nokia-router-cfg-tool.py", line 137, in
xml_data = zlib.decompress(compressed)
zlib.error: Error -3 while decompressing data: incorrect header check
I got the same above error
-> little endian CPU detected
-> data_size = 0x1a6a1
-> data_size = 108193
-> fw_magic = 0xfffffffe
-> checksum = 0xd6710b5
-> checksum = 224858293
-> uncomp_size = 0xd7c76
-> uncomp_size = 883830
-> trying direct zlib decompression...
-> direct decompression failed, trying pkcs decryption...
trying manual decompression without header...
Manual Decompression failed: Error -3 while decompressing data: invalid stored block lengths
DEBUG: compressed [0] : 0x11
Traceback (most recent call last):
File "/mnt/hms/udara/Documents/Projects/Noia ONT/nokia-router-cfg-tool.py", line 220, in <module>
xml_data = zlib.decompress(compressed)
^^^^^^^^^^^^^^^^^^^^^^^^^^^
zlib.error: Error -3 while decompressing data: incorrect header check
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "/mnt/hms/udara/Documents/Projects/Noia ONT/nokia-router-cfg-tool.py", line 231, in <module>
xml_data = decompress_obj.decompress(compressed)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
zlib.error: Error -3 while decompressing data: invalid stored block lengths
Different encryption or password?.
The data block starts with 0x11 instead of 0xFF.
I would really appreciate it if someone could help.
I will also upload a firmware dump soon!
Manufacturer: ALCL
ProductClass: G-1425G-B
SerialNumber: ALCLFC429D49
HWVer:3FE49937DAAA
SWVer:3FE49568HJLL88
Thank you.
Hello everyone. I am trying to use my Nokia G1425G-B as an Access Point, but I just have access to the userAdmin credentials printed in the back part of equipment. So I am unable do disable DHCP and etc.
I went through a good article about reversing engineering this equipment (portuguese version: https://blog.throot.com.br/artigo-eng-reversa-modem-throot/), but I am stuck at obtaining the .bin file, since the menu for firmware upgrade/download config seems to be hidden for my user under Maintenance. The article I went through said that this menu is indeed hidden. I am able to obtain a 200 answer from URL: http://192.168.1.254/upgrade.cgi, but nothing loads.
Any hint on where should I start from?
nmap shows only ports 80 and 443 are open.
Below you can find some details about my device:
Device Name G-1425G-B
Vendor Nokia
Serial Number XX
Hardware Version 3FE49937ACAA
Boot Version Bootbase1.1-Apr-03-2022--08:23:12
Software Version 3FE49568IJJK06(1.2203.406)
Chipset MTK7528H
Thanks!
Hello everyone. I am trying to use my Nokia G1425G-B as an Access Point, but I just have access to the userAdmin credentials printed in the back part of equipment. So I am unable do disable DHCP and etc.
I went through a good article about reversing engineering this equipment (portuguese version: https://blog.throot.com.br/artigo-eng-reversa-modem-throot/), but I am stuck at obtaining the .bin file, since the menu for firmware upgrade/download config seems to be hidden for my user under Maintenance. The article I went through said that this menu is indeed hidden. I am able to obtain a 200 answer from URL: http://192.168.1.254/upgrade.cgi, but nothing loads.
Any hint on where should I start from?
nmap shows only ports 80 and 443 are open.
Below you can find some details about my device:
Device Name G-1425G-B Vendor Nokia Serial Number XX Hardware Version 3FE49937ACAA Boot Version Bootbase1.1-Apr-03-2022--08:23:12 Software Version 3FE49568IJJK06(1.2203.406) Chipset MTK7528H
Thanks!
any luck?
Hello everyone. I am trying to use my Nokia G1425G-B as an Access Point, but I just have access to the userAdmin credentials printed in the back part of equipment. So I am unable do disable DHCP and etc.
I went through a good article about reversing engineering this equipment (portuguese version: https://blog.throot.com.br/artigo-eng-reversa-modem-throot/), but I am stuck at obtaining the .bin file, since the menu for firmware upgrade/download config seems to be hidden for my user under Maintenance. The article I went through said that this menu is indeed hidden. I am able to obtain a 200 answer from URL: http://192.168.1.254/upgrade.cgi, but nothing loads.
Any hint on where should I start from?
nmap shows only ports 80 and 443 are open.
Below you can find some details about my device:
Device Name G-1425G-B Vendor Nokia Serial Number XX Hardware Version 3FE49937ACAA Boot Version Bootbase1.1-Apr-03-2022--08:23:12 Software Version 3FE49568IJJK06(1.2203.406) Chipset MTK7528H
Thanks!any luck?
nothing!
I was able to find some technical services in Brazil, that claim to unlock those devices, but they need on OLT (Internet Provider Optical Infrastructure) so that it can be done.
Since i'm just a standard home user, it can't be done.
There are some guys on another thread I'm following that would try to connect to this equipment through UART/Serial. But nothing yet.
any luck?
I went as far as following the taA0P52jUXw youtube tutorial and at the last step I have a 'bad decrypt' error
The command I pushed was:
openssl aes-256-cbc -d -pbkdf2 -iter 10 -p -in block_with_salt -out testdec -k "S23l7nZm47XyMGs6y6oJpN9CR4nbfIZHJ4VRwp7HcdV6o2YvUmeNYFlz08Otwz78"
It gaves me the correct salt for my file but throws the following error:
bad decrypt
40E70257E47C0000:error:1C80006B:Provider routines:ossl_cipher_generic_block_final:wrong final block length:../providers/implementations/ciphers/ciphercommon.c:443:
Everything else is correct and I have tried adding padding to the end of the passphrase ("\n") but the result is the same, with a different key and iv, so I'm not sure if maybe the passphrase has changed
My OpenSSL version is 3.0.13 in ubuntu
My ONT info is as follows:
Model: G-1425G-H
Brand: Nokia
Hw ver: 3TN00990ADAA
Boot ver: U-Boot (Nov182024-23:46:33)
Sw ver: 3TN00702JJLK32(1.2403.532)
Chipset: MTK7552
Batch #: Mar 25 2025
did you were able to resolve this?