Skip to content

Set Ruckus Unleashed 200.7 / ZoneDirector DPSK passwords / DVLANs

This sample code allows you to extract the DPSK configuration from a ZoneDirector or Unleashed backup, then create a new backup containing your edited configuration.

Newer versions of Unleashed let you directly upload user-defined passwords, so this code isn't useful for modern APs.

DANGER

This code is super-hacky, and is totally unsupported.

But if everything goes wrong, then a factory reset then a restore of the original backup should get you back up and running.

Bash Function (using Python)

sh
rks_dpsk() {
RKS_DPSK_ACTION="$1" RKS_INPUT_PATH="$2" RKS_DPSK_PATH="$3" RKS_OUTPUT_PATH="$4" python3 - <<END

import gzip
import io
import os
import struct
import tarfile
import xml.etree.ElementTree as ET

rks_dpsk_action = os.environ['RKS_DPSK_ACTION']
rks_input_path = os.environ['RKS_INPUT_PATH']
rks_dpsk_path = os.environ['RKS_DPSK_PATH']
rks_output_path = os.environ['RKS_OUTPUT_PATH']

def __tac_decrypt(path: str) -> io.BytesIO:
    (xor_int, xor_flip) = struct.unpack('QQ', b')\x1aB\x05\xbd,\xd6\xf25\xad\xb8\xe0?T\xc58')
    struct_int8 = struct.Struct('Q')
    output_file = io.BytesIO()
    previous_input_int = 0
    with open(path, "rb") as input_file:
        input_data = input_file.read()
        for input_int in struct.unpack_from(str(len(input_data) // 8) + 'Q', input_data):
            output_bytes = struct_int8.pack(previous_input_int ^ xor_int ^ input_int)
            xor_int ^= xor_flip
            previous_input_int = input_int
            output_file.write(output_bytes)
        output_padding = int.from_bytes(output_bytes[-1:], 'big') & 0xf
        output_file.seek(-output_padding, os.SEEK_END)
        output_file.truncate()
        output_file.seek(0)
        return output_file

def __tac_encrypt(input_data: bytes, output_path: str):
    (xor_int, xor_flip) = struct.unpack('QQ', b')\x1aB\x05\xbd,\xd6\xf25\xad\xb8\xe0?T\xc58')
    struct_int8 = struct.Struct('Q')
    with open(output_path, "wb") as output_file:
        input_blocks = len(input_data) // 8
        output_int = 0
        for input_int in struct.unpack_from(str(input_blocks) + "Q", input_data):
            output_int ^= xor_int ^ input_int
            xor_int ^= xor_flip
            output_file.write(struct_int8.pack(output_int))
        input_block = input_data[input_blocks * 8:]
        input_padding = 8 - len(input_block)
        input_int = struct_int8.unpack(input_block.ljust(8, bytes([input_padding | input_padding << 4])))[0]
        output_int ^= xor_int ^ input_int
        output_file.write(struct_int8.pack(output_int))

def __encode_attributes(xml_bytes: bytes, offset: int) -> bytes:
    xml_str = xml_bytes.decode("utf-8")
    root = ET.fromstring(xml_str)
    for element in root.iter():
        for attr in list(element.attrib):
            if attr.startswith('x-'):
                element.attrib[attr] = ''.join(chr(ord(letter) + offset) for letter in element.attrib[attr])
    return ET.tostring(root, encoding="utf-8")

def extract_dpsk(backup_path: str, dpsk_path: str):
    """Extract decrypted DPSK config from backup"""
    with open(dpsk_path, 'wb') as dpsk_file, __tac_decrypt(backup_path) as backup_file:
        with tarfile.open(fileobj = backup_file) as tar:
            extracted = tar.extractfile('etc/airespider/dpsk-list.xml').read()
            decoded = __encode_attributes(extracted, -1)
            dpsk_file.write(decoded)

def replace_dpsk(backup_path: str, dpsk_path: str, output_path: str):
    """Create new backup with encrypted DPSK config"""
    with open(dpsk_path, 'rb') as dpsk_file:
        encoded = __encode_attributes(dpsk_file.read(), 1)
        with __tac_decrypt(backup_path) as input_backup, gzip.open(input_backup, 'rb') as input_tarfile:
            tar_buffer = io.BytesIO(input_tarfile.read())
            tar_buffer.seek(0)
            with tarfile.open(fileobj = tar_buffer, mode='a') as output_tar:
                tar_info = tarfile.TarInfo('etc/airespider/dpsk-list.xml')
                tar_info.size = len(encoded)
                output_tar.addfile(tar_info, io.BytesIO(encoded))
            tar_buffer.seek(0)
            output_tgz = io.BytesIO()
            with gzip.open(output_tgz, 'wb') as output_gzip:
                output_gzip.write(tar_buffer.read())
            output_tgz.seek(0)
            __tac_encrypt(output_tgz.read(), output_path)

def rks_dpsk(action: str,backup_path: str, dpsk_path: str, output_path: str | None = None):
    """Extract or replace backup DPSK config"""
    if action == 'extract':
        extract_dpsk(backup_path, dpsk_path)
    elif action == 'replace':
        replace_dpsk(backup_path, dpsk_path, output_path)

rks_dpsk(rks_dpsk_action, rks_input_path, rks_dpsk_path, rks_output_path)

END
}

Usage

  • Backup your ZoneDirector or Unleashed AP

  • Extract the DPSK config

shell
$ rks_dpsk 'extract' 'test_ul.bak' 'test_dpsk.xml'
  • Edit the extracted DPSK config

  • Create a new backup containing your modifed DPSK config

shell
$ rks_dpsk 'replace' 'test_ul.bak' 'test_dpsk.xml' 'test_ul_modded.bak'
  • Restore the new backup to your ZoneDirector or Unleashed AP

Released under the BSD Zero Clause License.