|
| 1 | +#!/usr/bin/python |
| 2 | + |
| 3 | +import sys |
| 4 | +import json |
| 5 | +import xlsxwriter |
| 6 | +import r2pipe |
| 7 | +from collections import OrderedDict |
| 8 | + |
| 9 | +# Predefined functions containing sensor addresses for comparision's |
| 10 | +sensors = { |
| 11 | + 'batt_voltage': ['0x9a56', '0x9f5b', '0xa166', '0xa307', '-0xae2c', '0xd982', '0xe1cd'], |
| 12 | + 'vehicle_speed': ['0x9be8', '0x9dce', '0xa59d', '0xa9a7', '0xafc6', '0xb960'], |
| 13 | + 'engine_speed': ['0xa59d', '0xa5ec', '0xa9a7', '0xafc6', '0xb5bf', '0xb960'], |
| 14 | + 'water_temp': ['0x9b46', '0xab56'], |
| 15 | + 'ignition_timing': ['0xdb1a', '0xda0f'], |
| 16 | + 'airflow': ['0xddcd'], |
| 17 | + 'throttle_position': ['0xe1cd'], |
| 18 | + 'knock_correction': ['0xafc6'] |
| 19 | +} |
| 20 | + |
| 21 | +results = [0, 0] |
| 22 | + |
| 23 | +class EcuFile: |
| 24 | + def __init__(self, file_name, functions): |
| 25 | + """ |
| 26 | + EcuFile constructor |
| 27 | + :param file_name: File name of ECU binary |
| 28 | + :param functions: JSON of function address & block hashes |
| 29 | + """ |
| 30 | + self.functions = OrderedDict() |
| 31 | + |
| 32 | + split = file_name.split('/') |
| 33 | + self.file_name = split[len(split) - 1] |
| 34 | + name = self.file_name.split('-') |
| 35 | + self.name = name[0][4:] + '-' + name[1][2:] + '-' + name[4].split('.')[0] |
| 36 | + |
| 37 | + r2 = r2pipe.open('./bins/' + self.file_name) |
| 38 | + r2.cmd('e asm.arch=m7700') |
| 39 | + r2.cmd('e anal.limits=true') |
| 40 | + r2.cmd('e anal.from=0x9000') |
| 41 | + r2.cmd('e anal.to=0xffff') |
| 42 | + |
| 43 | + for address, hashes in functions.items(): |
| 44 | + # Clean up hashes |
| 45 | + hashes = hashes[1:-1].split(',') |
| 46 | + hashes = [x.replace('\'', '') for x in hashes] |
| 47 | + hashes = [x.strip(' ') for x in hashes] |
| 48 | + |
| 49 | + if hashes != [''] and int(address, 16) > 36864: |
| 50 | + self.functions[address] = hashes |
| 51 | + |
| 52 | + r2.quit() |
| 53 | + print('Created ECU file ' + self.file_name) |
| 54 | + |
| 55 | + |
| 56 | +class IndexTable: |
| 57 | + def __init__(self, ecu_file_1, ecu_file_2): |
| 58 | + """ |
| 59 | + IndexTable constructor |
| 60 | + :param ecu_file_1, ecu_file_2: ECU files used for this table |
| 61 | + """ |
| 62 | + self.indexes = OrderedDict() |
| 63 | + self.tables = OrderedDict() |
| 64 | + self.name = ecu_file_1.name + ' ' + ecu_file_2.name |
| 65 | + self.test_name = ecu_file_2.file_name |
| 66 | + |
| 67 | + # Custom cell formats |
| 68 | + self.header_format = book.add_format({'font_color': 'white', 'bg_color': 'black'}) |
| 69 | + self.purple_format = book.add_format({'font_color': 'white', 'bg_color': 'purple'}) |
| 70 | + self.blue_format = book.add_format({'font_color': 'white', 'bg_color': 'blue'}) |
| 71 | + self.red_format = book.add_format({'font_color': 'white', 'bg_color': 'red'}) |
| 72 | + |
| 73 | + print('Created index table ' + self.name) |
| 74 | + |
| 75 | + def push_index(self, function_1, function_2, jaccard_index): |
| 76 | + """ |
| 77 | + Adds new 'cell' for table |
| 78 | + :param function_1, function_2: Header addresses |
| 79 | + :param jaccard_index: Jaccard Index calculation |
| 80 | + """ |
| 81 | + self.indexes[function_1, function_2] = jaccard_index |
| 82 | + |
| 83 | + def _write_format(self, sheet, highest_index, highest_test): |
| 84 | + """ |
| 85 | + Format cells with result data |
| 86 | + :param sheet: Excel sheet to write write results |
| 87 | + :param highest_index: Highest jaccad index in row |
| 88 | + :param highest_test: Highest gram compare in row |
| 89 | + """ |
| 90 | + if highest_index[1] == highest_test[1]: |
| 91 | + sheet.conditional_format( |
| 92 | + highest_index[0], highest_index[1], highest_index[0], highest_index[1], |
| 93 | + {'type': 'no_errors', 'format': self.purple_format} |
| 94 | + ) |
| 95 | + results[0] = results[0] + 1 |
| 96 | + else: |
| 97 | + sheet.conditional_format( |
| 98 | + highest_index[0], highest_index[1], highest_index[0], highest_index[1], |
| 99 | + {'type': 'no_errors', 'format': self.blue_format} |
| 100 | + ) |
| 101 | + sheet.conditional_format( |
| 102 | + highest_test[0], highest_test[1], highest_test[0], highest_test[1], |
| 103 | + {'type': 'no_errors', 'format': self.red_format} |
| 104 | + ) |
| 105 | + results[1] = results[1] + 1 |
| 106 | + |
| 107 | + def write_results(self, book, test_blocks): |
| 108 | + """ |
| 109 | + Writes all results to Excel sheet |
| 110 | + :param book: Excel sheet containing result data |
| 111 | + :param test_blocks: Code blocks to test results with |
| 112 | + """ |
| 113 | + print('Loading sheet ' + table.name) |
| 114 | + |
| 115 | + r2 = r2pipe.open('./bins/' + table.test_name) |
| 116 | + r2.cmd('e asm.arch=m7700') |
| 117 | + r2.cmd('e anal.limits=true') |
| 118 | + r2.cmd('e anal.from=0x9000') |
| 119 | + r2.cmd('e anal.to=0xffff') |
| 120 | + |
| 121 | + sheet = book.add_worksheet(table.name) |
| 122 | + sheet.freeze_panes(0, 1) |
| 123 | + sheet.set_column(0, 0, 23) |
| 124 | + |
| 125 | + row, col = 0, 0 |
| 126 | + highest_index = [0, 0, 0] |
| 127 | + highest_test = [0, 0, 0] |
| 128 | + tmp_key = '' |
| 129 | + |
| 130 | + # Write results to cells |
| 131 | + for keys, jaccard_index in table.indexes.items(): |
| 132 | + # Switch to new row |
| 133 | + if keys[0] != tmp_key: |
| 134 | + tmp_key = keys[0] |
| 135 | + row = row + 1 |
| 136 | + col = 1 |
| 137 | + |
| 138 | + # Side header for each row |
| 139 | + sheet.write(row, 0, keys[0], self.header_format) |
| 140 | + print('\t Added row {}'.format(keys[0])) |
| 141 | + |
| 142 | + if highest_index != [0, 0, 0]: |
| 143 | + self._write_format(sheet, highest_index, highest_test) |
| 144 | + |
| 145 | + highest_index = [0, 0, 0] |
| 146 | + highest_test = [0, 0, 0] |
| 147 | + else: |
| 148 | + col = col + 1 |
| 149 | + |
| 150 | + # Grab function gram for unknown file |
| 151 | + r2.cmd('s {}'.format(keys[1])) |
| 152 | + r2.cmd('af-') |
| 153 | + r2.cmd('aa') |
| 154 | + |
| 155 | + try: |
| 156 | + test_ins = [] |
| 157 | + for ins in json.loads(r2.cmd('pdfj').decode('utf-8', 'ignore'), strict=False, object_pairs_hook=OrderedDict)['ops']: |
| 158 | + test_ins.append(ins['opcode'].split(' ')[0].lower()) |
| 159 | + except: |
| 160 | + col = col - 1 |
| 161 | + continue |
| 162 | + |
| 163 | + # Calculate Jaccard index for test blocks |
| 164 | + for opcodes_list, address in test_blocks.items(): |
| 165 | + if address == keys[0].split('-')[0]: |
| 166 | + test_index = _jaccard_index(opcodes_list, test_ins) |
| 167 | + if test_index > highest_test[2]: |
| 168 | + highest_test = [row, col, test_index] |
| 169 | + |
| 170 | + # Check if encountered higher Jaccard index |
| 171 | + if jaccard_index > highest_index[2]: |
| 172 | + highest_index = [row, col, jaccard_index] |
| 173 | + |
| 174 | + sheet.write(0, col, keys[1], self.header_format) |
| 175 | + sheet.write(row, col, round(jaccard_index, 2)) |
| 176 | + |
| 177 | + r2.quit() |
| 178 | + |
| 179 | + self._write_format(sheet, highest_index, highest_test) |
| 180 | + |
| 181 | + |
| 182 | +def _jaccard_index(list_1, list_2): |
| 183 | + """ |
| 184 | + Calculate Jaccard index from two lists (Use shortest list as check) |
| 185 | + :param list_1, list_2: Lists to compare |
| 186 | + :returns: Jaccard index of list_1 & list_2 |
| 187 | + """ |
| 188 | + if len(list_1) < len(list_2): |
| 189 | + intersection = len([x for x in list_1 if x in list_2]) |
| 190 | + else: |
| 191 | + intersection = len([x for x in list_2 if x in list_1]) |
| 192 | + |
| 193 | + union = len(list_1) + len(list_2) - intersection |
| 194 | + |
| 195 | + return float(intersection) / union |
| 196 | + |
| 197 | + |
| 198 | +def _create_tables(control_file, ecu_files): |
| 199 | + """ |
| 200 | + Creates comparison tables |
| 201 | + :param ecu_files: List of EcuFile objects |
| 202 | + :returns: List of created tables |
| 203 | + """ |
| 204 | + tables = [] |
| 205 | + |
| 206 | + for ecu_file in ecu_files: |
| 207 | + table = IndexTable(control_file, ecu_file) |
| 208 | + |
| 209 | + # Loop through functions in ECU files |
| 210 | + for function_1, function_1_hashes in control_file.functions.items(): |
| 211 | + for function_2, function_2_hashes in ecu_file.functions.items(): |
| 212 | + for sensor, addresses in sensors.items(): |
| 213 | + if function_1 in addresses: |
| 214 | + table.push_index( |
| 215 | + function_1 + '-' + sensor, |
| 216 | + function_2, |
| 217 | + _jaccard_index(function_1_hashes, function_2_hashes) |
| 218 | + ) |
| 219 | + break |
| 220 | + |
| 221 | + tables.append(table) |
| 222 | + |
| 223 | + return tables |
| 224 | + |
| 225 | + |
| 226 | +if __name__ == '__main__': |
| 227 | + ecu_files = [] |
| 228 | + control_file = None |
| 229 | + |
| 230 | + if len(sys.argv) != 3: |
| 231 | + print('Run \'python JsonParser.py file.json output.xlsx') |
| 232 | + exit() |
| 233 | + |
| 234 | + # Open & parse JSON dump |
| 235 | + with open(sys.argv[1]) as file: |
| 236 | + json_data = json.load(file, object_pairs_hook=OrderedDict) |
| 237 | + |
| 238 | + for file_name in json_data: |
| 239 | + ecu_file = EcuFile(file_name, json_data[file_name]) |
| 240 | + |
| 241 | + # Pick out control file |
| 242 | + if ecu_file.name == '27-93-EG33': |
| 243 | + control_file = ecu_file |
| 244 | + else: |
| 245 | + if 'EG33' in ecu_file.name: |
| 246 | + ecu_files.append(ecu_file) |
| 247 | + |
| 248 | + # Open and format block data |
| 249 | + test_blocks = OrderedDict() |
| 250 | + |
| 251 | + with open('blocks.txt') as file: |
| 252 | + # radare setup |
| 253 | + r2 = r2pipe.open('./bins/722527-1993-USDM-SVX-EG33.bin') |
| 254 | + r2.cmd('e asm.arch=m7700') |
| 255 | + r2.cmd('e anal.limits=true') |
| 256 | + r2.cmd('e anal.from=0x9000') |
| 257 | + r2.cmd('e anal.to=0xffff') |
| 258 | + |
| 259 | + for address in [x.strip('\n') for x in file]: |
| 260 | + opcodes = [] |
| 261 | + |
| 262 | + r2.cmd('s {}'.format(address)) |
| 263 | + r2.cmd('af-') |
| 264 | + r2.cmd('aa') |
| 265 | + |
| 266 | + # Grab opcodes from function |
| 267 | + for ins in json.loads(r2.cmd('pdfj').decode('utf-8', 'ignore'), strict=False, object_pairs_hook=OrderedDict)['ops']: |
| 268 | + opcodes.append(ins['opcode'].split(' ')[0].lower()) |
| 269 | + |
| 270 | + test_blocks[tuple(opcodes)] = address |
| 271 | + |
| 272 | + r2.quit() |
| 273 | + |
| 274 | + # Setup Excel sheet |
| 275 | + book = xlsxwriter.Workbook(sys.argv[2]) |
| 276 | + tables = _create_tables(control_file, ecu_files) |
| 277 | + |
| 278 | + # Write all table data to sheets |
| 279 | + for table in tables: |
| 280 | + table.write_results(book, test_blocks) |
| 281 | + |
| 282 | + book.close() |
| 283 | + |
| 284 | + print('\nWrote values to {}\n'.format(sys.argv[2])) |
| 285 | + print('Final results {}%\n'.format(round((float(results[0]) / results[1]) * 100), 2)) |
0 commit comments