From b895ca1b0c1ec2eb2021b52cf64864414df26552 Mon Sep 17 00:00:00 2001 From: Jens Heine Date: Sat, 26 Oct 2024 18:38:13 +0200 Subject: [PATCH 01/14] Tolerate 0x prefixes before hex data --- parse-uboot-dump.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/parse-uboot-dump.py b/parse-uboot-dump.py index c67ec7f..4f91be8 100755 --- a/parse-uboot-dump.py +++ b/parse-uboot-dump.py @@ -10,10 +10,12 @@ for line in i.readlines(): line = line.strip() - if re.match(r'^[0-9a-f]{8}:',line): + if re.match(r'^[0-9a-f]{8}:',line) or re.match(r'^0x[0-9a-f]{8}:',line): line = line.split(":") + print(line) if len(line) == 2: line = line[1] + line = line.replace("0x","") line = line.replace(" ","")[:32] data = bytes.fromhex(line) o.write(data) From 9fc653ba569d57b51371dacc4388876e8b38fe97 Mon Sep 17 00:00:00 2001 From: Jens Heine Date: Sat, 26 Oct 2024 19:01:34 +0200 Subject: [PATCH 02/14] Add some args parsing --- parse-uboot-dump.py | 68 +++++++++++++++++++++++++++++++++------------ 1 file changed, 51 insertions(+), 17 deletions(-) diff --git a/parse-uboot-dump.py b/parse-uboot-dump.py index 4f91be8..f541968 100755 --- a/parse-uboot-dump.py +++ b/parse-uboot-dump.py @@ -1,21 +1,55 @@ #!/usr/bin/env python3 +# +# 20241026 original from matt brown, modified by jens heine +# import re import sys +from optparse import OptionParser -infile = sys.argv[1] -outfile = sys.argv[2] - -i = open(infile,"r") -o = open(outfile,"wb") - -for line in i.readlines(): - line = line.strip() - if re.match(r'^[0-9a-f]{8}:',line) or re.match(r'^0x[0-9a-f]{8}:',line): - line = line.split(":") - print(line) - if len(line) == 2: - line = line[1] - line = line.replace("0x","") - line = line.replace(" ","")[:32] - data = bytes.fromhex(line) - o.write(data) + +def main(): + parser = OptionParser() + parser.add_option("-i", "--infile", dest="infile", + help="read data from FILE", metavar="FILE") + parser.add_option("-o", "--outfile", dest="outfile", + help="write binary data to FILE", metavar="FILE") + parser.add_option("-v", "--verbose", + action="store_true", dest="verbose", default=False, + help="be verbose") + + (options, args) = parser.parse_args() + + infile = None + if options.infile: + infile = options.infile + + outfile = None + if options.outfile: + outfile = options.outfile + + if infile is None or outfile is None: + print("Error: Missing argument, try -h for help") + sys.exit(1) + + _verbose = False + if options.verbose: + _verbose = options.verbose + + i = open(infile, "r") + o = open(outfile, "wb") + + for line in i.readlines(): + line = line.strip() + if re.match(r'^[0-9a-f]{8}:', line) or re.match(r'^0x[0-9a-f]{8}:', line): + line = line.split(":") + print(line) + if len(line) == 2: + line = line[1] + line = line.replace("0x", "") + line = line.replace(" ", "")[:32] + data = bytes.fromhex(line) + o.write(data) + + +if __name__ == '__main__': + main() From c630e3200d7a54b044b1ca3d8c8783f183c84d95 Mon Sep 17 00:00:00 2001 From: Jens Heine Date: Sat, 26 Oct 2024 21:25:27 +0200 Subject: [PATCH 03/14] Aktualisieren von parse-uboot-dump.py be verbose if set --- parse-uboot-dump.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/parse-uboot-dump.py b/parse-uboot-dump.py index f541968..035ca46 100755 --- a/parse-uboot-dump.py +++ b/parse-uboot-dump.py @@ -42,7 +42,8 @@ def main(): line = line.strip() if re.match(r'^[0-9a-f]{8}:', line) or re.match(r'^0x[0-9a-f]{8}:', line): line = line.split(":") - print(line) + if _verbose: + print(line) if len(line) == 2: line = line[1] line = line.replace("0x", "") From cfec6425bc449df27878270dca2b06ff3928422c Mon Sep 17 00:00:00 2001 From: Jens Heine Date: Sat, 26 Oct 2024 23:10:30 +0200 Subject: [PATCH 04/14] regex niver, output nicer --- parse-uboot-dump.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/parse-uboot-dump.py b/parse-uboot-dump.py index 035ca46..7baa1dd 100755 --- a/parse-uboot-dump.py +++ b/parse-uboot-dump.py @@ -38,18 +38,24 @@ def main(): i = open(infile, "r") o = open(outfile, "wb") + data_count = 0 for line in i.readlines(): line = line.strip() - if re.match(r'^[0-9a-f]{8}:', line) or re.match(r'^0x[0-9a-f]{8}:', line): + if re.match(r'^(0x)*[0-9a-f]{8}:', line): + #if re.match(r'^[0-9a-f]{8}:', line) or re.match(r'^0x[0-9a-f]{8}:', line): line = line.split(":") if _verbose: - print(line) + print('in : ' + str(line)) if len(line) == 2: line = line[1] line = line.replace("0x", "") line = line.replace(" ", "")[:32] data = bytes.fromhex(line) + if _verbose: + print('out : ' + line) o.write(data) + data_count += len(data) + print(str(data_count) + ' bytes written to ' + outfile) if __name__ == '__main__': From 908d052541e1ac71bc815c2bb4855e87a694f976 Mon Sep 17 00:00:00 2001 From: Jens Heine Date: Sat, 26 Oct 2024 23:10:30 +0200 Subject: [PATCH 05/14] regex niver, output nicer --- parse-uboot-dump.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/parse-uboot-dump.py b/parse-uboot-dump.py index 035ca46..7baa1dd 100755 --- a/parse-uboot-dump.py +++ b/parse-uboot-dump.py @@ -38,18 +38,24 @@ def main(): i = open(infile, "r") o = open(outfile, "wb") + data_count = 0 for line in i.readlines(): line = line.strip() - if re.match(r'^[0-9a-f]{8}:', line) or re.match(r'^0x[0-9a-f]{8}:', line): + if re.match(r'^(0x)*[0-9a-f]{8}:', line): + #if re.match(r'^[0-9a-f]{8}:', line) or re.match(r'^0x[0-9a-f]{8}:', line): line = line.split(":") if _verbose: - print(line) + print('in : ' + str(line)) if len(line) == 2: line = line[1] line = line.replace("0x", "") line = line.replace(" ", "")[:32] data = bytes.fromhex(line) + if _verbose: + print('out : ' + line) o.write(data) + data_count += len(data) + print(str(data_count) + ' bytes written to ' + outfile) if __name__ == '__main__': From 0cd96047e9ae806a478cae8ab2aef660dfe4cff0 Mon Sep 17 00:00:00 2001 From: Jens Heine Date: Sat, 26 Oct 2024 23:21:15 +0200 Subject: [PATCH 06/14] Remove comment --- parse-uboot-dump.py | 1 - 1 file changed, 1 deletion(-) diff --git a/parse-uboot-dump.py b/parse-uboot-dump.py index 7baa1dd..c352536 100755 --- a/parse-uboot-dump.py +++ b/parse-uboot-dump.py @@ -42,7 +42,6 @@ def main(): for line in i.readlines(): line = line.strip() if re.match(r'^(0x)*[0-9a-f]{8}:', line): - #if re.match(r'^[0-9a-f]{8}:', line) or re.match(r'^0x[0-9a-f]{8}:', line): line = line.split(":") if _verbose: print('in : ' + str(line)) From b0a6fe35cd0d7d11e3949abe499c09451304f0a4 Mon Sep 17 00:00:00 2001 From: Jens Heine Date: Sun, 27 Oct 2024 01:41:52 +0200 Subject: [PATCH 07/14] Make outfile arg optional, ask if outfile already exists. --- parse-uboot-dump.py | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/parse-uboot-dump.py b/parse-uboot-dump.py index c352536..aa7fff2 100755 --- a/parse-uboot-dump.py +++ b/parse-uboot-dump.py @@ -2,15 +2,17 @@ # # 20241026 original from matt brown, modified by jens heine # +import os import re import sys from optparse import OptionParser +from pathlib import Path def main(): parser = OptionParser() parser.add_option("-i", "--infile", dest="infile", - help="read data from FILE", metavar="FILE") + help="read data from FILE (required)", metavar="FILE") parser.add_option("-o", "--outfile", dest="outfile", help="write binary data to FILE", metavar="FILE") parser.add_option("-v", "--verbose", @@ -23,11 +25,11 @@ def main(): if options.infile: infile = options.infile - outfile = None + outfile = "firmware.bin" if options.outfile: outfile = options.outfile - if infile is None or outfile is None: + if infile is None: print("Error: Missing argument, try -h for help") sys.exit(1) @@ -35,6 +37,14 @@ def main(): if options.verbose: _verbose = options.verbose + if Path(outfile).is_file(): + print("Error: outfile already exists.") + answer = input("Overwrite (y/n)? : ") + if answer != 'y': + sys.exit(0) + else: + os.remove(outfile) + i = open(infile, "r") o = open(outfile, "wb") From 328e05934bf85a87ba41fbdd23a2c40fe9efa380 Mon Sep 17 00:00:00 2001 From: Jens Heine Date: Sun, 27 Oct 2024 10:51:31 +0100 Subject: [PATCH 08/14] Extend regex pattern to ignore case of hex value, verbose mode more useful --- parse-uboot-dump.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/parse-uboot-dump.py b/parse-uboot-dump.py index aa7fff2..cce6225 100755 --- a/parse-uboot-dump.py +++ b/parse-uboot-dump.py @@ -40,6 +40,7 @@ def main(): if Path(outfile).is_file(): print("Error: outfile already exists.") answer = input("Overwrite (y/n)? : ") + print() if answer != 'y': sys.exit(0) else: @@ -51,19 +52,21 @@ def main(): data_count = 0 for line in i.readlines(): line = line.strip() - if re.match(r'^(0x)*[0-9a-f]{8}:', line): + if re.match(r'^(0x)*[0-9A-Fa-f]{8}:', line): line = line.split(":") - if _verbose: - print('in : ' + str(line)) + #if _verbose: + # print('in : ' + str(line)) if len(line) == 2: line = line[1] line = line.replace("0x", "") line = line.replace(" ", "")[:32] data = bytes.fromhex(line) if _verbose: - print('out : ' + line) + #print('out : ' + line) + print(str(line)) o.write(data) data_count += len(data) + print() print(str(data_count) + ' bytes written to ' + outfile) From abae76d608409f49a4b46906e6b5e6ce6febe839 Mon Sep 17 00:00:00 2001 From: Jens Heine Date: Mon, 28 Oct 2024 10:52:45 +0100 Subject: [PATCH 09/14] Add overwrite file info nicer --- parse-uboot-dump.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/parse-uboot-dump.py b/parse-uboot-dump.py index cce6225..be78582 100755 --- a/parse-uboot-dump.py +++ b/parse-uboot-dump.py @@ -38,7 +38,7 @@ def main(): _verbose = options.verbose if Path(outfile).is_file(): - print("Error: outfile already exists.") + print("Error: outfile '" + str(outfile) + "' already exists.") answer = input("Overwrite (y/n)? : ") print() if answer != 'y': From b58d35997121908a4093ae904a05abc6043d8332 Mon Sep 17 00:00:00 2001 From: Jens Heine Date: Mon, 28 Oct 2024 14:12:33 +0100 Subject: [PATCH 10/14] little endian test stuff, beta --- parse-uboot-dump.py | 60 ++++++++++++++++++++++++++++++++++++--------- 1 file changed, 48 insertions(+), 12 deletions(-) diff --git a/parse-uboot-dump.py b/parse-uboot-dump.py index be78582..dbd1e27 100755 --- a/parse-uboot-dump.py +++ b/parse-uboot-dump.py @@ -9,12 +9,23 @@ from pathlib import Path +# def decode_2_ascii(b: bytes) -> str: +# result_str = "" +# for _b in list(b): +# if int.from_bytes(_b, "little") >= 32 and int.from_bytes(_b) <= 126: +# result_str = result_str + chr(int.from_bytes(_b)) + + def main(): parser = OptionParser() parser.add_option("-i", "--infile", dest="infile", help="read data from FILE (required)", metavar="FILE") parser.add_option("-o", "--outfile", dest="outfile", - help="write binary data to FILE", metavar="FILE") + help="write binary data to FILE (default: firmware.bin)", metavar="FILE") + parser.add_option("-l", "--little-endian", dest="little_endian", + help="convert data to little endian (default:big endian)", action="store_true") + parser.add_option("-f", "--force", dest="force", + help="force overwrite existing outfile", action="store_true") parser.add_option("-v", "--verbose", action="store_true", dest="verbose", default=False, help="be verbose") @@ -33,13 +44,21 @@ def main(): print("Error: Missing argument, try -h for help") sys.exit(1) + _little_endian = False + if options.little_endian: + _little_endian = options.little_endian + + _force = False + if options.force: + _force = options.force + _verbose = False if options.verbose: _verbose = options.verbose - if Path(outfile).is_file(): + if not _force and Path(outfile).is_file(): print("Error: outfile '" + str(outfile) + "' already exists.") - answer = input("Overwrite (y/n)? : ") + answer = input("Overwrite (y/N)? : ") print() if answer != 'y': sys.exit(0) @@ -54,18 +73,35 @@ def main(): line = line.strip() if re.match(r'^(0x)*[0-9A-Fa-f]{8}:', line): line = line.split(":") - #if _verbose: - # print('in : ' + str(line)) if len(line) == 2: line = line[1] line = line.replace("0x", "") - line = line.replace(" ", "")[:32] - data = bytes.fromhex(line) - if _verbose: - #print('out : ' + line) - print(str(line)) - o.write(data) - data_count += len(data) + memory_address_contents = line.split(" ") + for memory_address_content in memory_address_contents: + if len(memory_address_content) == 0: + continue + if _verbose: + try: + decoded = bytes.fromhex(memory_address_content).decode(encoding="ascii") + except: + decoded = '..' + print('Offset in : ' + str(data_count).rjust(8) + ' ' + str(memory_address_content) + + ' : ' + decoded) + if _little_endian: + memory_address_content = (memory_address_content[2:4] + memory_address_content[0:2] + + memory_address_content[6:8] + memory_address_content[4:6]) + data = bytes.fromhex(memory_address_content) + # if len(data) == 0: + # continue + if _verbose: + try: + decoded = bytes.fromhex(memory_address_content).decode(encoding="ascii") + except: + decoded = '..' + print('Offset out : ' + str(data_count).rjust(8) + ' ' + str(memory_address_content) + + ' : ' + decoded) + o.write(data) + data_count += len(data) print() print(str(data_count) + ' bytes written to ' + outfile) From c78e4c02d47f89606382402625f7aa2e218bded4 Mon Sep 17 00:00:00 2001 From: Jens Heine Date: Tue, 29 Oct 2024 22:25:36 +0100 Subject: [PATCH 11/14] Fixed little endian conversion, verbose output nicer --- parse-uboot-dump.py | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/parse-uboot-dump.py b/parse-uboot-dump.py index dbd1e27..add3f64 100755 --- a/parse-uboot-dump.py +++ b/parse-uboot-dump.py @@ -9,13 +9,6 @@ from pathlib import Path -# def decode_2_ascii(b: bytes) -> str: -# result_str = "" -# for _b in list(b): -# if int.from_bytes(_b, "little") >= 32 and int.from_bytes(_b) <= 126: -# result_str = result_str + chr(int.from_bytes(_b)) - - def main(): parser = OptionParser() parser.add_option("-i", "--infile", dest="infile", @@ -83,22 +76,20 @@ def main(): if _verbose: try: decoded = bytes.fromhex(memory_address_content).decode(encoding="ascii") - except: + except Exception as e: decoded = '..' - print('Offset in : ' + str(data_count).rjust(8) + ' ' + str(memory_address_content) + print('Offset ' + str(data_count).rjust(8) + ' in : ' + str(memory_address_content) + ' : ' + decoded) if _little_endian: - memory_address_content = (memory_address_content[2:4] + memory_address_content[0:2] - + memory_address_content[6:8] + memory_address_content[4:6]) + memory_address_content = (memory_address_content[6:8] + memory_address_content[4:6] + + memory_address_content[2:4] + memory_address_content[0:2]) data = bytes.fromhex(memory_address_content) - # if len(data) == 0: - # continue if _verbose: try: decoded = bytes.fromhex(memory_address_content).decode(encoding="ascii") except: decoded = '..' - print('Offset out : ' + str(data_count).rjust(8) + ' ' + str(memory_address_content) + print('Offset ' + str(data_count).rjust(8) + ' out : ' + str(memory_address_content) + ' : ' + decoded) o.write(data) data_count += len(data) From bc0ebae3524925795fab61bc9048c4839d227beb Mon Sep 17 00:00:00 2001 From: Jens Heine Date: Tue, 29 Oct 2024 22:49:36 +0100 Subject: [PATCH 12/14] Update README.md Updated readme information --- README.md | 53 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/README.md b/README.md index dd974af..e42efd5 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,55 @@ # firmwaretools a set of scripts and tools for various firmware analysis tasks + +## parse-uboot-dump.py + +parse-uboot-dump.py - Use this tool to parse the picocom output of a uboot memory dump and create a firmware.bin file from it. There is also little endian support (needed for fritz 7141 i.e.). I reccomend to create a binary from the parse-uboot-dump.py with: + +``` +pyinstaller parse-uboot-dump.py --onefile --clean +``` +You can find the static binary in the ./dist folder which I use in the following example. + +Let's say we dump some memory data like this from the uboot prompt and we have captured the output : + +``` +picocom -b 38400 -l -r /dev/ttyUSB0 --logfile `date +"%Y%m%d_%H%M%S"`_picocom.log +... + +Eva_AVM >dm 0x90010000 100 + +0x90010000: 0xFEED1281 0x000A7B78 0x94000000 0x075A0201 +0x90010010: 0x000A7B60 0x0020B085 0x00AEE9B1 0x8000005D +0x90010020: 0x00000000 0xFD6F0000 0xB7A3FFFF 0xE9FF4581 +0x90010030: 0x21F14485 0xBB6544E2 0x34D6C2AC 0x088E0F7B +0x90010040: 0x3E009DD3 0x66CD578E 0xEA57F680 0x779805D8 +0x90010050: 0xFBF3D43C 0x2BFFCC97 0x1E6DFDC2 0x491DA9F2 +0x90010060: 0x0387B70D 0xEA8EF4BD 0x078A3680 0xC9BF1778 +0x90010070: 0x7BC23E02 0xA9985FE5 0x037D49E0 0x93238AC1 +0x90010080: 0x3235276B 0x64687BAF 0x6BD2204E 0x91A30B23 + +``` + +``` +parse-uboot-dump -h +Usage: parse-uboot-dump [options] + +Options: + -h, --help show this help message and exit + -i FILE, --infile=FILE + read data from FILE (required) + -o FILE, --outfile=FILE + write binary data to FILE (default: firmware.bin) + -l, --little-endian convert data to little endian (default:big endian) + -f, --force force overwrite existing outfile + -v, --verbose be verbose +``` + +We can now parse the picocom output and convert it to a nice firmware.bin file which has the correct little endian mapping already done: + +``` +parse-uboot-dump -i 20241026_232645_picocom.log -l +``` +Now you can nicely binwalk through the binary "firmware.bin"... + +Happy coding, Jens From 46322ee78aca0cf3ab1547488c8371dca23b7774 Mon Sep 17 00:00:00 2001 From: Jens Heine Date: Tue, 29 Oct 2024 22:55:24 +0100 Subject: [PATCH 13/14] Update README.md --- README.md | 39 ++++++++++++++++++++++++++------------- 1 file changed, 26 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index e42efd5..b129d0f 100644 --- a/README.md +++ b/README.md @@ -15,19 +15,16 @@ Let's say we dump some memory data like this from the uboot prompt and we have c ``` picocom -b 38400 -l -r /dev/ttyUSB0 --logfile `date +"%Y%m%d_%H%M%S"`_picocom.log ... +Eva_AVM >dm 0x90000000 1000 -Eva_AVM >dm 0x90010000 100 - -0x90010000: 0xFEED1281 0x000A7B78 0x94000000 0x075A0201 -0x90010010: 0x000A7B60 0x0020B085 0x00AEE9B1 0x8000005D -0x90010020: 0x00000000 0xFD6F0000 0xB7A3FFFF 0xE9FF4581 -0x90010030: 0x21F14485 0xBB6544E2 0x34D6C2AC 0x088E0F7B -0x90010040: 0x3E009DD3 0x66CD578E 0xEA57F680 0x779805D8 -0x90010050: 0xFBF3D43C 0x2BFFCC97 0x1E6DFDC2 0x491DA9F2 -0x90010060: 0x0387B70D 0xEA8EF4BD 0x078A3680 0xC9BF1778 -0x90010070: 0x7BC23E02 0xA9985FE5 0x037D49E0 0x93238AC1 -0x90010080: 0x3235276B 0x64687BAF 0x6BD2204E 0x91A30B23 - +0x90000000: 0x40809000 0x40809800 0x401A6000 0x241BFFFE +0x90000010: 0x035BD024 0x3C1BFFBF 0x377BFFFF 0x035BD024 +0x90000020: 0x409A6000 0x40806800 0x24080003 0x40888000 +... +0x90000F60: 0x3C08A861 0x35081604 0x24090001 0xAD090000 +0x90000F70: 0x00000000 0x03E00008 0xBC800000 0x24044000 +0x90000F80: 0x24050010 0x3C068000 0x00C43821 0x00E53823 +0x90000F90: 0xBCC00000 0x14C7FFFE 0x00C53021 0x03E00008 ``` ``` @@ -50,6 +47,22 @@ We can now parse the picocom output and convert it to a nice firmware.bin file w ``` parse-uboot-dump -i 20241026_232645_picocom.log -l ``` -Now you can nicely binwalk through the binary "firmware.bin"... + +Now you can nicely binwalk through the binary "firmware.bin" or use xxd to analyze the data... + +``` +xxd firmware.bin +00000000: 0090 8040 0098 8040 0060 1a40 feff 1b24 ...@...@.`.@...$ +00000010: 24d0 5b03 bfff 1b3c ffff 7b37 24d0 5b03 $.[....<..{7$.[. +00000020: 0060 9a40 0068 8040 0300 0824 0080 8840 .`.@.h.@...$...@ +... +00000830: ffff 616e 6e65 7800 4200 7573 625f 6d61 ..annex.B.usb_ma +00000840: 6e75 6661 6374 7572 6572 5f6e 616d 6500 nufacturer_name. +00000850: 4156 4d00 7573 625f 7265 7669 7369 6f6e AVM.usb_revision +00000860: 5f69 6400 3078 3030 3030 0075 7362 5f64 _id.0x0000.usb_d +00000870: 6576 6963 655f 6964 0030 7830 3030 3000 evice_id.0x0000. +00000880: 5365 7269 616c 4e75 6d62 6572 0030 3030 SerialNumber.000 +... +``` Happy coding, Jens From 4d2e63c7111ec52068706e70d3966f1d45df0bc7 Mon Sep 17 00:00:00 2001 From: Jens Heine Date: Tue, 29 Oct 2024 22:56:41 +0100 Subject: [PATCH 14/14] Update README.md --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index b129d0f..77b3898 100644 --- a/README.md +++ b/README.md @@ -62,6 +62,11 @@ xxd firmware.bin 00000860: 5f69 6400 3078 3030 3030 0075 7362 5f64 _id.0x0000.usb_d 00000870: 6576 6963 655f 6964 0030 7830 3030 3000 evice_id.0x0000. 00000880: 5365 7269 616c 4e75 6d62 6572 0030 3030 SerialNumber.000 +00000890: 3030 3030 3030 3030 3030 3030 3000 5072 0000000000000.Pr +000008a0: 6f64 7563 7449 4400 4672 6974 7a5f 426f oductID.Fritz_Bo +000008b0: 785f 3731 3431 0048 5752 6576 6973 696f x_7141.HWRevisio +000008c0: 6e00 3130 3800 7265 7365 7276 6564 0000 n.108.reserved.. +000008d0: 626c 7565 746f 6f74 6800 0075 7362 5f72 bluetooth..usb_r ... ```