How to decrypt NS3 passwords from ZyXEL config file (NDMS V2)
ZyXEL routers, for example, the Keenetic Lite II model, have a System → Configuration tab where you can download the running-config (running configuration) file. This file contains the current settings, including the password for connecting to the Internet service provider and the password for the Wi-Fi network.
Passwords are stored encoded, for example:
authentication wpa-psk ns3 HfNV/Uq8ubal+UXhXERgHRdO authentication password ns3 9IpS3eYkkEN3Vld3CXRZtnBz
The wpa-psk line is the encoded password from the Wi-Fi access point, and the password line is the password for the Internet connection to the ISP.
This encoding method is called differently: NS3 / NDMS V2 / ZyXEL config.
User Felis-Sapiens reverse-engineered the coding method and even wrote a Python 3 script to encode and decode.
And the user VasiliyP made another implementation of decoding in JavaScript, thanks to which users can decode NS3 (NDMS V2) right in the web browser window.
Decoding NS3 (NDMS V2) in Web Browser
Create zyxel_scramble.htm file:
gedit zyxel_scramble.htm
And copy into it
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta name="robots" content="noindex"> <title>ns3 decoder</title> <script> var Base64 = { _keyStr: 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=', decode: function (f_input) { var result = ''; var v1, v2, v3; var v4, v5, v6, v7; var v8 = 0; f_input = f_input.replace(/[^A-Za-z0-9+/=]/g, ''); while (v8 < f_input.length) { v4 = this._keyStr.indexOf(f_input.charAt(v8++)); v5 = this._keyStr.indexOf(f_input.charAt(v8++)); v6 = this._keyStr.indexOf(f_input.charAt(v8++)); v7 = this._keyStr.indexOf(f_input.charAt(v8++)); v1 = v4 << 2 | v5 >> 4; v2 = (v5 & 15) << 4 | v6 >> 2; v3 = (v6 & 3) << 6 | v7; result = result + String.fromCharCode(v1); if (v6 != 64) { result = result + String.fromCharCode(v2) }; if (v7 != 64) { result = result + String.fromCharCode(v3) } }; return result } } function xor1(a0, buffer){ var a1 = 0; var a3 = 0; var v1 = 1; for (;;) { v1++; if (v1 == a0) break; a3 ^= buffer[a1 + v1 - 2]; } var a2 = 1; var t0 = 8; for (;;) { var v0 = 0xFF ^ a3; v0 = v0 << a2; var v1 = a3 >> a2; v1 ^= v0; v1 &= 0xFF; v0 = 0xFF ^ v1; v0 ^= a3; v1 = v1 << a2; v1 ^= v0; a2++; a3 = v1 & 0xFF; if (a2 == t0) break; } var v1 = 1; for (;;) { v1++; a1++; if (v1 == a0) return; buffer[a1 - 1] ^= a3; } } function xor2(a0, buffer, a2, a3){ var a1 = 0; a2 &= 0xFF; a3 &= 0xFF; var v1 = 1; for (;;) { v1 += 1; a1++; if (v1 == a0) return; var v0 = buffer[a1 - 1]; v0 ^= a3; a3 += a2; buffer[a1 - 1] = v0 & 0xFF; a2 = a3 - a2; } } function ns3decode(ns3){ //var buffer = Base64.decode(ns3).split('').map(v => v.charCodeAt(0)); var buffer = Base64.decode(ns3).split(''); for (var i = 0; i < buffer.length; i++) buffer[i] = buffer[i].charCodeAt(0); var s4_size = buffer.length; var s2_size = s4_size - 2; var size24924925 = s4_size * 0x24924925; var hi_size24924925 = size24924925 / 0x100000000; var tmp = s4_size - hi_size24924925; var addr_move_to = (hi_size24924925 + (tmp / 2 )) / 4; var s0 = buffer.splice(addr_move_to, 1); buffer.push(buffer[buffer.length - 1]); var mod = s0 % (s4_size - 1); var addr_move_to = mod; var s2 = buffer.splice(addr_move_to, 1); buffer.push(buffer[buffer.length - 1]); xor1(s4_size, buffer); xor2(s4_size, buffer, s2, s0); //var str = buffer.map(v => String.fromCharCode(v)).join(''); var arr = []; for (var i = 0; i < buffer.length; i++) arr[i] = String.fromCharCode(buffer[i]); var str = arr.join(''); for(var i = 0; i < buffer.length; i++){ if (!buffer[i]) { str = str.substr(0, i); break; } } return str; } function calculate(){ var ns3 = document.getElementById("input").value; if(ns3.length == 0){ document.getElementById("output").hidden = true; return; } var decoded = ns3decode(ns3); var box = document.getElementById("output"); box.value = decoded; box.hidden = false; box.select(); } function init() { var box = document.getElementById("input"); box.ondragenter = function() { this.value = ''; document.getElementById("output").value = ''; }; box.ondrop = function() { calculate(); }; box.onclick = function() { this.select(); }; box.select(); var box = document.getElementById("output"); box.onclick = function() { this.select(); }; } </script> </head> <body onload="init()" > <h1>ns3 decoder</h1> <input type="text" id="input" style="border: solid 1px #EEEEEE" value="BiEjZE6GOPmOmTtDIO0Gtcw+"/> <input type="submit" id="button" onClick="calculate()" value="decode"/> <br> <input type="text" id="output" hidden="true" style="border: solid 1px #EEEEEE" /> </body> </html>
Now just open the file in any web browser.
Enter the data to decode into the window and click the “decode” button.
Encoding and decoding NS3 (NDMS V2) on the command line
Create zyxel_scramble.py file
gedit zyxel_scramble.py
And copy into it:
#!/usr/bin/env python3 # # zyxel_scramble.py # # author: Felis-Sapiens # # Decode password from ZyXEL config (NDMS V2) import sys from base64 import b64decode, b64encode from hashlib import md5 def first_step(password, x1, x2): x1 &= 0xff x2 &= 0xff for i in range(len(password)): password[i] = (password[i] ^ x2) & 0xff x2, x1 = x1 + x2, x2 return password def second_step(password): x = 0 for b in password: x ^= b for i in range(1,8): a = ((x >> i) ^ (~x << i)) & 0xff x = ((a << i) ^ (~a ^ x)) & 0xff for i in range(len(password)): password[i] ^= x return password def scramble_decode(password): if len(password) % 4 != 0: password += '=' * (4 - len(password) % 4) password = list(b64decode(password)) length = len(password) if length not in [0x12, 0x24, 0x48]: print('ERROR: invalid input length') return a2 = length // 7 x2 = password[a2] del password[a2] a1 = x2 % (length - 1) x1 = password[a1] del password[a1] password = second_step(password) password = first_step(password, x1, x2) zero_pos = password.index(0) if zero_pos != -1: length = zero_pos return bytes(password[:length]).decode() def scramble_encode(password): old_length = len(password) length = len(password) + 3 if length < 0x13: length = 0x12 elif length < 0x25: length = 0x24 elif length < 0x49: length = 0x48 else: print('ERROR: password is too long') return password = password.encode() md5_digest = md5(password).digest() password = list(password) if length != old_length: password.append(0) for i in range(old_length+1, length-2): password.append((password[i-old_length-1] * 2 + md5_digest[2 + i%14]) & 0xff) x1 = md5_digest[0] x2 = md5_digest[1] password = first_step(password, x1, x2) password = second_step(password) password.insert(x2 % (length - 1), x1) password.insert(length // 7, x2) return b64encode(bytes(password)) def main(): if len(sys.argv) < 3: print('Usage: zyxel_scramble.py decode|encode password') return if sys.argv[1] == 'decode': password = scramble_decode(sys.argv[2]) elif sys.argv[1] == 'encode': password = scramble_encode(sys.argv[2]) else: print('ERROR: unknown command', sys.argv[1]) return if password is not None: print(password) if __name__ == '__main__': main()
To decode, use a command like:
python3 zyxel_scramble.py decode 'STRING'
For example:
python3 zyxel_scramble.py decode '9IpS3eYkkEN3Vld3CXRZtnBz'
To encode to NS3 (NDMS V2) use a command of the form:
python3 zyxel_scramble.py encode 'STRING'
For example:
python3 zyxel_scramble.py encode 'HackWare'
A string like “b'HfNV/Uq8ubal+UXhXERgHRdO'” will be displayed – you need to take from it what is in single quotes.
Conclusion
Thus, you can recover the password for connecting to the Internet service provider if you accidentally reset your router to factory settings (or you have problems when updating the firmware), but you still have the router's configuration file.
Related articles:
- How to find out to which Wi-Fi networks a computer were connected to and stored Wi-Fi passwords (77.2%)
- How to find all passwords and keys in a large number of files (77.2%)
- How to decrypt stored Windows passwords using mimikatz and DAPA (77.2%)
- How to reset a Windows password (77.2%)
- How to hack HTTP Basic and Digest Authentication (77.2%)
- How to install WiFi-Pumpkin in Linux Mint or Ubuntu (RANDOM - 4.4%)