travis: Add python script for damaging MCUboot image
Change-Id: Ic975b2fa937baafe57c8c492ef889ffb292f691e
Signed-off-by: Mate Toth-Pal <mate.toth-pal@arm.com>
diff --git a/ci/fih_test_docker/damage_image.py b/ci/fih_test_docker/damage_image.py
new file mode 100644
index 0000000..afa25b8
--- /dev/null
+++ b/ci/fih_test_docker/damage_image.py
@@ -0,0 +1,211 @@
+# Copyright (c) 2020 Arm Limited
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import argparse
+import logging
+import struct
+import sys
+
+from imgtool.image import (IMAGE_HEADER_SIZE, IMAGE_MAGIC,
+ TLV_INFO_MAGIC, TLV_PROT_INFO_MAGIC, TLV_VALUES)
+from shutil import copyfile
+
+
+def get_tlv_type_string(tlv_type):
+ tlvs = {v: f"IMAGE_TLV_{k}" for k, v in TLV_VALUES.items()}
+ return tlvs.get(tlv_type, "UNKNOWN({:d})".format(tlv_type))
+
+
+class ImageHeader:
+
+ def __init__(self):
+ self.ih_magic = 0
+ self.ih_load_addr = 0
+ self.ih_hdr_size = 0
+ self.ih_protect_tlv_size = 0
+ self.ih_img_size = 0
+ self.ih_flags = 0
+ self.iv_major = 0
+ self.iv_minor = 0
+ self.iv_revision = 0
+ self.iv_build_num = 0
+ self._pad1 = 0
+
+ @staticmethod
+ def read_from_binary(in_file):
+ h = ImageHeader()
+
+ (h.ih_magic, h.ih_load_addr, h.ih_hdr_size, h.ih_protect_tlv_size, h.ih_img_size,
+ h.ih_flags, h.iv_major, h.iv_minor, h.iv_revision, h.iv_build_num, h._pad1
+ ) = struct.unpack('<IIHHIIBBHII', in_file.read(IMAGE_HEADER_SIZE))
+ return h
+
+ def __repr__(self):
+ return "\n".join([
+ " ih_magic = 0x{:X}".format(self.ih_magic),
+ " ih_load_addr = " + str(self.ih_load_addr),
+ " ih_hdr_size = " + str(self.ih_hdr_size),
+ " ih_protect_tlv_size = " + str(self.ih_protect_tlv_size),
+ " ih_img_size = " + str(self.ih_img_size),
+ " ih_flags = " + str(self.ih_flags),
+ " iv_major = " + str(self.iv_major),
+ " iv_minor = " + str(self.iv_minor),
+ " iv_revision = " + str(self.iv_revision),
+ " iv_build_num = " + str(self.iv_build_num),
+ " _pad1 = " + str(self._pad1)])
+
+
+class ImageTLVInfo:
+ def __init__(self):
+ self.format_string = '<HH'
+
+ self.it_magic = 0
+ self.it_tlv_tot = 0
+
+ @staticmethod
+ def read_from_binary(in_file):
+ i = ImageTLVInfo()
+
+ (i.it_magic, i.it_tlv_tot) = struct.unpack('<HH', in_file.read(4))
+ return i
+
+ def __repr__(self):
+ return "\n".join([
+ " it_magic = 0x{:X}".format(self.it_magic),
+ " it_tlv_tot = " + str(self.it_tlv_tot)])
+
+ def __len__(self):
+ return struct.calcsize(self.format_string)
+
+
+class ImageTLV:
+ def __init__(self):
+ self.it_value = 0
+ self.it_type = 0
+ self.it_len = 0
+
+ @staticmethod
+ def read_from_binary(in_file):
+ tlv = ImageTLV()
+ (tlv.it_type, _, tlv.it_len) = struct.unpack('<BBH', in_file.read(4))
+ (tlv.it_value) = struct.unpack('<{:d}s'.format(tlv.it_len), in_file.read(tlv.it_len))
+ return tlv
+
+ def __len__(self):
+ round_to = 1
+ return int((4 + self.it_len + round_to - 1) // round_to) * round_to
+
+
+def get_arguments():
+ parser = argparse.ArgumentParser(description='Corrupt an MCUBoot image')
+ parser.add_argument("-i", "--in-file", required=True, help='The input image to be corrupted (read only)')
+ parser.add_argument("-o", "--out-file", required=True, help='the corrupted image')
+ parser.add_argument('-a', '--image-hash',
+ default=False,
+ action="store_true",
+ required=False,
+ help='Corrupt the image hash')
+ parser.add_argument('-s', '--signature',
+ default=False,
+ action="store_true",
+ required=False,
+ help='Corrupt the signature of the image')
+ return parser.parse_args()
+
+
+def damage_tlv(image_offset, tlv_off, tlv, out_file_content):
+ damage_offset = image_offset + tlv_off + 4
+ logging.info(" Damaging TLV at offset 0x{:X}...".format(damage_offset))
+ value = bytearray(tlv.it_value[0])
+ value[0] = (value[0] + 1) % 256
+ out_file_content[damage_offset] = value[0]
+
+
+def is_valid_signature(tlv):
+ return tlv.it_type == TLV_VALUES['RSA2048'] or tlv.it_type == TLV_VALUES['RSA3072']
+
+
+def damage_image(args, in_file, out_file_content, image_offset):
+ in_file.seek(image_offset, 0)
+
+ # Find the Image header
+ image_header = ImageHeader.read_from_binary(in_file)
+ if image_header.ih_magic != IMAGE_MAGIC:
+ raise Exception("Invalid magic in image_header: 0x{:X} instead of 0x{:X}".format(image_header.ih_magic, IMAGE_MAGIC))
+
+ # Find the TLV header
+ tlv_info_offset = image_header.ih_hdr_size + image_header.ih_img_size
+ in_file.seek(image_offset + tlv_info_offset, 0)
+
+ tlv_info = ImageTLVInfo.read_from_binary(in_file)
+ if tlv_info.it_magic == TLV_PROT_INFO_MAGIC:
+ logging.debug("Protected TLV found at offset 0x{:X}".format(tlv_info_offset))
+ if image_header.ih_protect_tlv_size != tlv_info.it_tlv_tot:
+ raise Exception("Invalid prot TLV len ({:d} vs. {:d})".format(image_header.ih_protect_tlv_size, tlv_info.it_tlv_tot))
+
+ # seek to unprotected TLV
+ tlv_info_offset += tlv_info.it_tlv_tot
+ in_file.seek(image_offset + tlv_info_offset)
+ tlv_info = ImageTLVInfo.read_from_binary(in_file)
+
+ else:
+ if image_header.ih_protect_tlv_size != 0:
+ raise Exception("No prot TLV was found.")
+
+ logging.debug("Unprotected TLV found at offset 0x{:X}".format(tlv_info_offset))
+ if tlv_info.it_magic != TLV_INFO_MAGIC:
+ raise Exception("Invalid magic in tlv info: 0x{:X} instead of 0x{:X}".format(tlv_info.it_magic, TLV_INFO_MAGIC))
+
+ tlv_off = tlv_info_offset + len(ImageTLVInfo())
+ tlv_end = tlv_info_offset + tlv_info.it_tlv_tot
+
+ # iterate over the TLV entries
+ while tlv_off < tlv_end:
+ in_file.seek(image_offset + tlv_off, 0)
+ tlv = ImageTLV.read_from_binary(in_file)
+
+ logging.debug(" tlv {:24s} len = {:4d}, len = {:4d}".format(get_tlv_type_string(tlv.it_type), tlv.it_len, len(tlv)))
+
+ if is_valid_signature(tlv) and args.signature:
+ damage_tlv(image_offset, tlv_off, tlv, out_file_content)
+ elif tlv.it_type == TLV_VALUES['SHA256'] and args.image_hash:
+ damage_tlv(image_offset, tlv_off, tlv, out_file_content)
+
+ tlv_off += len(tlv)
+
+
+def main():
+ args = get_arguments()
+
+ logging.debug("The script was started")
+
+ copyfile(args.in_file, args.out_file)
+ in_file = open(args.in_file, 'rb')
+
+ out_file_content = bytearray(in_file.read())
+
+ damage_image(args, in_file, out_file_content, 0)
+
+ in_file.close()
+
+ file_to_damage = open(args.out_file, 'wb')
+ file_to_damage.write(out_file_content)
+ file_to_damage.close()
+
+
+if __name__ == "__main__":
+ logging.basicConfig(format='%(levelname)5s: %(message)s',
+ level=logging.DEBUG, stream=sys.stdout)
+
+ main()