mirror of
https://github.com/aimacintyre/xdsl-modem-fw-tools.git
synced 2025-12-10 04:54:38 +01:00
With fimrware release 4.4.2.1 for the Vigor 2765, Draytek added an extra "modem" package which listed two firmware versions that I've not seen references to before: - 8.D.1.F.1.7_8.D.1.0.1.1 (VDSL vectoring, ADSL Annex A) - 8.D.1.F.1.7_8.D.1.0.1.2 (VDSL vectoring, ADSL Annex B) These two firmwares have a common 6 byte closing sequence that is completely different from all the other known firmwares, while otherwise having the same structure so enhance the script to support the extra closing sequence. While at it, refactor the script to move the output format strings to constants and make several other minor editorial fixes.
265 lines
9.7 KiB
Python
Executable file
265 lines
9.7 KiB
Python
Executable file
#!/usr/bin/env python3
|
|
|
|
# SPDX-License-Identifier: GPL-2.0-or-later
|
|
|
|
# Copyright (C) 2023 Andrew I MacIntyre <andymac@pcug.org.au>
|
|
|
|
# Extract Lantiq xDSL files from decompressed firmware images
|
|
#
|
|
# Recognised xDSL device files:
|
|
# - Lantiq VRX200 (VR9)
|
|
# - Lantiq VRX300 (VR10)
|
|
# - Lantiq VRX500 (VR11)
|
|
#
|
|
# Known source firmware images
|
|
# - Draytek Vigor 130 (VR9, v3.8.4.1 - requires decompression first)
|
|
# - Draytek Vigor 2760-Delight (VR9, v3.8.9.5 - requires decompression first)
|
|
# - Draytek Vigor 2762 (VR10, v3.9.6.3 - requires decompression first)
|
|
# - Draytek Vigor 165 (VR11, v4.4.1 - requires decompression first)
|
|
# - Draytek Vigor 2765 (VR11, v4.4.1 - requires decompression first)
|
|
#
|
|
# Lantiq VR9, VR10 & VR11 xDSL driver files have been found to have the
|
|
# following structure:
|
|
# - a little endian long containing the total length of the file - 8 bytes
|
|
# - one of two constant byte sequences which can be used to detect the
|
|
# start of the file, starting with the 4th byte of the file:
|
|
# = a 32 byte sequence which appears to be present in all non-G.vector
|
|
# capable files; or
|
|
# = a 78 byte sequence which appears to be present in all G.vector
|
|
# capable files
|
|
# - the bulk of executable code and data comprising the file
|
|
# - one of two constant byte closing sequences:
|
|
# = a 16 byte sequence common to most recognised files
|
|
# = a 6 byte sequence present in some recent files
|
|
#
|
|
# Note:
|
|
#
|
|
# 1) Your use of this script is entirely your (the user's)
|
|
# responsibility and at your own risk.
|
|
#
|
|
# 2) The Draytek images identified above are copyrighted and the embedded
|
|
# components are licensed only for use on the devices for which the
|
|
# original images are intended. Your use of this script gives you the
|
|
# means to extract material to use for your personal purposes but gives
|
|
# you no right to redistribute (i.e. give copies to others or make
|
|
# publicly available) files produced by this script.
|
|
#
|
|
# 3) For the pattern recognition to work the source files must be in
|
|
# decompressed form. For this reason this script won't find xDSL files
|
|
# embedded in SquashFS filesystem images, though it can be used to
|
|
# determine the Lantiq file version of xDSL files extracted from other
|
|
# sources (but see also Martin Blumenstingl's ltq-xdsl-fw-info.sh script
|
|
# mentioned in the Credits for this purpose).
|
|
#
|
|
# 4) The simple pattern recognition approach implemented in this script
|
|
# doesn't make any guarantees as to the integrity of the output file(s).
|
|
# While I have attempted to verify it's output using known good source
|
|
# data there will be risks that what is output may be incomplete or
|
|
# invalid and potentially place any device you use output files in at
|
|
# risk of failure.
|
|
#
|
|
# 5) The above noted Draytek images contain 2-4 complete xDSL files and
|
|
# don't appear to contain any binary difference files. Some xDSL files
|
|
# are present in multiple images for devices with the same chipset.
|
|
#
|
|
# Credits:
|
|
# - Martin Blumenstingl's respository documenting where to find and
|
|
# how to extract various Lantiq VRX200 (VR9) xDSL files provided the
|
|
# example files from which the recognition technique was developed
|
|
# (see https://xdarklight.github.io/lantiq-xdsl-firmware-info/)
|
|
# - the version string extraction is derived from the method used by
|
|
# Martin Blumenstingl's ltq-xdsl-fw-info.sh script
|
|
# (see https://github.com/xdarklight/lantiq-xdsl-firmware-info)
|
|
|
|
import os
|
|
import sys
|
|
import struct
|
|
|
|
|
|
|
|
### constants
|
|
|
|
# common strings
|
|
NULL = b'\x00'
|
|
UNDERSCORE = b'_'
|
|
EMPTY = ''
|
|
|
|
# target strings
|
|
XDSL_START = (('A', b'\x00\x00\x00\x00\x0a\x00\x00\x00\x68\x24\x00\x00\x00\x00\xff\xff' \
|
|
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'),
|
|
('B', b'\x00\x00\x00\x00\x0b\x00\x00\x00\x68\x24\x00\x00\x00\x00\xff\xff' \
|
|
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' \
|
|
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' \
|
|
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' \
|
|
b'\x00\x00\x00\x00\x01\x00\x00\x00\x02\x00\x00\x00\x00\x00'))
|
|
|
|
XDSL_END = (('A', b'\x0b\x46\x42\x3e\x0c\x47\x43\x3f\x0d\x48\x44\x40\x0e\x49\x45\x41'),
|
|
('B', b'\xe0\x78\x60\x02\x04\x00'))
|
|
|
|
XDSL_VERSION = b'\x40\x28\x23\x29'
|
|
|
|
# text format templates
|
|
FMT_VERSION = ' version: %s'
|
|
FMT_NO_VERS = ' version: none identified!'
|
|
FMT_MATCH = '\n[%d] xDSL file found:'
|
|
FMT_OFFSET = ' offset: 0x%x'
|
|
FMT_TYPE_S = ' start mark: type %s'
|
|
FMT_TYPE_E = ' end mark: type %s'
|
|
FMT_LENGTH = ' length: %d bytes'
|
|
FMT_PARTIAL = """[%d] xDSL file with type %s start marker found at 0x%x
|
|
but apparent length extends beyond end of image!"""
|
|
FMT_FEXIST = ' %s: file already exists - skipping!'
|
|
FMT_FN_VER = 'xcpe_%s.bin'
|
|
FMT_FN_NOV = '%s-dsl_%s.%d.bin'
|
|
|
|
# runtime help
|
|
USAGE_STR = """
|
|
usage: %s <source_file> [-l]
|
|
|
|
where:
|
|
-l - (optional) list details of files found but don't extract them
|
|
""" % sys.argv[0]
|
|
|
|
|
|
|
|
### helper routines
|
|
|
|
# write a message on standard output
|
|
def logln(msg):
|
|
sys.stdout.write('%s\n' % msg)
|
|
sys.stdout.flush()
|
|
|
|
|
|
# return version number strings from a Lantiq xDSL file
|
|
# - there should be two, each of 6 characters separated by full stop
|
|
# characters; each string is null terminated
|
|
# - return as a combined string for use in a file name
|
|
# - some older files have the VDSL version prefixed with "V_"
|
|
# which will be preserved
|
|
def xdsl_versions(xdsl_bytes):
|
|
srch_offset = 0
|
|
vers_offs_s = len(XDSL_VERSION)
|
|
versions = []
|
|
for i in (0, 1):
|
|
vmark_idx = xdsl_bytes.find(XDSL_VERSION, srch_offset)
|
|
if vmark_idx < 0:
|
|
break
|
|
srch_offset = vmark_idx + vers_offs_s
|
|
term_idx = xdsl_bytes.find(NULL, srch_offset)
|
|
if term_idx - srch_offset > 13:
|
|
break
|
|
versions.append(xdsl_bytes[srch_offset: term_idx])
|
|
srch_offset = term_idx + 1
|
|
|
|
if len(versions) == 2:
|
|
version_str = versions[0] + UNDERSCORE + versions[1]
|
|
return version_str.decode('ascii')
|
|
else:
|
|
return EMPTY
|
|
|
|
|
|
|
|
### main routine
|
|
|
|
def extract_xdsl_files(src_file, list_only=False):
|
|
|
|
# read the source
|
|
try:
|
|
src_bytes = bytearray(open(src_file, 'rb').read())
|
|
except (OSError, IOError):
|
|
logln('%s: error reading file' % src_file)
|
|
sys.exit(1)
|
|
src_length = len(src_bytes)
|
|
if list_only:
|
|
logln('source file: %s' % src_file)
|
|
|
|
# try and find the starting string
|
|
hit_count = 0
|
|
for xdsl_s_type, xdsl_start_marker in XDSL_START:
|
|
search_offset = 0
|
|
xdsl_start_len = len(xdsl_start_marker)
|
|
while True:
|
|
offset = src_bytes.find(xdsl_start_marker, search_offset)
|
|
if offset > search_offset + 3:
|
|
|
|
# check for the file length which is a little endian long
|
|
# immediately preceding the start marker which is also
|
|
# the first 4 bytes of the file; the value found doesn't
|
|
# account for itself or the first 4 bytes of the start
|
|
# marker (which are all NULLs)
|
|
xdsl_length = struct.unpack_from('<L', src_bytes, offset - 4)[0] + 4
|
|
xdsl_end = offset + xdsl_length
|
|
if xdsl_end <= src_length:
|
|
|
|
# check for an end marker finishing at the specified size
|
|
end_matched = False
|
|
for xdsl_e_type, end_mark in XDSL_END:
|
|
if src_bytes[xdsl_end - len(end_mark): xdsl_end] == end_mark:
|
|
end_matched = True
|
|
break
|
|
|
|
if end_matched:
|
|
hit_count += 1
|
|
hit_s = offset - 4
|
|
hit_l = xdsl_length + 4
|
|
logln(FMT_MATCH % hit_count)
|
|
logln(FMT_OFFSET % hit_s)
|
|
logln(FMT_TYPE_S % xdsl_s_type)
|
|
logln(FMT_TYPE_E % xdsl_e_type)
|
|
logln(FMT_LENGTH % hit_l)
|
|
xdsl_bytes = src_bytes[hit_s: hit_s + hit_l]
|
|
|
|
# extract version string
|
|
version_str = xdsl_versions(xdsl_bytes)
|
|
if version_str:
|
|
logln(FMT_VERSION % version_str)
|
|
xdsl_file = FMT_FN_VER % version_str
|
|
else:
|
|
logln(FMT_NO_VERS)
|
|
xdsl_file = FMT_FN_NOV % (src_file, xdsl_type, hit_count)
|
|
|
|
# save xDSL file bytes to a file if it doesn't already exist
|
|
if not list_only:
|
|
if not os.path.exists(xdsl_file):
|
|
open(xdsl_file, 'wb').write(xdsl_bytes)
|
|
else:
|
|
logln(FMT_FEXIST % xdsl_file)
|
|
|
|
# any others to be found?
|
|
search_offset = xdsl_end
|
|
|
|
else:
|
|
# any others to be found?
|
|
search_offset += offset + xdsl_start_len
|
|
|
|
else:
|
|
# anything returned would be truncated
|
|
logln(FMT_PARTIAL % (hit_count + 1, xdsl_s_type, offset))
|
|
break
|
|
|
|
else:
|
|
break
|
|
|
|
if not hit_count:
|
|
logln('! no xDSL files found')
|
|
|
|
|
|
|
|
### run as script
|
|
|
|
if __name__ == '__main__':
|
|
try:
|
|
list_opt = sys.argv[2]
|
|
if list_opt != '-l':
|
|
raise ValueError('"%s": option not recognised' % list_opt)
|
|
except IndexError:
|
|
list_opt = False
|
|
except ValueError as e:
|
|
logln(e.args[0])
|
|
logln(USAGE_STR)
|
|
sys.exit(1)
|
|
try:
|
|
extract_xdsl_files(sys.argv[1], list_opt)
|
|
except IndexError:
|
|
logln(USAGE_STR)
|
|
sys.exit(1)
|