Ivanti VPN: Kernel Decryption for Investigation

OctopantagooseOctopantagoose
8 min read

Ivanti Secure Appliance (ISA):

  • Version: 22.x

  • Models: ISA300, ISA3000, ISA4000(-V), ISA5000, ISA6000(-V), ISA7000, ISA8000(-V)


Introduction

There are two types of Ivanti Connected Secure Appliances, both of which utilize full-disk encryption. The first type uses LILO bootloaders, while the second uses GRUB bootloaders.

The ISA-Series models we’re going to discuss uses GRUB Bootloaders. If you’re interested in kernel decryption with the PSA-Series models that uses LILO bootloaders, please check this blog instead.

Step 1: Decryption Preparation

A Virtual Machine, which I’ll be using Kali for demonstration.

Step 2: Attaching the Ivanti Evidence

Ivanti disk image can usually be collected with the dd command. However, in this blog, we’ll walk through the Ivanti appliances on Hyper-V (*.vhdx).

With image.vhdx, it would need to first be mount into a Physical Drive by FTK Imager:

Remember to change these settings on FTK Imager:

  • Mount Type: Physical Only

  • Mount Method: Block Device / Writable

Now, we can then mount it directly as a Hard Disk into our Kali environment. Shown in the figure below as Hard Disk 2 (SCSI).

If you are using VMware like us, to attach the physical disk, go to VM setting: Hardware > Hard Disk > SCSI > Use a physical disk > [choose the physical disk]

Step 3: Check if Physical Drive Successfully Attached

First, use lsblk to check if physical drive successfully mounted.

┌──(kali㉿kali)-[/mnt]
└─$ lsblk

As a verification step, open the image in FTK Imager and compare the number of detected partitions with the output of lsblk. If both showed the same number of partitions, the result is correct (Mounted drive is the sdb shown below).

By mounting it as physical drive, you’ll see something like sdb/sdc when viewed with lsblk.

If mounted unsuccessfully, check here.

Step 4: Extracting keys from first 3 partitions

Now we’re ready to extract the disk images for analysis.

Let’s first create three directories to serve as mount points for partitions extracted from the disk image. Then mount the partitions one by one:

┌──(root㉿kali)-[/mnt]
└─$ sudo mkdir /mnt/sdb{1..3}

┌──(root㉿kali)-[/mnt]
└─$ mount -o ro /dev/sdb1 /mnt/sdb1
┌──(root㉿kali)-[/mnt]
└─$ mount -o ro /dev/sdb2 /mnt/sdb2
┌──(root㉿kali)-[/mnt]
└─$ mount -o ro /dev/sdb3 /mnt/sdb3

Two main keys that we want are located in partition #2 and #3, one for the “current” version and one for the “rollback” version.

Through experience, we know that p1 is grub (probably the screen that shows whether if we want to do factory reset or run certain version).

And that the kernel under p2 and p3 are compressed kernel images of the “current” and “rollback” partitions.

We would start by using an open-source shell script: extract-vmlinux, which allows us to extract an uncompressed kernel image (vmlinux) from a compromised kernel image. Download this and continue on with the following commands.

┌──(root㉿kali)-[/home/kali]
└─$ sh extract-vmlinux.sh /mnt/sdb2/kernel > /Downloads/key2 

┌──(root㉿kali)-[/home/kali]
└─$ sh extract-vmlinux.sh /mnt/sdb3/kernel > /Downloads/key3 

┌──(root㉿kali)-[/tmp]
└─$ file key2

The extracted kernel image contains the key in a specific location, we can get it through locating the Linux Version ’s address as a starting point. The key will be located at an offset of either 0xc0 or 0xd0 from the starting point (depending on the version):

┌──(root㉿kali)-[/home/kali]
└─$ strings -t x ./Downloads/key2 | grep "Linux version "
10000a0 Linux version 4.17.00.36-selinux-jailing-22.7-r2-production (slt_ec_builder@asg-linux64-0015.lab.psecure.net) (gcc version 7.4.0 (Ubuntu 7.4.0-1ubuntu1~18.04.1)) 

┌──(root㉿kali)-[/home/kali]
└─$ echo $((0x10000a0+0xc0))
16777568

Undergo same process for both key2 and key3. With the calculated location with the extracted address, we can now extract the key:


┌──(root㉿kali)-[/home/kali]
└─$ xxd -s 16777568 -l 16 -p ./Downloads/key2
20323032340a00000000000000000000

┌──(root㉿kali)-[/home/kali]
└─$ xxd -s 16777568 -l 16 -p ./Downloads/key3
13d7b32e2600b7747d80fba8f8d5c7ca

Here, key2 seems pretty weird.

Important Note:
If we see the decryption key with a sequential ..00000.. try it again with another offset. A trial-and-error is needed to find the correct key.

Let’s try it with the offset 0xd0:

And compare it with the offset 0xc0:

We can now see that:

  • For key2, the decryption key is placed at the location 0×10000a0 + 0xd0 = 4944982f4e0c7482ff61164810a4bb69

  • For key3, the decryption key is placed at the location 0×10000a0 + 0xc0 = 13d7b32e2600b7747d80fba8f8d5c7ca

Below is a better visualisation illustrating the position of the decryption key relative to the starting point: Linux Version .

┌──(root㉿kali)-[/home/kali/Downloads]
└─$ xxd -s 0x10000a0 -l 0x100 -g 16 ./key3
010000a0: 4c696e75782076657273696f6e20342e  Linux version 4.
010000b0: 31372e30302e33352d73656c696e7578  17.00.35-selinux
010000c0: 2d6a61696c696e672d70726f64756374  -jailing-product
010000d0: 696f6e2028736c745f65635f6275696c  ion (slt_ec_buil
010000e0: 646572406173672d6c696e757836342d  der@asg-linux64-
010000f0: 303031352e6c61622e70736563757265  0015.lab.psecure
01000100: 2e6e6574292028676363207665727369  .net) (gcc versi
01000110: 6f6e20372e342e3020285562756e7475  on 7.4.0 (Ubuntu
01000120: 20372e342e302d317562756e7475317e   7.4.0-1ubuntu1~
01000130: 31382e30342e31292920233120534d50  18.04.1)) #1 SMP
01000140: 20547565204a756e2031382031363a32   Tue Jun 18 16:2
01000150: 353a33332055544320323032340a0000  5:33 UTC 2024...
01000160: 13d7b32e2600b7747d80fba8f8d5c7ca  ....&..t}.......
01000170: 00000000000000000000000000000000  ................
01000180: e0e51d81ffffffffb0e61d81ffffffff  ................
01000190: 60b81d81ffffffff70a41d81ffffffff  `.......p.......

┌──(root㉿kali)-[/home/kali]
└─$ xxd -s 16777568 -l 16 -p ./Downloads/key3
13d7b32e2600b7747d80fba8f8d5c7ca

Step 5: Decrypting coreboot.img Using the Decryption Keys

How Ivanti Works When Being Booted Up

  1. Starting from RAM. Runs bootloader (grub).

  2. Bootloader loads both Linux kernel (kernel) and initramfs, which is shown as coreboot.img.

    Initramfs : A temporary filesystem image used to help the Linux Kernel mount the root filesystem and run the main Init system . — Debian Wiki

  3. If coreboot.img(initramfs) is encrypted, kernel will use its key to decrypt coreboot.img. This decrypted filesystem would be stored in the memory, thus it will disappear if the device restarts.

  4. In this filesystem, it includes two important files for disk encryption/decryption. First is the encryption/decryption tool cryptsetup. And second is the lvmkey that cryptsetup would need in order to successfully encrypt/decrypt the disk image.

    cryptsetup will be executed by the “Init system” that was loaded by kernel.

  5. Now the disk is decrypted, we can control the disk image (partitions groupA, groupB, and groupZ)

Focusing on point #4, in order to decrypt the disk image, we need both lvmkey and cryptsetup, which are both under coreboot.img. From point #3, we also know that the "keys” from the kernel can be used to decrypt coreboot.img, this is where we’re going to start.

To decrypt coreboot.img with the keys we extracted from the previous step. We can use the help of the following code main.go.

Download Decryption Script (tool)

  • Github: Pantagoose/ivanti-decrypt

  • Purpose: Decrypts the encrypted partitions

  • Usage: ./main <input_file> <output_file> <aes_key_hex>

# Usage: ./main <input_file> <output_file> <aes_key_hex>

┌──(root㉿kali)-[/home/kali]
└─$ ./main /mnt/sdb2/coreboot.img /Downloads/2_coreboot.img.dec 4944982f4e0c7482ff61164810a4bb69

┌──(root㉿kali)-[/home/kali]
└─$ ./main /mnt/sdb3/coreboot.img /Downloads/3_coreboot.img.dec 13d7b32e2600b7747d80fba8f8d5c7ca

coreboot.img is only 4M in size.

Check if Successfully Decrypted

Partition #2 — Before and After Decryption

┌──(root㉿kali)-[/home/kali]
└─$ file /mnt/sdb2/coreboot.img
/mnt/sdb2/coreboot.img: OpenPGP Public Key

┌──(root㉿kali)-[/home/kali]
└─$ file ./Downloads/2_coreboot.img.dec        # Decrypted coreboot.img
./Downloads/2_coreboot.img.dec: gzip compressed data, from Unix, original size modulo 2^32 1012296094 gzip compressed data, reserved method, has CRC, extra field, from FAT filesystem (MS-DOS, OS/2, NT), original size modulo 2^32 1012296094

Partition #3 — Before and After Decryption

┌──(root㉿kali)-[/home/kali]
└─$ file /mnt/sdb3/coreboot.img
/mnt/sdb3/coreboot.img: data  

┌──(root㉿kali)-[/home/kali] 
└─$ file ./Downloads/3_coreboot.img.dec      # Decrypted coreboot.img
./Downloads/3_coreboot.img.dec: gzip compressed data, last modified: Sat Oct  5 17:32:45 2024, max compression, from Unix, original size modulo 2^32 948851932 gzip compressed data, unknown method, ASCII, has CRC, was "", has comment, encrypted, from FAT filesystem (MS-DOS, OS/2, NT), original size modulo 2^32 948851932

Step 6: Decompressing the Decrypted coreboot.img to find lvmkey

Part 1 — Decompress with gunzip

The decrypted version would be a gzip file. To decompress it let’s use the gunzip command:

After decompressing the gzip, it’s now an ASCII cpio archive file type.

Part 2 — Decompress with cpio

Just to keep things neat, since we are decompressing a filesystem, let’s decompress both in different directories.

┌──(root㉿kali)-[/home/kali/Downloads]
└─$ mkdir 2_coreboot 3_coreboot                                                          

┌──(root㉿kali)-[/home/kali/Downloads]
└─$ mv 2_coreboot.img.dec.1 2_coreboot                          

┌──(root㉿kali)-[/home/kali/Downloads]
└─$ mv 3_coreboot.img.dec.1 3_coreboot

Now we can decompress the “cpio archive files” by using the command cpio. Once decompressed, it will list out all the files like what’s shown below:

As the end product would be as follow:

The lvmkey is located under /etc:

┌──(root㉿kali)-[/home/kali/Downloads/here]
└─$ ls
2_coreboot  3_coreboot

┌──(root㉿kali)-[/home/kali/Downloads/here]
└─$ find . -name lvmkey
./3_coreboot/etc/lvmkey
./2_coreboot/etc/lvmkey

Now we get our lvmkey, we can go ahead and decrypt the image.

Step 7: Decrypting the Disk

You may wonder what about the cryptsetup tool? They do exist under /bin as well.

However, since we are using Kali, cryptsetup is included by default, so we don’t need to extract it from the evidence.

The following command is used to open (unlock) a LUKS-encrypted partition or disk so it can be accessed. With lsblk, we know that our target partitions are: groupA-home, groupA-runtime, groupB-home, and groupB-runtime.

cryptsetup luksOpen --key-file ./2_coreboot/etc/lvmkey /dev/groupA/home groupA_home
cryptsetup luksOpen --key-file ./2_coreboot/etc/lvmkey /dev/groupA/runtime groupA_runtime
cryptsetup luksOpen --key-file ./3_coreboot/etc/lvmkey /dev/groupB/home groupB_home
cryptsetup luksOpen --key-file ./3_coreboot/etc/lvmkey /dev/groupB/runtime groupB_runtime

Once we unlocked, we can finally mount the partitions to start our investigation.

To preserve the integrity of the evidence, ensure they are mounted in “read-only mode” using the
mount -o ro command:

mount -o ro /dev/mapper/groupA_home /mnt/groupA_home
mount -o ro /dev/mapper/groupA_runtime /mnt/groupA_runtime
mount -o ro /dev/mapper/groupB_home /mnt/groupB_home
mount -o ro /dev/mapper/groupB_runtime /mnt/groupB_runtime

One last check for the integrity of the partitions we just mounted:

And we’re done! :D

.

.

.

.

.


About the Author

Pantagoose - Trying to preserve knowledge but my brain cells aren’t helping anymore, so I’m relying on blogging as my backup memory.

Octo • Pantagoose • Boo


10
Subscribe to my newsletter

Read articles from Octopantagoose directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Octopantagoose
Octopantagoose

A bit of Security. A bit of Human.