moxa-encode-fw.py 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109
  1. #! /usr/bin/env python3
  2. # SPDX-License-Identifier: GPL-2.0-or-later
  3. import argparse
  4. import struct
  5. from binascii import crc32
  6. from dataclasses import dataclass
  7. from itertools import cycle
  8. from typing import List
  9. def xor(data: bytes) -> bytes:
  10. passphrase = "Seek AGREEMENT for the date of completion.\0"
  11. pw = cycle(bytearray(passphrase.encode('ascii')))
  12. return bytearray(b ^ next(pw) for b in data)
  13. def add_fw_header(data: bytes, magic: int, hwid: int, build_id: int,
  14. offsets: List[int]) -> bytes:
  15. unknown_1 = 0x01
  16. unknown_2 = 0x0000
  17. unknown_3 = 0x00000000
  18. unknown_4 = 0x01000000
  19. file_crc = crc(data, 0)
  20. header_struct = struct.Struct('>QIBBHIIIIII' + 'I' * len(offsets))
  21. header_size = header_struct.size
  22. file_size = header_size + len(data)
  23. header_offsets = map(lambda x: x + header_size, offsets)
  24. header_data = header_struct.pack(magic, file_size, unknown_1, len(offsets),
  25. unknown_2, hwid, build_id, unknown_3,
  26. build_id, unknown_4, *header_offsets,
  27. file_crc)
  28. return header_data + data
  29. def add_file_header(data: bytes, filename: str, build_id: int) -> bytes:
  30. unknown1 = 0x01000000
  31. unknown2 = 0x00000000
  32. file_crc = crc(data, 0)
  33. header_struct = struct.Struct(">16sIIIII")
  34. file_size = header_struct.size + len(data)
  35. header_data = header_struct.pack(filename.encode('ascii'), file_size,
  36. unknown1, build_id, unknown2, file_crc)
  37. return header_data + data
  38. def crc(data: bytes, init_val: int) -> int:
  39. return 0xffffffff ^ (crc32(data, 0xffffffff ^ init_val))
  40. @dataclass
  41. class Partition:
  42. name: str
  43. size: int
  44. def main():
  45. partitions = [
  46. Partition(name='kernel', size=2048 * 1024),
  47. Partition(name='root', size=9216 * 1024),
  48. Partition(name='userdisk', size=3076 * 1024),
  49. ]
  50. parser = argparse.ArgumentParser(prog='moxa-encode-fw',
  51. description='MOXA IW firmware encoder')
  52. parser.add_argument('-i', '--input', required=True, type=str, help='Firmware file')
  53. parser.add_argument('-o', '--output', required=True, type=str, help="Output path for encoded firmware file")
  54. parser.add_argument('-m', '--magic', required=True, type=lambda x: int(x,0), help="Magic for firmware header")
  55. parser.add_argument('-d', '--hwid', required=True, type=lambda x: int(x,0), help="Hardware id of device")
  56. parser.add_argument('-b', '--buildid', required=True, type=lambda x: int(x,0), help="Build id of firmware")
  57. args = parser.parse_args()
  58. with open(args.input, 'rb') as input_file:
  59. firmware = bytearray(input_file.read())
  60. offsets = []
  61. pos_input = 0
  62. pos_output = 0
  63. firmware_seg = bytearray()
  64. for partition in partitions:
  65. part_data = firmware[pos_input:pos_input + partition.size]
  66. # just to make sure that no partition is empty
  67. if len(part_data) == 0:
  68. part_data = bytearray([0x00])
  69. header = add_file_header(part_data, partition.name, args.buildid)
  70. firmware_seg += header
  71. offsets.append(pos_output)
  72. pos_input += partition.size
  73. pos_output += len(header)
  74. moxa_firmware = add_fw_header(firmware_seg, args.magic, args.hwid, args.buildid, offsets)
  75. encrypted = xor(moxa_firmware)
  76. with open(args.output, 'wb') as output_file:
  77. output_file.write(encrypted)
  78. if __name__ == '__main__':
  79. main()