-
-
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') |
Invalid config - cleared, by using the command line generated for script, thanks.
will check on ONTUSER and update u, thanks
The issue is not related to patching but I'm facing an issue. I tried to enable port forwarding on Nokia G-2425G-A after that I'm facing internet connectivity issue. Can someone please help? If I hard reset the router then it works for few minutes(2-3 minutes) then again internet goes down. On router page it says Authentication failure.
[alert] <129>1 1970-01-01T00:03:40.542117+00:00 AONT syslog 4001 - - cfg_getParam(oid=173,paramName=ISPLogo) failed
[alert] <129>1 1970-01-01T00:03:40.542628+00:00 AONT syslog 4001 - - cfgDal_getParamVal(InternetGatewayDevice.UserInterface.ISPLogo) stl=0 update=0 failed
[alert] <129>1 1970-01-01T00:03:55.038055+00:00 AONT syslog 4001 - - Session is closed now, now reset the WAN
[alert] <129>1 1970-01-01T00:03:55.055777+00:00 AONT syslog 4001 - - wan_reset done
[err] <131>1 1970-01-01T00:04:13.287854+00:00 AONT pppd 7059 - - PPP-Nego:upap_rauthnak() PAP authentication failed
[alert] <129>1 1970-01-01T00:04:14.581323+00:00 AONT syslog 4001 - - [tr069] signal 15 received, terminate now ```
the bin/sh trick was patched after 3FE49362IJHK46 you can still change the password for
ONTUSER
by settingONTUSER
as the username inTelnetSshAccount
section, and whatever password you want, and then enablingONTUSER
to drop into busybox instead of vtysh by settingLimitAccount_ONTUSER
to false if you are getting invalid config when importing, make sure you are using the exact commandline the script tells you to use when you unpacked (important, because it contains the key to encrypt with) otherwise, you can try just naming itconfig.cfg
How to set ont user pls tell in detals
Thanks working for me
@rajkosto Excellent. Worked like a charm.
How did you extract/obtain this ?
PKCSPasswords = ["S23l7nZm47XyMGs6y6oJpN9CR4nbfIZHJ4VRwp7HcdV6o2YvUmeNYFlz08Otwz78"]
Reverse engineered the firmware, it's in an encrypted file in /etc, keys for which differ for each fw ver, but this password is still consistent across firmwares and even newer models, if they change it I'll have to obtain newer firmware and reverse engineer it again
Hi, how to get pppoe password?
I'ts encrypted
<Password dv="" ml="64" rw="RW" t="string" v="N/rTtxnMbWc7FH21eoRiMg==" ealgo="ab"></Password>
real example on Nokia G-240W-G
<Password dv="" ml="64" rw="RW" t="string" v="A5HWYRXLFIRVXNBTNOWXD2G4K63CCN"></Password>
real example on Nokia G-1425G-A
nokia-router-cfg-tool.py -d "N/rTtxnMbWc7FH21eoRiMg=="
works for the first one
the second one has no ealgo so its not encrypted
nokia-router-cfg-tool.py -d "N/rTtxnMbWc7FH21eoRiMg=="
works for the first one the second one has no ealgo so its not encrypted
In fact I took in shodan, an open file from a nokia G-240W-G for me to compare the Pppoe password field, and it generated an encrypted code and apparently the nokia G-1425G-A code is not encrypted, it really is this password ... I ask you because I am wanting to clone the ONT to a ONU and I will only need the pppoe password since the user pppoe is standard for all users.
I don't know if you still support that "project", but i am facing that issue and i have no idea what to do. Model is G-140W-C. I have done that in the past, but seems like it's patched? I am running it in Ubuntu with python3. I will be glad if you can help.
Try "pip install pycryptodome"
You are a lifesaver. Thank you!
Anyone know how to set Fastmile Gateway 3 (3TG00799ABAA) to PPPoE mode using the WAN port? I have managed to do it on the wifi 5 model.
ISP has blocked backup restore and PPPOE options.
SW ver: 1.2204.00.0261
please help there is no option for backup and restore in nokia 2425 g so how to enable telnet
That worked like charm. Thanks so much.
I am trying to decrypt PPPOE username and password, but I get this error, what could it be?
<Username dv="" ml="64" rw="RW" t="string" v="UsuarioPONETB"></Username>
<Password dv="" ml="64" rw="RW" t="string" v="passwordETB" ckn="key3"></Password>
it doesn't look like its encrypted at all. "UsuarioPONETB" it seems in non-eng language (user and ponet) ponet being ISP company or something. paswordETB also seem plain-text.
"/IhBegpXLte2Urey$ZGukYdqZ2bXKTsO.XDn6UL9ZTcdqeEptwdUroRpqXu." is the password in G0425B ONU, how can I decrypt it, previous decryption method of using -d doesn't work.
gives this error:
Traceback (most recent call last): File "/content/drive/MyDrive/Script/nokia-router-cfg-tool.py", line 365, in <module> print('\ndecrypted: ' + RouterCrypto().decrypt(base64.b64decode(sys.argv[2])).decode('UTF-8') + '\n') File "/usr/lib/python3.10/base64.py", line 87, in b64decode return binascii.a2b_base64(s) binascii.Error: Incorrect padding
Please Help.
Am I doing something wrong on my G-2425G-A? I got the following
just to let you know, I only tried to run the above script with no other plugins (eg AES etc), not sure if anything else is needed
also, I also tried binwalk on the config file and it didnt give back anything:
just google the error "no module named crypto"
@rahilarious had an issue with my linux distro and didn't install it at first...Managed to do so after your prompt and eveything worked!
Hey! thanks for the awesome work.
I have made the following changes in the config file, and restored it on the router:
<LimitAccount_ONTUSER rw="RW" t="boolean" v="true"></LimitAccount_ONTUSER>
<TelnetSshAccount. n="TelnetSshAccount" t="staticObject">
<Enable rw="RW" t="boolean" v="False"></Enable>
<UserName ml="64" rw="RW" t="string" v="ONTUSER"></UserName>
<Password ml="64" rw="RW" t="string" v="password"></Password>
</TelnetSshAccount.>
Then when I do telnet 192.168.1.1
in putty (This is my routers IP) and try to login using ONTUSER
, password
. I get incorrect password.
Is there anything else that I need to do be able to login?
I am using G-2425G-A
with 3FE49362JJIJ54
software version.
Oh oh, on it!
Hi , are you able to get full access ( backup and restore) ??..
model_name=Nokia WiFi Beacon 1.1
Username=superadmin
Password=Telc@Admin2#
Hey! thanks for the awesome work. I have made the following changes in the config file, and restored it on the router:
<LimitAccount_ONTUSER rw="RW" t="boolean" v="true"></LimitAccount_ONTUSER>
<TelnetSshAccount. n="TelnetSshAccount" t="staticObject"> <Enable rw="RW" t="boolean" v="False"></Enable> <UserName ml="64" rw="RW" t="string" v="ONTUSER"></UserName> <Password ml="64" rw="RW" t="string" v="password"></Password> </TelnetSshAccount.>
Then when I do
telnet 192.168.1.1
in putty (This is my routers IP) and try to login usingONTUSER
,password
. I get incorrect password. Is there anything else that I need to do be able to login?I am using
G-2425G-A
with3FE49362JJIJ54
software version.
All your booleans are wrong...
you want LimitAccount_ONTUSER to be False so it will exist/be usable
and you want Enable on TelnetSshAccount to be true so SSH works
@rajkosto
ontuser is already enabled and password is set in firmware with md5crypt hash (that i cant decrypt password =$1$gN5WP2Fo$6B.xGzm6kfpDOpYlY89Wp. or $1$ojmCYQtx$ktc5DH0Kvu/jCpuUSAQB0. )
Try this in your config file
i just curious, anybody tried to find where the default data is been stored.
@Nepankaj inside .ubi volume
Hello sir @rajkosto i am getting an File invalid error! while importing config.cfg file to the router please help sir how to get rid of it or just help me to understand what is going on!!!
the bin/sh trick was patched after 3FE49362IJHK46
you can still change the password for
ONTUSER
by settingONTUSER
as the username inTelnetSshAccount
section, and whatever password you want, and then enablingONTUSER
to drop into busybox instead of vtysh by settingLimitAccount_ONTUSER
to falseif you are getting invalid config when importing, make sure you are using the exact commandline the script tells you to use when you unpacked (important, because it contains the key to encrypt with)
otherwise, you can try just naming it
config.cfg