Ivanti VPN: Kernel Decryption for Investigation

Table of contents
- Ivanti Secure Appliance (ISA):
- Introduction
- Step 1: Decryption Preparation
- Step 2: Attaching the Ivanti Evidence
- Step 3: Check if Physical Drive Successfully Attached
- Step 4: Extracting keys from first 3 partitions
- Step 5: Decrypting coreboot.img Using the Decryption Keys
- Step 6: Decompressing the Decrypted coreboot.img to find lvmkey
- Step 7: Decrypting the Disk

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 withlsblk
.
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 location0×10000a0 + 0xd0
=4944982f4e0c7482ff61164810a4bb69
For
key3
, the decryption key is placed at the location0×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
Starting from RAM. Runs bootloader (grub).
Bootloader loads both Linux kernel (
kernel
) andinitramfs
, which is shown ascoreboot.img
.Initramfs : A temporary filesystem image used to help the Linux Kernel mount the root filesystem and run the main Init system . — Debian Wiki
If
coreboot.img
(initramfs
) is encrypted,kernel
will use its key to decryptcoreboot.img
. This decrypted filesystem would be stored in the memory, thus it will disappear if the device restarts.In this filesystem, it includes two important files for disk encryption/decryption. First is the encryption/decryption tool
cryptsetup
. And second is thelvmkey
thatcryptsetup
would need in order to successfully encrypt/decrypt the disk image.
cryptsetup
will be executed by the “Init system” that was loaded by kernel.Now the disk is decrypted, we can control the disk image (partitions
groupA
,groupB
, andgroupZ
)
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 only4M
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 themount -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
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.