diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c18dd8d --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +__pycache__/ diff --git a/README.md b/README.md index bfac4e1..4fd3cda 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,11 @@ GCD Parser ========== -This is a parser for GCD files (firmware updates from a well-known manufacturer). +This is a parser and some tools for working with GCD files (firmware updates from a well-known manufacturer). It's in Python, so feel free to add cool new features and submit pull requests. -Thanks to TurboCCC and kunix for your work. +Thanks to TurboCCC, kunix and Alex W. for your work. Most info from: diff --git a/binsum.py b/binsum.py new file mode 100644 index 0000000..83c81e3 --- /dev/null +++ b/binsum.py @@ -0,0 +1,56 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +# Many thanks to Alex W. who figured this out! + +""" +Calculates the SHA1 of a fw_all.bin until the ending marker. +""" + +from hashlib import sha1 +from struct import unpack +from grmn import devices +import sys + +FILE = sys.argv[1] +BLOCKSIZE = 4096 + +END_MARKER = b"\xff\xff\x5a\xa5\xff\xff\xff\xff" + +first_block = True +past_end = False +trailer = bytes() +trailer_pos = -1 + +csum = sha1() +print("Reading {} ...".format(FILE)) +with open(FILE, "rb") as f: + while True: + block = f.read(BLOCKSIZE) + if first_block: + start = block.find(b"\xff\xff\xff\xff\xf0\xb9\x9d\x38\x0f\x46\x62\xc7") + if start < 0: + print("No Fenix firmware header found.") + else: + start += 4 + hwid = unpack("20}: 0x{:04x} / {:d} ({})".format(fdesc, v, v, get_device(v))) + print(" - {:>20}: 0x{:04x} / {:d} ({})".format(fdesc, v, v, devices.DEVICES.get(v, "Unknown device"))) elif fid == 0x2015: print(" - {:>20}: {} Bytes".format(fdesc, v)) else: @@ -163,7 +151,7 @@ with open(FILE, "rb") as f: elif ttype == 0x02bd and not fw_all_done: hw_id = unpack("H", payload[0x208:0x20a])[0] fw_ver = unpack("H", payload[0x20c:0x20e])[0] - print(" - Device ID: {:04x} / {:d} ({})".format(hw_id, hw_id, get_device(hw_id))) + print(" - Device ID: {:04x} / {:d} ({})".format(hw_id, hw_id, devices.DEVICES.get(hw_id, "Unknown device"))) print(" - Firmware version: {:04x} / {:d}".format(fw_ver, fw_ver)) fw_all_done = True else: diff --git a/gcdstruct.py b/gcdstruct.py new file mode 100644 index 0000000..eff8883 --- /dev/null +++ b/gcdstruct.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +""" +Prints out the structure of the given GCD file. +""" + +from grmn import Gcd, ChkSum +import sys + +FILE = sys.argv[1] + +print("Opening {}".format(FILE)) + +gcd = Gcd(FILE) + +for i, tlv in enumerate(gcd.struct): + print("#{:03d}: {}".format(i, tlv)) diff --git a/gcksum.py b/gcksum.py index e838ed3..e3b1b4a 100644 --- a/gcksum.py +++ b/gcksum.py @@ -1,6 +1,11 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- +""" +Calculates the Byte-checksum of a file and +shows if it's (last Byte) correct or not. +""" + from grmn import ChkSum import sys diff --git a/gcksum_search.py b/gcksum_search.py new file mode 100644 index 0000000..5bbd5ba --- /dev/null +++ b/gcksum_search.py @@ -0,0 +1,33 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +""" +Adds up all bytes from the beginning of a file and shows where +a byte in the file matches the expected checksum at that location. +""" + +from grmn import ChkSum +import sys + +FILE = sys.argv[1] +BLOCKSIZE = 4096 +OFFSET = 0x0 + +csum = ChkSum() +prev_remainder = b"\x00" +print("Reading {} ...".format(FILE)) +with open(FILE, "rb") as f: + while True: + start_pos = f.tell() + block = f.read(BLOCKSIZE) + block = block[OFFSET:] + for i in range(0, len(block)): + c = block[i:i+1] + exp = bytes([csum.get_expected()]) + if c == exp: + print("Found matching 0x{:02x} at 0x{:x} ({:x} + {:d}).".format(c[0], OFFSET + start_pos + i, OFFSET, start_pos + i)) + csum.add(bytes(c)) + if len(block) < BLOCKSIZE: + break + #break + f.close() diff --git a/grmn/chksum.py b/grmn/chksum.py index 8923c70..c0de702 100644 --- a/grmn/chksum.py +++ b/grmn/chksum.py @@ -10,7 +10,7 @@ class ChkSum: self.chksum += sum(bytearray(data)) self.last_byte = data[-1] self.chksum &= 0xff - + def add_from_file(self, filename: str, print_progress: bool = False, blocksize: int=16384): with open(filename, "rb") as f: while True: diff --git a/grmn/devices.py b/grmn/devices.py index 3fa4974..5038f38 100644 --- a/grmn/devices.py +++ b/grmn/devices.py @@ -663,6 +663,7 @@ DEVICES = { 3109: "DriveAssist 51, APAC", 3112: "Edge 520 Plus", 3115: "GPSMAP 64sc SiteSurvey", + 3126: "Instinct", 3134: "Fenix 5S Plus APAC", 3135: "Fenix 5X Plus APAC", 3139: "zumo 396, APAC", diff --git a/grmn/gcd.py b/grmn/gcd.py index 60bf275..48cf601 100644 --- a/grmn/gcd.py +++ b/grmn/gcd.py @@ -10,7 +10,7 @@ import sys GCD_SIG = b"G\x41RM\x49Nd\00" DEFAULT_COPYRIGHT = b"Copyright 1996-2017 by G\x61rm\x69n Ltd. or its subsidiaries." DEFAULT_FIRST_PADDING = 21 -DEFAULT_ALIGN = 0x1000 +DEFAULT_ALIGN = 0x1000 # second padding block pads until 0x1000 TLV_TYPES = { 0x0001: "Checksum rectifier", @@ -28,23 +28,38 @@ TLV_TYPES = { 0xffff: "EOF marker", } +# Typical structure: +# first 0x1000 Bytes: GCD_SIG > 0x0001 > 0x0002 > 0x0003 > 0x0005 > 0x0001 > 0x0002 +# then: 0x0001 > ( 0x0006 > 0x0007 > 0x???? > 0x0001 ... ) > 0xffff + class TLV: - def __init__(self, type_id: int, expected_length: int, value=None): + def __init__(self, type_id: int, expected_length: int, value=None, offset: int=None): self.type_id = type_id + self.offset = offset self.comment = TLV_TYPES.get(type_id, "Type {:04x} / {:d}".format(type_id, type_id)) self.length = expected_length self.value = None + self.is_parsed = False if value is not None: self.value = bytes(value) @staticmethod - def factory(header: bytes): - (type_id, length) = unpack("