Understand how Threat Led Penetration Testing (TLPT) establishes a foundation for DORA compliance Watch the video›

Tearing Down (Sonic)Walls: Decrypting SonicOSX Firmware

Vulnerability Intelligence: Decrypting SonicOSX: Tearing Down Walls purple background.

Share

Summary

Bishop Fox researchers successfully reverse-engineered the encryption protecting SonicWall SonicOSX firmware, gaining access to the underlying file system for purposes of good-faith security research. They presented a walkthrough of their process and results at DistrictCon Year 0, the contents of which are described below. The researchers also released a tool, Sonicrack, to automatically extract keys from VMware virtual machine bundles and decrypt VMware NSv firmware images.

Background

SonicWall produces a range of next-generation firewall appliances that are frequently used by large commercial enterprises to protect their network perimeters. Appliance models include SOHO and TZ for consumers and small businesses, NSa for mid-size companies, NSsp and Supermassive for large enterprises, and NSv for hypervisor and cloud deployments.

Sonicwall next-gen firewall applicance models

These devices all run the SonicOS operating system, which currently has four major versions under active support (5 through 8). Prior to the release of version 7.1.1, firmware images for hardware appliances were distributed in a proprietary SIG encryption format, while NSv virtual machines used LUKS encryption to protect embedded volumes. In February 2022, Catalpa published a technique for extracting the encryption keys from memory and decrypting the NSv volumes. In December 2023, Praetorian improved on this technique by reverse-engineering the key derivation process and releasing a tool to decrypt static NSv images.

Starting with the release of version 7.0.1, SonicWall upgraded the operating system to SonicOSX, which brought significant changes to platform features, OS architecture, file system organization, and firmware packaging. Significantly, versions 7.1.1 and later of the NSv virtual machines use the same proprietary SIG encryption as the firmware for hardware appliances.

As a brief aside, it’s worth noting that a small proportion of firmware images for NSsp and NSv used a third proprietary encryption format, SWI. Our research team successfully reverse-engineered that format as well, and published a walkthrough of the decryption process on our blog in December.

Here’s a summary of encryption formats by appliance model and OS series:

Summary of encryption formats by appliance model and OS series


Walkthrough Part 1: Extracting Keys

In this walkthrough, we’ll be using an Open Virtualization Application (OVA) version of SonicOSX version 7.1.1-7047.

Our goal is to unpack the VMware OVA image and get to the root file system so that we can analyze the files within it and gain an understanding of how the operating system functions. Typically, we would expect the process to look like this for an unencrypted file system:

Display of Unpacked VMware OVA image process

The OVA bundle is just a tar archive – once unpacked, it contains several files that VMware uses to build the virtual machine, but all we care about is the primary virtual machine disk (a VMDK file). That file is also an archive, which can be unpacked using 7zip. Typically, this archive contains several volume images that are mounted when the virtual machine boots. One of these should contain the firmware, and we usually just need to mount the image to extract it. From there, the firmware could be in a variety of formats, but if it's not encrypted, then we just have to unpack it to get the root file system.

When the file system is encrypted, we must decrypt it before we can unpack the file system. To do that, we need to find encryption keys and some indication of how the encryption was performed. In a hardware appliance, we would expect the keys to be contained within some sort of secure storage mechanism, such as a Trusted Platform Module (TPM). In a virtual machine, however, no such mechanism is available, so we know the keys must be contained somewhere within the virtual machine image. We would first check all the volume images to see if the keys are just sitting there, but that doesn’t happen very often. They could also be stored in the NVRAM file, but what we commonly find is they are stored somewhere within the bootloader:

Display of unpacking VMware OVA image process with bootloader storage

As a result, most of the fun in reverse-engineering encrypted firmware is in dissecting the bootloader to hunt for keys! Each vendor seems to take a different approach to this part and, as you’ll see, SonicWall chose a particularly complex one.

We start by unpacking the OVA file and then disk1.vmdk as described above, and we find several embedded volumes:

Embedded volumes

We mount these images, examine the volumes, and find that only two contain files: BOOT and INSTALL-CACHE:

Volumes with BOOT
Volumes with INSTALL-CACHE

The BOOT volume contains an EFI bootloader, and the INSTALL-CACHE volume contains firmware in SIG format. If we run binwalk on the firmware binary, it fails to identify any file signatures within:

$ binwalk currentFirmware.bin.sig
Analyzed 1 file for 85 file signatures (187 magic patterns) in 1.4 seconds

Furthermore, binwalk’s entropy analysis feature shows high entropy throughout the file (note the horizontal yellow line), suggesting that it is encrypted:

binwalk's entropy analysis

Since there is only one other file contained within the virtual machine volumes, it seems safe to assume the encryption keys must be somewhere within the bootloader. Using binwalk to automatically unpack bootx64.efi produces the following files:

Unpacked bootx64.efi

We inspect the Grub configuration file to discover the purpose of the other files:

$ cat grub.cfg
...omitted for brevity...

# SOURCE drop-in
source (memdisk)/dropin.cfg

###
### Boot Menu:
###
menuentry 'Installer' --unrestricted {
  set fallback=1
  linux (memdisk)/soniccorex.bin $linux_console $linux_quiet 
$linux_append
  initrd (memdisk)/initramfs.gz
} 

We can see here that dropin.cfg is run prior to loading the Grub boot menu, and by examining the contents of that file, we learn that its role is to enforce the use of secure boot:

$ cat dropin.cfg
### Don't let prod builds proceed with out secure-boot
get_efivar -f uint8 -s secured SecureBoot
if [ "${secured}" != "1" ]; then
    echo "Invalid firmware detected, please contact SonicWall Technical Support"
    echo "Shutting down ...."
    sleep 5
    halt
fi

We also learn that soniccorex.bin is a Linux kernel and initramfs.gz is used as an initial ramdisk (initrd). You probably know that the primary function of a bootloader is to load the kernel into memory and start the operating system’s boot process, but you’d be forgiven for not knowing what initrd is. According to Wikipedia, an initial ramdisk is:

…a scheme for loading a temporary root file system into memory, to be used as part of the Linux startup process.…commonly used to make preparations before the real root file system can be mounted.

That sounds like a great place to handle pre-boot operations like, say, decrypting a file system, does it not? Since binwalk didn’t find any keys hiding directly in the bootloader, let’s unpack the initramfs archive and see what we find.

Unpacked initramfs archive

Is your Spidey sense tingling? It looks like we have a key sitting here in the initial ramdisk file system (hooray!), but there also appears to be an encrypted archive called sunup (uh oh). Before we jump to any conclusions, let’s examine init to see what’s happening during this part of the boot process:

$ cat init
...omitted for brevity...

ROOT_MOUNT="/stage1rootfs"
ROOT_IMAGE_ENC=/sunup.cpio.gz.enc
ROOT_IMAGE="/tmp/sunup.cpio.gz"

...omitted for brevity...

    if test "$use_sunup_key_from" = "FS"
    then
      if is_key_in_fs
      then
        log "Found key in FS..."
        # key=/onetime.key is present in FS
      else
        halt "Unable to setup stage2 (key not in fs) ..."
      fi
    fi

...omitted for brevity...

    # decrypt stage2 (sunup)
    openssl enc -a -d -aes-256-cbc -pbkdf2 \
      -in $ROOT_IMAGE_ENC -salt \
      -out $ROOT_IMAGE \
      -pass file:${KEY}

...omitted for brevity...
    # switch_root can only switch to a mount point (not a dir)
    mount -t tmpfs tmpfs "$ROOT_MOUNT"

    ( cd "$ROOT_MOUNT" && gzip -dc "$ROOT_DISK/$ROOT_IMAGE" | cpio -dmi 2>/dev/null )
    log "Changing root to stage2 (sunup) ... "
    # boot the image

...omitted for brevity...

    exec switch_root -c /dev/console $ROOT_MOUNT /init ||
        fatal "Couldn't jump to stage2 initramfs ..."

First, we see that it’s a Bash script. The lines excerpted above indicate that the script mounts and boots a secondary initram file system, and the image it mounts is sunup.cpio.gz.enc. As we suspected, the image is encrypted, and when onetime.key is present in the primary initram file system, the key is used to decrypt the image before mounting it.

Another part of the script indicates that the sunup encryption key might instead be extracted from various secure storage mechanisms, which is good to know, but is irrelevant since we’re analyzing a virtual machine and secure storage is not available.

Let’s continue down the rabbit hole and use the openssl command above to decrypt the sunup image, then unpack it to see what files it contains:

$ openssl enc -a -d -aes-256-cbc -pbkdf2 -in sunup.cpio.gz.enc -salt -out sunup.cpio.gz -pass file:onetime.key

$ gunzip sunup.cpio.gz

$ cpio -i <sunup.cpio
dev/console: Can't create 'dev/console'
244944 blocks
Decryption of sunup image

Here we have yet another file system, the stage 2 initramfs, also known as the sunup installer. A quick search reveals some keys that might be the firmware keys we’re looking for:

$ find . -name "*.key"
./usr/share/installer/FW-crypt-release.key
./usr/share/installer/SUNUP-crypt-release.key
./usr/share/installer/SCX-crypt-release.key

Once again, we need to analyze the init script to understand what actually happens during this part of the boot process. Before we continue, however, let’s recap what we’ve done so far. Here’s a flowchart illustrating the steps we took to get from the OVA package down to the sunup volume, with each distinct file system represented as a new row in the chart:

Flowchart illustrating the steps from OVA package to sunup volume

Walkthrough Part 2: Decrypting Firmware

Analyzing the init script within the sunup volume (stage 2 initramfs) points us to several Bash scripts contained within the init.d directory, which in turn point to many more Bash scripts in /usr/bin.

Eventually, we find a handful of files that are responsible for decrypting the firmware and installing the root file system:

  • /usr/bin/installer
  • /usr/bin/fwDecrypt
  • /usr/bin/multi-crypto
  • /usr/lib/config/platform/system.sh

Within the /usr/bin/installer script, we find the primary functions used to decrypt firmware images:

fwEncrypt() {
  CMD_TAG=fw-decrypt stdin: "$SOURCE_MONITOR" fwEncryptExtract
  SCX_PACKAGING+=(SW-fwEncrypt)
}

...omitted for brevity...

sw-crypt-package() {
  local format value type rest
  read -r format value type rest
  SCX_PACKAGING+=(SW-crypt)
  SCX_SWP_HEADER="$format $value $type $rest"$'\n'$(dd 2>/dev/null bs=256 count=1 | base64)
  CMD_TAG=scx-decrypt stdin: "$SOURCE_MONITOR" swpackage_decrypt "${SCX_SWP_HEADER#*$'\n'}"
}

These functions appear to decrypt the SIG firmware bundle and something referred to as “swpackage.” This is interesting, because it suggests that two rounds of decryption are necessary to get to the root file system (we hope).

We’ll start by analyzing the fwEncrypt function.

fwEncrypt() {
  CMD_TAG=fw-decrypt stdin: "$SOURCE_MONITOR" fwEncryptExtract
  SCX_PACKAGING+=(SW-fwEncrypt)
}

We see that fwEncrypt is calling another function, fwEncryptExtract, which we find inside /usr/bin/fwDecrypt. The first part of this function defines a header format which it parses from the start of the SIG firmware image:

fwEncryptExtract() {
  local len=36 iv magic headerLen headerRev headerVer zero algoKeyId 

...omitted for brevity...

  readi4 algoKeyId &&
  readi2 sskbIndex &&
  readi2 rsaKeyLen && # len=20 characters
  test $(( $headerLen - $rsaKeyLen )) = 36 &&
  readxn 16 iv ||     # len=36 characters
  installer_fatal "Can't unpack fwEncrypt header"

The fwEncryptExtract function extracts encrypted key material from the file header and calls another function, recoverAesX, to decrypt it and obtain an AES key:

local rsaKeyName="${sskb[$sskbIndex]}"

  # we can now read rsaKeyLen bytes which is the encrypted aesKey
  local aes
  # shellcheck disable=SC2086
  if test "$rsaKeyLen" = 0
  then aes=$(recoverAesX $rsaKeyName < "$FWENCRYPT_KEY_DIR/aes-$algoKeyId.rsa")
  else aes=$(dd bs="$rsaKeyLen" count=1 2>/dev/null | recoverAesX $rsaKeyName)
  fi

The recoverAesX function shows that FW_DECRYPT_KEY is used to decrypt the AES key:

recoverAesX() {
  installer_decrypt_key "${1:-${FW_DECRYPT_KEY}}" | hexdump -ve '1/1 "%.2x"'
  test "${PIPESTATUS[*]}" = "0 0"
}

FW_DECRYPT_KEY therefore must be a key encrypting key, or KEK. Searching through the sunup file system, we find that FW_DECRYPT_KEY is defined in /usr/lib/config/platform/system.sh:

FW_DECRYPT_KEY='FW-crypt-release.key'

If you recall, FW-crypt-release.key is one of the files we guessed earlier was a firmware encryption key, so we seem to be on the right track! Now let’s take a closer look at how the installer_decrypt_key function works – we’ll find it in /usr/bin/multi-crypto:

# $1 contains decrypt-key name
# $2 has base64 encrypted payload (on or non-base64 on stdin)
# decrypt payload (encrypted key) using key in $1
installer_decrypt_key() {
  local names name padding paddings="oaep pkcs1" decrypt decrypt_type decrypt_types="${SCX_INSTALLER_KEY_SEARCH:-ss ss_extra file}"
  names="$1"

  debug "Decrypt using $names"
  # we grab payload base64 because we want multiple attempts to decrypt it
  if test -n "$2"
  then payload_b64="$2"
  else # on stdin
       payload_b64="$(base64)"
  fi

  for padding in $paddings
  do for decrypt_type in $decrypt_types
     do decrypt="$decrypt_type-rsa-$padding-decrypt"
        for name in $names
        do debug "Try $decrypt $name"
           base64 -d <<<"$payload_b64" | "$decrypt" "$name"
           if test "${PIPESTATUS[*]}" = "0 0"
           then debug "Decrypted with $decrypt $name"
                return 0
           fi
        done
     done
  done
}

This function essentially tries to decrypt the key material using the KEK with two different padding types: it tries oaep first, and if that fails, it tries pkcs1. Both of those function calls use openssl to perform the AES key decryption:

file-rsa-pkcs1-decrypt() {
  openssl rsautl -decrypt -inkey "${SCX_KEY_PATH}$1${SCX_KEY_SUFFIX}" 2>/dev/null && return
}

file-rsa-oaep-decrypt() {
  openssl rsautl -decrypt -oaep -inkey "${SCX_KEY_PATH}$1${SCX_KEY_SUFFIX}" 2>/dev/null && return
}

Alternative decrypt functions are called if the KEK was retrieved from secure storage, but in our case, because we’re analyzing a virtual machine, the KEK was retrieved from a file within the sunup volume.

Once the AES key has been decrypted, we return to the fwEncryptExtract function, which uses openssl to decrypt the rest of the firmware with the AES key, then validates the footer of the decrypted file:

openssl enc -d -nopad -aes-128-cbc -iv "$iv" -K "$aes" 2>/dev/null | fwDepadFooter

Let’s recap what we’ve learned so far with a flow chart:

Decrypting SIG firmware with KEK process
  1. We begin with an encrypted SIG firmware file and a firmware KEK.
  2. We split the firmware file into two parts, referred to in the scripts as an SSDH header and encrypted SSDH package.
  3. We extract encrypted key material from the SSHD header.
  4. We decrypt the key material using the firmware KEK to obtain an AES key.
  5. We decrypt the SSDH package using the AES key.

That completes our analysis of the fwEncrypt function from /usr/bin/installer. Next, we’ll analyze the sw-crypt-package function from /usr/bin/installer.

sw-crypt-package() {
  local format value type rest
  read -r format value type rest
  SCX_PACKAGING+=(SW-crypt)
  SCX_SWP_HEADER="$format $value $type $rest"$'\n'$(dd 2>/dev/null bs=256 count=1 | base64)
  CMD_TAG=scx-decrypt stdin: "$SOURCE_MONITOR" swpackage_decrypt "${SCX_SWP_HEADER#*$'\n'}"
}

Here we see that sw-crypt-package is extracting a 256-byte header from the SSDH package, base64-encoding it, and passing the result to a swpackage_decrypt function:

swpackage_decrypt() {
  local KEY
  # attempt to decrypt key from first 256 bytes
  if KEY="$(installer_decrypt_key "$INSTALLER_DECRYPT_KEY" "$1")"

This function calls installer_decrypt_key again, which repeats the process of extracting and decrypting key material from the header, only this time it’s using a different KEK. Returning to system.sh, we find the definition for INSTALLER_DECRYPT_KEY:

INSTALLER_DECRYPT_KEY='SCX-crypt-release.key'

Another win – we previously identified that file in the sunup volume too! After decrypting the AES key, swpackage_decrypt uses openssl to decrypt the rest of the SSDH package:

{ printf "Salted__" ; test -n "$1" && base64 -d <<<"$1" ; cat /dev/stdin ; } | openssl enc -d -pbkdf2 -aes-256-cbc -pass fd:4 4<<<"${KEY}" 2>/dev/null

The scripts refer to the resulting file as an SW package, which we are hoping contains the root file system (our goal). Let’s recap the firmware decryption process once more:

Decrypting SIG firmware with KEKs additional recap
  1. We begin with an encrypted SIG firmware file, a firmware KEK, and a software KEK.
  2. We split the firmware file into two parts, referred to in the scripts as an SSDH header and encrypted SSDH package.
  3. We extract encrypted key material from the SSHD header.
  4. We decrypt the key material using the firmware KEK to obtain an AES key.
  5. We decrypt the SSDH package using the AES key.
  6. We split the decrypted SSDH package into two parts, referred to in the scripts as an SW header and encrypted SW package.
  7. We extract encrypted key material from the SW header.
  8. We decrypt the key material using the software KEK to obtain an AES key.
  9. We decrypt the SW package using the AES key.

Now, all that remains is to analyze the decrypted SW package to see if it contains the root file system we’re looking for. Let’s run binwalk to see what it tells us:

$ binwalk -e sw-package
-------------------------------------------------------------------------------------------------------------------------------------------------------------
DECIMAL                            HEXADECIMAL                        DESCRIPTION
-------------------------------------------------------------------------------------------------------------------------------------------------------------
0                                  0x0                                gzip compressed data, operating system: Unix, timestamp: 2024-02-05 22:50:37, total
                                                                      size: 2943 bytes
2943                               0xB7F                              gzip compressed data, original file name:
                                                                      "soniccorex-image-release-nsv-vmware-20240205204943.rootfs.ext4", operating system:
                                                                      Unix, timestamp: 2024-02-05 22:50:35, total size: 328547938 bytes
328550881                          0x139549E1                         gzip compressed data, original file name:
                                                                      "soniccorex-image-release-nsv-vmware-20240205204943.rootfs.ext4.verity", operating
                                                                      system: Unix, timestamp: 2024-02-05 22:50:37, total size: 19841152 bytes
-------------------------------------------------------------------------------------------------------------------------------------------------------------
[+] Extraction of gzip data at offset 0x0 completed successfully
[+] Extraction of gzip data at offset 0xB7F completed successfully
[+] Extraction of gzip data at offset 0x139549E1 completed successfully
-------------------------------------------------------------------------------------------------------------------------------------------------------------

Analyzed 1 file for 85 file signatures (187 magic patterns) in 6.1 seconds

Excellent! It looks like we found what we were looking for. Let’s unpack the rootfs image with 7zip (noting binwalk didn’t keep the original filename):

$ mkdir rootfs
$ cd rootfs
$ 7z x ../extractions/sw-package.extracted/B7F/decompressed.bin

...omitted for brevity...

Folders:    980
Files:      11119
Size:       700436639
Compressed: 681230336

Finally, at long last, we can browse through the root file system of SonicOSX:

Root file system of SonicOSX

Just to prove to ourselves that we finally reached our goal, let’s search for sonicosv, the main binary that runs most of the firewall functionality:

$ find . -name sonicosv
./usr/share/lxc/containers/app-container-image-sonicos/rootfs/opt/sonicwall/sonicos/bin/sonicosv

$ cd usr/share/lxc/containers/app-container-image-sonicos/rootfs/opt/sonicwall/sonicos/bin

$ file sonicosv
sonicosv: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux-x86-64.so.2, BuildID[sha1]=cf5f4698ddd071c6733e681a85519fbdd2a21094, for GNU/Linux 3.2.0, stripped

Mission accomplished! This journey was a longer one than we anticipated, so let’s see if we can capture the whole process in a single flowchart:

Flowchart complete process

Phew! After putting together this diagram, we couldn’t help ourselves – we had to compare it to Inception – and don’t you know, it worked out pretty well:

Process of decryption inception

Sonicrack

After going to all the trouble of reverse-engineering the key extraction and firmware decryption process for SonicOSX, we decided to open-source the fruits of our labor. Our intent behind releasing Sonicrack is to make the current generation of SonicWall firewalls more accessible for good-faith security research. After all, if our small team of researchers can successfully decrypt the firmware, well-funded nation-state actors have certainly done the same. It is our belief that tools like this one help to level the playing field for those who work to protect consumers from malicious adversaries.

To use Sonicrack, you’ll need to start by running it against a VMware OVA image. We recommend building and running it in a Docker container since some of the dependencies can behave differently in other environments.

$ docker build -t sonicrack .

...omitted for brevity...

$ docker run -it --privileged --rm -v .:/data sonicrack ./sonicrack.py --keys /data/keys --output /data /data/nsv-vmware.7.1.1-7047-R5557.ova
[*] Extracting files from OVA archive
[*] Decrypting firmware image
[+] Successfully decrypted soniccorex-image-release-nsv-vmware-20240205204943.rootfs.ext4

Performing the first run against an OVA image is important so that Sonicrack can extract the keys:

$ ls keys
FW-crypt-release.key  SCX-crypt-release.key

Once you have the keys, you can decrypt any VMware SIG firmware (version 7.1.1 and later):

$ docker run -it --rm -v .:/data sonicrack ./sonicrack.py --keys /data/keys --output /data /data/sw_nsv_vmware_eng.7.1.3-7015-R6965.bin.sig
[*] Decrypting firmware image
[+] Successfully decrypted soniccorex-image-release-nsv-vmware-20241209200721.rootfs.ext4

Happy hacking! And as always, be sure to check out a few of our additional resources to see further research on SonicWall vulnerabilities and insights.

Subscribe to our blog and advisories

Be first to learn about latest tools, advisories, and findings.


Jon Williams

About the author, Jon Williams

Senior Security Engineer

As a researcher for the Bishop Fox Capability Development team, Jon spends his time hunting for vulnerabilities and writing exploits for software on our customers' attack surface. He previously served as an organizer for BSides Connecticut for four years and most recently completed the Corelan Advanced Windows Exploit Development course. Jon has presented talks and written articles about his security research on various subjects, including enterprise wireless network attacks, bypassing network access controls, and malware reverse engineering.

More by Jon

This site uses cookies to provide you with a great user experience. By continuing to use our website, you consent to the use of cookies. To find out more about the cookies we use, please see our Privacy Policy.