This post will describe how to add a trigger that initiates a “self-destruct” sequence when your BusKill laptop kill cord’s connection is severed–rendering your data permanently & irrevocably destroyed in the event that your laptop were physically separated from you (ie: by a snach-and-run thief).
Many people were disappointed when the original post introducing BusKill only alluded to a self-destruct trigger, without actually describing how to use it with BusKill. This was done for two reasons:
- Most people probably don’t actually want an accidental false-positive to destroy all their data and
- A self-destruct sequence should be taken seriously. Its implementation should be thoroughly thought-out, tested, and forensically analyzed
This article will provide that thorough analysis and explain to the reader how to implement a self-destruct trigger with BusKill on linux machines that have FDE with LUKS.
Note: This is a very long article providing a detailed explanation on our forensic analysis in Kali to prove the efficacy of our self-destruct script. If you’d like, you can skip to the Install Steps.
But please read the disclaimer first, to understand the risks.
Disclaimer
This guide contains experimental files, commands, and software. The information contained in this article may or may not lead to corruption or total permanent deletion of some or all of your data. We’ve done our best to carefully guide the reader so they know the risks of each BusKill trigger, but we cannot be responsible for any data loss that has occurred as a result of following this guide.
The contents of this guide is provided openly and is licensed under the CC-BY-SA license. The software included in this guide is licensed under the GNU GPLv3 license. All content here is consistent with the limitations of liabilities outlined in its respective licenses.
We highly recommend that any experiments with the scripts included in this article are used exclusively on a disposable machine containing no valuable data.
If data loss is a concern for you, then leave now and do not proceed with following this guide. You have been warned.
Release Note
Also be aware that, due to the risks outlined above, BusKill will not be released with this “self-destruct” trigger.
BusKill will not be released with this “self-destruct” trigger. If you purchase a BusKill cable, it will only ship with non-destructive triggers
If you purchase a BusKill cable, it will only ship with non-destructive triggers that lock the screen or shutdown the computer. Advanced users can follow guides to add additional destructive triggers, such as the one described in this post, but they should do so at their own risk–taking carefully into consideration all of the warnings outlined above and throughout this article.
If you buy a BusKill cable, the worst that can happen is your computer will abruptly shutdown.
Investigating the LUKS Header
Before we go through the process of wiping our FDE LUKS header, let’s take some time to dig into the LUKS header’s structure and poke at it. That way, we can compare the actual bits on the storage drive before & after the self-destruct trigger has run, and we can do a forensic analysis to validate that our self-destruct trigger is working.
Some of this data will necessarily be unencrypted metadata areas, some will be a bunch of 0s buffering between areas, and some will be areas of just uninteligible/encrypted data.
Create Example LUKS Volume
In this article, I’m going to be publishing hexdumps of an encrypted LUKS volume’s header. This header contains the master key, salts, and other data that you absolutely never want to share with others.
For the purposes of this guide, we’ll be creating an ephemeral LUKS volume on a file. This has the added benefit of permitting the reader to follow-along with the commands on their own system without requiring our device names to match.
First, let’s create a 20M file from random data
user@host:~$ head -c 20M /dev/urandom > luksVol1 user@host:~$ du -sh luksVol1 20M luksVol1 user@host:~$
Now make it a LUKS volume. This command will make you type ‘YES
‘ to confirm before it will overwrite ‘luksVol1
‘. It will also ask you for a passphrase.
user@host:~$ sudo cryptsetup luksFormat luksVol1 [sudo] password for user: WARNING! ======== This will overwrite data on luksVol1 irrevocably. Are you sure? (Type uppercase yes): YES Enter passphrase for luksVol1: Verify passphrase: user@host:~$ sudo cryptsetup -v isLuks luksVol1 Command successful. user@host:~$
Success! Now that we have a luks volume setup in our file, we can start poking at the bytes on that file to see how the LUKS header is encoded. This is critical to how we design our self-destruct trigger script, so that we can verify that we’re actually wiping the master encryption key, salt, etc–rendering all the drive’s data useless to an adversary–even if the passphrase were known.
LUKS 0xbabe
Clemens Fruhwirth had a bit of fun with this one. The beginning of every LUKS partition (the magic number field) starts with LUKS BABE
!
More specifically, in hex that’s
0x4c55 0x4b53 0xbabe
And where
0x4c = L 0x55 = U 0x4b = K 0x53 = S
We can see this, for example, on our newly created luks container file using `head
` to grab the first 6 bytes and pass it to `od
`.
user@host:~$ head --bytes 6 luksVol1 | od --format c --endian big 0000000 L U K S 272 276 0000006 user@host:~$ head --bytes 6 luksVol1 | od --format x2 --endian big 0000000 4c55 4b53 babe 0000006 user@host:~$
A better tool for this is hexdump:
user@host:~$ hexdump -Cn 6 luksVol1 00000000 4c 55 4b 53 ba be |LUKS..| 00000006 user@host:~$
Hexdumps refresher
Before proceeding, I’ll provide a brief refresher for those of us that don’t look at hexdumps every day. If you do look at hexdumps often, feel free to skip this section.
First remember that a single bit can represent two values: a zero or a one. Four bits together (1111) can represent up to 1+2+4+8 = 15. Including zero, that’s 16 values.
A single digit in hex (0-F = base-16) can also represent up to 16 values.
A byte has 8 bits. That’s twice the length of the four-bits or a single hex digit shown above. Therefore, hexdumps usually group hex digits together in twos by defaut, as that represents a single byte.
Also conveniently, 8 bits = 2 hex digits = 1 ASCII character.
Now let’s consider this hexdump of the first 128 (decimal = base-10) = 0x80 (hex = base-16) = 1000 0000 (binary = base-2) bytes.
user@host:~$ hexdump -Cn 128 luksVol1 00000000 4c 55 4b 53 ba be 00 02 00 00 00 00 00 00 40 00 |LUKS..........@.| 00000010 00 00 00 00 00 00 00 03 00 00 00 00 00 00 00 00 |................| 00000020 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| * 00000040 00 00 00 00 00 00 00 00 73 68 61 32 35 36 00 00 |........sha256..| 00000050 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 00000060 00 00 00 00 00 00 00 00 63 48 e4 17 34 7a be 49 |........cH..4z.I| 00000070 cd 9a 15 8b 81 88 07 ef f6 04 16 f4 fc 7d 36 aa |.............}6.| 00000080 user@host:~$
Every line in the above output is broken into 3 sections:
- On the left: The offset (or start) byte of the line, encoded in hex
- In the middle: The data encoded in hex
- On the right: The data encoded as ASCII characters (whare a dot (.) means that the value cannot be represented in ASCII)
[1] The first section shows an offset of ‘00000000
‘, telling us that this line of data starts at the 0
th byte of data on our encrypted container luksVol1
. The second line is ‘00000010
‘. Again, that’s implicitly in hex, so 00000010
(hex) = 16
(decimal) = 0001 0000
(binary).
The last line with data is ‘00000070
‘, so the first byte on that last line (0xcd
) is the 0x70
th (hex) = 112
th (decimal) byte in our ‘luksVol1
‘ file. And the last byte on that line is the 0x7F
th (hex) = 127
th (decimal) byte. Remember that we’re counting from 0 here.
The next byte (not shown) that would appear in this file is the 0x80
th (hex) = 128
th byte of the file. Note that we passed `hexdump
` the ‘-n 128
‘ argument, indicating that we only wanted to see the first 128
bytes (in decimal). Note that this `hexdump -n
` argument, counts from 1, not 0.
[2] shows all our data in hex. Again, a single digit in hex fits 4 binary bits. 0xF
(hex) = 15
(decimal) = 1111
(binary). So each hex digit is grouped in twos before being delimited by a space, such that the space between the hex data here delimits each byte (8 bits).
Here we see the first byte of the first row is 0x4c
(hex) = 76
(decimal) = 0100 1100
(binary). A single line in a hexdump contains 32 hex digits = 16 bytes. The first set of 8 bytes are delimited by the second set of 8 bytes on a single line by a double-space.
On the first line of our hexdump output above, “0x02
” is the 7
th (decimal) byte and “0x00
” is the 8
th (decimal) byte of our LUKS container. The last byte of our first line (0x00
) is the 15
th (decimal) byte of the file, and the next byte that drops down to the next line (also 0x00
) is the 16
th (decimal) byte of the file.
[3] Delimited at the end of the line in pipes (|...|
) is the same data represented by [2], but displayed as characters in ASCII instead of hex. An ASCII character is represented by a single byte = two hex digits. Here we see the first four bytes of the device are 0x4c554b53
, which translates into “LUKS” in ASCII.
The next two bytes (0xbabe
) translate into “Masculine ordinal indicator” (º) followed by “Fraction three quarters” (¾) — which apparently our hexdump tool decided not to print, and just put a dot (.) instead *shrug*
Decoding the LUKS container’s header
At the time of writing, there’s actually two distinct specifications for how the bits are encoded on a LUKS container.
The first is LUKS1, which was first described in a paper titled LUKS On-Disk Format Specification published by Clemens Fruhwirth for his master’s thesis in 2005.
The second is LUKS2, which was formally documented in a paper titled LUKS2 On-Disk Format Specification published by Milan Brož (Principal Program Manager, Red Hat) in 2018.
In this article, our example LUKS containers all use LUKS2. LUKS2 was
- first added in `
cryptsetup
` v2.0.0 in 2017, - became the default LUKS version in `
cryptsetup
` v2.1.0 in 2019, and - became the default LUKS version with Debian 10 released in 2020.
The On-Disk Format for LUKS2 is very different from LUKS1. The only two data fields that match exactly are the ‘LUKS 0xbabe
‘ “magic number” field and the version
field.
offset | length | name | data type | description |
---|---|---|---|---|
0 | 6 | magic number | byte[] | ‘L’,’U’,’K’,’S’, 0xba, 0xbe |
6 | 2 | version | uint16_t | LUKS version |
As the table above shows, our first field is the so-called LUKS_MAGIC
field that starts at byte 0 and is 6 bytes long.
user@host:~$ hexdump -Cn 6 -s 0 luksVol1 00000000 4c 55 4b 53 ba be |LUKS..| 00000006 user@host:~$
Next is the version; it’s 2 bytes long, starts at byte 6, and it’s an unsigned 16-bit unsigned integer (uint16_t
). Here we can see that our new LUKS file volume is using LUKS version 2.
user@host:~$ hexdump -Cn 2 -s 6 luksVol1 00000006 00 02 |..| 00000008 user@host:~$
After that is where the two On-Disk Formats diverge.
luksErase
In January 2014, Kali Linux published an article titled How to Nuke your Encrypted Kali Installation that described how to use an old `cryptsetup
` patch (from 2008 by Juergen Pabel) that would permit the user to type a special duress password when decrypting the drive on-boot. When entered, this duress password would wipe (nuke) the encrypted drive where Kali was installed.
This sparked a discussion on the dm-crypt mailing list that ultimately lead to the creation of the luksErase
command in `cryptsetup
` v1.6.4 (released February 2014).
In 2008, Juergen Pabel submitted his patch for the LUKS “nuke” feature to Clemens Fruhwirth (then maintainer of LUKS), but it was never merged into `
cryptsetup
`.
In 2018 (ten years later), a feature request was submitted again, but it was rejected by the current maintainer of LUKS (Milan Brož)
Calling `luksErase
` overwrites the keyslots area–effectively rendering all of the encrypted data permanently irrecoverable. However, it does not wipe the plaintext metadata in the header.
I submitted a feature request asking for the `luksErase` command to have the ability to wipe the plaintext metadata in the header as well, but–meanwhile–we’ll just have to do that ourselves.
LUKS plaintext metadata
In order to wipe the entire LUKS header, including the plaintext metadata, we’ll call `cryptsetup luksErase
` followed by a command to overwrite the plaintext metadata area of the LUKS container. But, to do this, we first must determine the start byte and end byte of the metadata area.
And it’s good to be precise. If we overwrite past the end of the header, then the encrypted data bit will be corrupted (breaking the header restore process in the event that a user wishes to recover from a false-positive self-destruct). If we overwrite too little, then we may provide partial metadata as forensic evidence to the user’s adversary.
LUKS1 metadata
In LUKS1, the partition header is a short and relatively fixed-length of 1052672
or 2097152
bytes. The exact length can be determined by inspecting the ‘payload-offset
‘ field and multiplying it by 512
(see “Figure 1: PHDR layout” of LUKS1 On-Disk Format Specification). This field starts at an offset of 104
bytes and is 4
bytes long.
For example, the following hexdump shows the payload-offset
field has a value of 0x1000
(hex) = 4096
(decimal). Multiplying that by 512, we know that the LUKS container’s header ends at byte 4096*512
= byte 2097152
.
root@disp4117:~# hexdump -Cs 104 -n 4 luksVol1 00000068 00 00 10 00 |....| 0000006c root@disp4117:~#
Overwriting the first 2097152 bytes of a LUKS1 header is actually superfluous; it overwrites the plaintext metadata and the keyslots area (that we’ll already overwrite with `cryptsetup luksErase
`)–but it’s an easier boundry to decode, and overwriting 2MiB is fast enough for our needs..
LUKS2 metadata
In LUKS2, the header is actually made-up of six distinct headers, including
- A primary binary header (exactly 4096 bytes)
- The “1st JSON” area (16384-4194304 bytes)
- A secondary binary header (exactly 4096 bytes)
- The “2nd JSON” area (16384-4194304 bytes)
- The Keyslots area (variable length)
- The Alignment Padding area
The easiest way to determine the end byte of a LUKS2 header’s metadata is to get the `hdr_size
` field and double it.
In LUKS2, the `hdr_size
` field is the size of the first two header areas (the “primary binary header” area plus the “1st header area). So double that ends where the keyslots area begins–which is where the metadata ends.
OK, with all that prerequisite knowledge out of the way, let’s proceed with installing a fresh version of Ubuntu, nuking it, and analyzing the result.
Self-Destruct Test
In this section, we will do a forensics analysis of a drive before & after BusKll triggers a self-destruct sequence.
ATA Secure Erase
First, we’ll trigger an ATA Secure Erase on the laptop’s SSD, and we’ll do a forensic analysis of this drive in its fresh state before installing Ubuntu with FDE.
To preform this analysis, I’ll be using Kali Linux and its `bulk_extractor
` tool.
After booting to Kali, we do a quick check on the drive’s partition structure. What you see here was created by a previous Ubuntu Desktop install.
root@kali:~# fdisk -l /dev/sda Disk /dev/sda: 111.81 GiB, 120034123776 bytes, 234441648 sectors Disk model: WDC WDS120G2G0B- Units: sectors of 1 * 512 = 512 bytes Sector size (logical/physical): 512 bytes / 512 bytes I/O size (minimum/optimal): 512 bytes / 512 bytes Disklabel type: dos Disk identifier: 0xcb6eced1 Device Boot Start End Sectors Size Id Type /dev/sda1 * 2048 1050623 1048576 512M b W95 FAT32 /dev/sda2 1052670 234440703 233388034 111.3G 5 Extended /dev/sda5 1052672 2549759 1497088 731M 83 Linux /dev/sda6 2551808 234440703 231888896 110.6G 83 Linux root@kali:~#
Now–in order to proceed with the ATA Secure Erase–we have to suspend to disk to unfreeze the drive.
root@kali:~# hdparm -I /dev/sda | grep frozen frozen root@kali:~# echo -n mem > /sys/power/state
After it comes back, we go through the process to trigger an ATA Secure Erase. Note that these commands may vary (or not be applicable at all) to your storage device:
root@kali:~# hdparm -I /dev/sda | grep frozen not frozen root@kali:~# hdparm --user-master u --security-set-pass PASSWD /dev/sda security_password: "PASSWD" /dev/sda: Issuing SECURITY_SET_PASS command, password="PASSWD", user=user, mode=high root@kali:~# hdparm --user-master u --security-erase PASSWD /dev/sda security_password: "PASSWD" /dev/sda: Issuing SECURITY_ERASE command, password="PASSWD", user=user root@kali:~#
Note that the dive’s partition structure has been factory reset, and it’s filled with all zeros.
root@kali:~# fdisk -l /dev/sda Disk /dev/sda: 111.81 GiB, 120034123776 bytes, 234441648 sectors Disk model: WDC WDS120G2G0B- Units: sectors of 1 * 512 = 512 bytes Sector size (logical/physical): 512 bytes / 512 bytes I/O size (minimum/optimal): 512 bytes / 512 bytes root@kali:~# root@kali:~# hexdump -C /dev/sda 00000000 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| * 1bf2976000 root@kali:~#
Forensic Analysis 0 (after factory reset)
And, just to be sure, we’ll attempt to find any recognizable patterns. We’ll also be doing this again after the Ubuntu install to compare the differences.
Note that, in addition to using all the default scanners in `
bulk_extractor
`, we’re also passing-in regular expressions of the following byte sequences specific to our LUKS header:
- magic = 0x4c554b53babe
- “keyslot” = 0x6b6579736c6f74
- “stripe” = 0x737472697065
- “offset” = 0x6f6666736574
- “encrypt” = 0x656e6372797074
- “segment” = 0x7365676d656e74
root@kali:~# mkdir bulk_out root@kali:~# bulk_extractor -o bulk_out -f $'\x4c\x55\x4b\x53\xba\xbe' -f $'\x6b\x65\x79\x73\x6c\x6f\x74' -f $'\x73\x74\x72\x69\x70\x65' -f $'\x6f\x66\x66\x73\x65\x74' -f $'\x65\x6e\x63\x72\x79\x70\x74' -f $'\x73\x65\x67\x6d\x65\x6e\x74' /dev/sda ... root@kali:~# cat bulk_out/*.txt root@kali:~#
No surprise; it found nothing.
Install Ubuntu
Now, let’s install Ubuntu on our freshly wiped drive.
Of course, I’ll be choosing FDE at install time.
Forensic Analysis 1 (after Ubuntu install)
Now that Ubuntu is installed, let’s poke at the drive again in Kali and see what we can learn without decrypting it.
First, we see that the drive now has 4 partitions:
- /dev/sda1 is an unencrypted 512M W95 FAT32 dumb partition with an MBR
- /dev/sda2 is an unencrypted “extended” partition containing two “logical” partitions
- /dev/sda5 is an unencrypted 731M logical /boot parititon
- /dev/sda6 is the LUKS-encrypted 111G / partition
root@kali:~/bulk_out_sda6# fdisk -l /dev/sda Disk /dev/sda: 111.81 GiB, 120034123776 bytes, 234441648 sectors Disk model: WDC WDS120G2G0B- Units: sectors of 1 * 512 = 512 bytes Sector size (logical/physical): 512 bytes / 512 bytes I/O size (minimum/optimal): 512 bytes / 512 bytes Disklabel type: dos Disk identifier: 0x8d666a9a Device Boot Start End Sectors Size Id Type /dev/sda1 * 2048 1050623 1048576 512M b W95 FAT32 /dev/sda2 1052670 234440703 233388034 111.3G 5 Extended /dev/sda5 1052672 2549759 1497088 731M 83 Linux /dev/sda6 2551808 234440703 231888896 110.6G 83 Linux root@kali:~/bulk_out_sda6#
Note that Ubuntu’s default partition layout may differ from above if, for example, you have disks larger than 2T
We can verify this by doing a `hexdump
` on the encrytped ‘/dev/sda6
‘ partition, which shows the LUKS magic number, version, ‘hdr_size
‘, etc.
root@kali:~# hexdump -Cn 4832 /dev/sda6 00000000 4c 55 4b 53 ba be 00 02 00 00 00 00 00 00 40 00 |LUKS..........@.| 00000010 00 00 00 00 00 00 00 03 00 00 00 00 00 00 00 00 |................| 00000020 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| * 00000040 00 00 00 00 00 00 00 00 73 68 61 32 35 36 00 00 |........sha256..| 00000050 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 00000060 00 00 00 00 00 00 00 00 5e 4d 69 a0 3c 96 b1 9c |........^Mi.<...| 00000070 f7 84 2f 71 82 bb af 05 2f 4a 4c fe 60 f5 74 16 |../q..../JL.`.t.| 00000080 ea 52 a8 8b 34 7a 0f b4 a9 36 82 2a c1 4f 3f 89 |.R..4z...6.*.O?.| 00000090 c7 4e e4 5d 4a d1 39 b3 c7 e1 c5 20 4d bf c3 a8 |.N.]J.9.... M...| 000000a0 53 01 38 33 a4 f0 2e ae 31 31 37 35 39 64 62 61 |S.83....11759dba| 000000b0 2d 61 33 36 32 2d 34 30 38 37 2d 39 31 34 66 2d |-a362-4087-914f-| 000000c0 38 61 62 64 32 61 61 66 31 35 66 33 00 00 00 00 |8abd2aaf15f3....| 000000d0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| * 000001c0 7b cf a2 6c 86 ae 22 03 48 75 49 7a 8b 92 1e 28 |{..l..".HuIz...(| 000001d0 fa 64 a0 bc cf e7 7a 83 1b 75 d9 96 09 79 b5 8e |.d....z..u...y..| 000001e0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| * 00001000 7b 22 6b 65 79 73 6c 6f 74 73 22 3a 7b 22 30 22 |{"keyslots":{"0"| 00001010 3a 7b 22 74 79 70 65 22 3a 22 6c 75 6b 73 32 22 |:{"type":"luks2"| 00001020 2c 22 6b 65 79 5f 73 69 7a 65 22 3a 36 34 2c 22 |,"key_size":64,"| 00001030 61 66 22 3a 7b 22 74 79 70 65 22 3a 22 6c 75 6b |af":{"type":"luk| 00001040 73 31 22 2c 22 73 74 72 69 70 65 73 22 3a 34 30 |s1","stripes":40| 00001050 30 30 2c 22 68 61 73 68 22 3a 22 73 68 61 32 35 |00,"hash":"sha25| 00001060 36 22 7d 2c 22 61 72 65 61 22 3a 7b 22 74 79 70 |6"},"area":{"typ| 00001070 65 22 3a 22 72 61 77 22 2c 22 6f 66 66 73 65 74 |e":"raw","offset| ...
Now let’s use `bulk_extractor
` again on our new encrypted LUKS partition.
root@kali:~# bulk_extractor -q -1 -o bulk_out_sda6 -f $'\x4c\x55\x4b\x53\xba\xbe' -f $'\x6b\x65\x79\x73\x6c\x6f\x74' -f $'\x73\x74\x72\x69\x70\x65' -f $'\x6f\x66\x66\x73\x65\x74' -f $'\x65\x6e\x63\x72\x79\x70\x74' -f $'\x73\x65\x67\x6d\x65\x6e\x74' /dev/sda6 ... root@kali:~# root@kali:~# cd bulk_out_sda6 root@kali:~/bulk_out_sda6# find . -name \*.txt | grep -vi histogram | xargs cat # BANNER FILE NOT PROVIDED (-b option) # BULK_EXTRACTOR-Version: 1.6.0 ($Rev: 10844 $) # Feature-Recorder: json # Filename: /dev/sda6 # Feature-File-Version: 1.1 4828 {"keyslots":{"0":{"type":"luks2","key_size":64,"af":{"type":"luks1","stripes":4000,"hash":"sha256"},"area":{"type":"raw","offset":"32768","size":"258048","encryption":"aes-xts-plain64","key_size":64},"kdf":{"type":"argon2i","time":4,"memory":612334,"cpus":4,"salt":"ohyuAKaNOvY/727uMiYgZ6zgVR/FSVTc4UTunJjkXh8="}}},"tokens":{},"segments":{"0":{"type":"crypt","offset":"16777216","size":"dynamic","iv_tweak":"0","encryption":"aes-xts-plain64","sector_size":512}},"digests":{"0":{"type":"pbkdf2","keyslots":["0"],"segments":["0"],"hash":"sha256","iterations":66737,"salt":"VQ+dkN5lo0vw4oxAuJY4wU0DGP+1X6lhouurxBOi180=","digest":"Qc1HkrLGbL2iqQb1TqZq7UKjReRmLcObGqScyqUi3Vo="}},"config":{"json_size":"12288","keyslots_size":"16744448"}} 03101a951dfb1f6b277f90081e2f0331 21212 {"keyslots":{"0":{"type":"luks2","key_size":64,"af":{"type":"luks1","stripes":4000,"hash":"sha256"},"area":{"type":"raw","offset":"32768","size":"258048","encryption":"aes-xts-plain64","key_size":64},"kdf":{"type":"argon2i","time":4,"memory":612334,"cpus":4,"salt":"ohyuAKaNOvY/727uMiYgZ6zgVR/FSVTc4UTunJjkXh8="}}},"tokens":{},"segments":{"0":{"type":"crypt","offset":"16777216","size":"dynamic","iv_tweak":"0","encryption":"aes-xts-plain64","sector_size":512}},"digests":{"0":{"type":"pbkdf2","keyslots":["0"],"segments":["0"],"hash":"sha256","iterations":66737,"salt":"VQ+dkN5lo0vw4oxAuJY4wU0DGP+1X6lhouurxBOi180=","digest":"Qc1HkrLGbL2iqQb1TqZq7UKjReRmLcObGqScyqUi3Vo="}},"config":{"json_size":"12288","keyslots_size":"16744448"}} 03101a951dfb1f6b277f90081e2f0331 # BANNER FILE NOT PROVIDED (-b option) # BULK_EXTRACTOR-Version: 1.6.0 ($Rev: 10844 $) # Feature-Recorder: find # Filename: /dev/sda6 # Feature-File-Version: 1.1 0 LUKS\xBA\xBE LUKS\xBA\xBE\x00\x02\x00\x00\x00\x00\x00\x00@\x00\x00\x00\x00\x00\x00\x00 4098 keyslot \x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00{"keyslots":{"0":{"type": 4165 stripe "type":"luks1","stripes":4000,"hash":" 4218 offset :{"type":"raw","offset":"32768","size" 4251 encrypt size":"258048","encryption":"aes-xts-pl 4424 segment }},"tokens":{},"segments":{"0":{"type": 4456 offset "type":"crypt","offset":"16777216","si 4508 encrypt "iv_tweak":"0","encryption":"aes-xts-pl 4591 keyslot type":"pbkdf2","keyslots":["0"],"segmen 4608 segment eyslots":["0"],"segments":["0"],"hash": 4802 keyslot _size":"12288","keyslots_size":"1674444 20482 keyslot \x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00{"keyslots":{"0":{"type": 20549 stripe "type":"luks1","stripes":4000,"hash":" 20602 offset :{"type":"raw","offset":"32768","size" 20635 encrypt size":"258048","encryption":"aes-xts-pl 20808 segment }},"tokens":{},"segments":{"0":{"type": 20840 offset "type":"crypt","offset":"16777216","si 20892 encrypt "iv_tweak":"0","encryption":"aes-xts-pl 20975 keyslot type":"pbkdf2","keyslots":["0"],"segmen 20992 segment eyslots":["0"],"segments":["0"],"hash": 21186 keyslot _size":"12288","keyslots_size":"1674444 # BANNER FILE NOT PROVIDED (-b option) # BULK_EXTRACTOR-Version: 1.6.0 ($Rev: 10844 $) # Feature-Recorder: email # Filename: /dev/sda6 # Feature-File-Version: 1.1 935334046 d7R@y5.LU \x86\xEFO\xE7%睁F\x90\x05*\x9F\xC5\xD6\xC1d7R@y5.LU\xC8/\xB7\x9FP\xE0|m\xEF\x0A\x89\xA1\xAE\x0C\x80\xA2 # BANNER FILE NOT PROVIDED (-b option) # BULK_EXTRACTOR-Version: 1.6.0 ($Rev: 10844 $) # Feature-Recorder: domain # Filename: /dev/sda6 # Feature-File-Version: 1.1 935334050 y5.LU %睁F\x90\x05*\x9F\xC5\xD6\xC1d7R@y5.LU\xC8/\xB7\x9FP\xE0|m\xEF\x0A\x89\xA1\xAE\x0C\x80\xA2 root@kali:~/bulk_out_sda6#
The above output shows that `bulk_extractor
` picked-up the following from our LUKS-encrypted partition:
- The ‘
json
‘ scanner picked-up the two plaintext metadata JSON areas from the LUKS header. - The ‘
find
‘ scanner picked-up 21 distinct entries that matched one of our byte sequences - The ‘
email
‘ scanner picked-up a bogus email address “d7R@y5.LU” — which is probably just a coincidence of random data - Same as above, the ‘
domain
‘ scanner picked-up the same bogus “y5.LU” domain from above
And for completeness, let’s use `bulk_extractor
` again on the entire ‘/dev/sda
‘ drive–not just the LUKS-encrypted partition:
root@kali:~# bulk_extractor -q -1 -o bulk_out -f $'\x4c\x55\x4b\x53\xba\xbe' -f $'\x6b\x65\x79\x73\x6c\x6f\x74' -f $'\x73\x74\x72\x69\x70\x65' -f $'\x6f\x66\x66\x73\x65\x74' -f $'\x65\x6e\x63\x72\x79\x70\x74' -f $'\x73\x65\x67\x6d\x65\x6e\x74' /dev/sda ... root@kali:~# root@kali:~# cd bulk_out root@kali:~/bulk_out# find . -name \*.txt | grep -Evi '_histogram|_services' | xargs tail --silent -n+6 | wc -l 2400 root@kali:~/bulk_out#
I’ve simplified the details of the report above for brevity, but it’s sufficient to note that it detected 2,400 matches from the following features: url, telephone, find, email, domain, zip, elf, and zip.
Interestingly, `bulk_extractor
` found 8 LUKS magic number sequences across the entire disk, but only one of them actually looks like the start of a LUKS container. My best guess is that the other 7 are from binaries or the magic patterns database file.
root@kali:~/bulk_out# grep 'LUKS\\xBA\\xBE' find.txt 701434826 LUKS\xBA\xBE \xFE\xF0\x07ONE\x00LVM2 001\x00LUKS\xBA\xBE\x00CD\x0D\x00\xF3\x0CCDROM\x00JFS 765575408 LUKS\xBA\xBE _mega\x1C\x002SUB\xC5\x00\xF0\x07\x00LUKS\xBA\xBE\x00SKUL\xBA\xBE\x00crypto_\x15 782257838 LUKS\xBA\xBE \x0A\x00access denied\x00LUKS\xBA\xBE\x00%s != %s\x0A\x00Ciphe 795806644 LUKS\xBA\xBE \xFE\xF0\x07ONE\x00LVM2 001\x00LUKS\xBA\xBE\x00CD\x0D\x00\xF3\x0CCDROM\x00JFS 883016223 LUKS\xBA\xBE _mega\x1C\x002SUB\xC5\x00\xF0\x07\x00LUKS\xBA\xBE\x00SKUL\xBA\xBE\x00crypto_\x15 900664248 LUKS\xBA\xBE \xFE\xF0\x07ONE\x00LVM2 001\x00LUKS\xBA\xBE\x00CD\x0D\x00\xF3\x0CCDROM\x00JFS 975291012 LUKS\xBA\xBE _mega\x1C\x002SUB\xC5\x00\xF0\x07\x00LUKS\xBA\xBE\x00SKUL\xBA\xBE\x00crypto_\x15 1306525696 LUKS\xBA\xBE \x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00LUKS\xBA\xBE\x00\x02\x00\x00\x00\x00\x00\x00@\x00\x00\x00\x00\x00\x00\x00 root@kali:~/bulk_out# hexdump -Cs 701434826 -n 80 /dev/sda 29cf0bca 4c 55 4b 53 ba be 00 43 44 0d 00 f3 0c 43 44 52 |LUKS...CD....CDR| 29cf0bda 4f 4d 00 4a 46 53 31 00 5f 42 48 52 66 53 5f 4d |OM.JFS1._BHRfS_M| 29cf0bea 00 52 65 49 73 45 72 34 08 00 23 46 73 09 00 15 |.ReIsEr4..#Fs...| 29cf0bfa 32 0a 00 af 33 46 73 00 4f 43 46 53 56 32 e8 00 |2...3Fs.OCFSV2..| 29cf0c0a 0c 39 67 7a 69 07 82 2a 3e 14 2c 47 11 63 f7 b5 |.9gzi..*>.,G.c..| 29cf0c1a root@kali:~/bulk_out# hexdump -Cs 765575408 -n 80 /dev/sda 2da1c0f0 4c 55 4b 53 ba be 00 53 4b 55 4c ba be 00 63 72 |LUKS...SKUL...cr| 2da1c100 79 70 74 6f 5f 15 00 33 00 00 40 a9 01 15 80 92 |ypto_..3..@.....| 2da1c110 0e 03 08 00 13 02 08 00 13 04 08 00 13 08 08 00 |................| 2da1c120 14 10 c2 18 03 08 00 02 41 00 c0 44 4d 5f 69 6e |........A..DM_in| 2da1c130 74 65 67 72 69 74 79 0d 00 00 0a 10 70 74 79 5f |tegrity.....pty_| 2da1c140 root@kali:~/bulk_out# hexdump -Cs 782257838 -n 80 /dev/sda 2ea04eae 4c 55 4b 53 ba be 00 25 73 20 21 3d 20 25 73 0a |LUKS...%s != %s.| 2ea04ebe 00 43 69 70 68 65 72 20 25 73 20 69 73 6e 27 74 |.Cipher %s isn't| 2ea04ece 20 61 76 61 69 6c 61 62 6c 65 00 69 6e 76 61 6c | available.inval| 2ea04ede 69 64 20 6b 65 79 73 69 7a 65 20 25 64 00 65 63 |id keysize %d.ec| 2ea04eee 62 00 70 6c 61 69 6e 00 63 62 63 2d 00 70 63 62 |b.plain.cbc-.pcb| 2ea04efe root@kali:~/bulk_out# hexdump -Cs 795806644 -n 80 /dev/sda 2f6f0bb4 4c 55 4b 53 ba be 00 43 44 0d 00 f3 0c 43 44 52 |LUKS...CD....CDR| 2f6f0bc4 4f 4d 00 4a 46 53 31 00 5f 42 48 52 66 53 5f 4d |OM.JFS1._BHRfS_M| 2f6f0bd4 00 52 65 49 73 45 72 34 08 00 23 46 73 09 00 15 |.ReIsEr4..#Fs...| 2f6f0be4 32 0a 00 af 33 46 73 00 4f 43 46 53 56 32 e8 00 |2...3Fs.OCFSV2..| 2f6f0bf4 0c 39 67 7a 69 07 82 2a 3e 14 2c 47 11 63 f7 b5 |.9gzi..*>.,G.c..| 2f6f0c04 root@kali:~/bulk_out# hexdump -Cs 883016223 -n 80 /dev/sda 34a1c21f 4c 55 4b 53 ba be 00 53 4b 55 4c ba be 00 63 72 |LUKS...SKUL...cr| 34a1c22f 79 70 74 6f 5f 15 00 33 00 00 40 a9 01 15 80 92 |ypto_..3..@.....| 34a1c23f 0e 03 08 00 13 02 08 00 13 04 08 00 13 08 08 00 |................| 34a1c24f 14 10 c2 18 03 08 00 02 41 00 c0 44 4d 5f 69 6e |........A..DM_in| 34a1c25f 74 65 67 72 69 74 79 0d 00 00 0a 10 70 74 79 5f |tegrity.....pty_| 34a1c26f root@kali:~/bulk_out# hexdump -Cs 900664248 -n 80 /dev/sda 35af0bb8 4c 55 4b 53 ba be 00 43 44 0d 00 f3 0c 43 44 52 |LUKS...CD....CDR| 35af0bc8 4f 4d 00 4a 46 53 31 00 5f 42 48 52 66 53 5f 4d |OM.JFS1._BHRfS_M| 35af0bd8 00 52 65 49 73 45 72 34 08 00 23 46 73 09 00 15 |.ReIsEr4..#Fs...| 35af0be8 32 0a 00 af 33 46 73 00 4f 43 46 53 56 32 e8 00 |2...3Fs.OCFSV2..| 35af0bf8 0c 39 67 7a 69 07 82 2a 3e 14 2c 47 11 63 f7 b5 |.9gzi..*>.,G.c..| 35af0c08 root@kali:~/bulk_out# hexdump -Cs 975291012 -n 80 /dev/sda 3a21c284 4c 55 4b 53 ba be 00 53 4b 55 4c ba be 00 63 72 |LUKS...SKUL...cr| 3a21c294 79 70 74 6f 5f 15 00 33 00 00 40 a9 01 15 80 92 |ypto_..3..@.....| 3a21c2a4 0e 03 08 00 13 02 08 00 13 04 08 00 13 08 08 00 |................| 3a21c2b4 14 10 c2 18 03 08 00 02 41 00 c0 44 4d 5f 69 6e |........A..DM_in| 3a21c2c4 74 65 67 72 69 74 79 0d 00 00 0a 10 70 74 79 5f |tegrity.....pty_| 3a21c2d4 root@kali:~/bulk_out# hexdump -Cs 1306525696 -n 80 /dev/sda 4de00000 4c 55 4b 53 ba be 00 02 00 00 00 00 00 00 40 00 |LUKS..........@.| 4de00010 00 00 00 00 00 00 00 03 00 00 00 00 00 00 00 00 |................| 4de00020 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| * 4de00040 00 00 00 00 00 00 00 00 73 68 61 32 35 36 00 00 |........sha256..| 4de00050 root@kali:~/bulk_out#
Self-Destruct
Now we’ll boot into Ubuntu, setup BusKill with this self-destruct trigger, and trip it!
First, download the lockscreen and self-destruct triggers from the ‘buskill-linux
‘ repo on GitHub and put them in /user/local/bin/
sudo wget -O /usr/local/bin/buskill-lock.sh https://raw.githubusercontent.com/BusKill/buskill-linux/master/triggers/buskill-lock.sh sudo wget -O /usr/local/bin/buskill-selfdestruct.sh https://raw.githubusercontent.com/BusKill/buskill-linux/master/triggers/buskill-selfdestruct.sh sudo chmod +x /usr/local/bin/buskill-lock.sh sudo chmod +x /usr/local/bin/buskill-selfdestruct.sh
Then add the udev rule that will call our `self-destruct.sh
` trigger script when the BusKill cable’s connection is severed. In this guide, we identify our BusKill-specific drive with the ‘ENV{ID_MODEL}=="Micromax_A74"
‘ udev property. You should replace this property with one that matches your BusKill-specific drive.
Note: to determine how to query your USB drive for device-specific identifiers, see Introducing BusKill: A Kill Cord for your Laptop.
cat << EOF | sudo tee /etc/udev/rules.d/buskill.rules ################################################################################ # File: /etc/udev/rules.d/buskill.rules # Purpose: Add buskill rules. More info: https://buskill.in/luks-self-destruct # Authors: Michael Altfield <michael@buskill.in> # Created: 2020-06-07 # License: GNU GPLv3 ################################################################################ ACTION=="remove", SUBSYSTEM=="usb", ENV{ID_MODEL}=="Micromax_A74", RUN+="/usr/local/bin/buskill-selfdestruct.sh --yes" EOF sudo udevadm control --reload
That’s it! When you remove a USB drive from your system matching the udev property defined in the udev rule above, the `selfdestruct.sh
` script will be triggered.
Fact: When I did this, I set a stop-watch on my first execution of the buskill self-destruct, and it counted 5.16 seconds between the time that I executed it and the time that my machine was fully shut-off. Excellent!
Forensic Analysis 2 (after self-destruct)
Did our self-destrut trigger work? Let’s check.
First evidence: well, I can’t boot to Ubuntu anymore. But let’s dig deeper in Kali.
A `hexdump
` on the encrytped ‘/dev/sda6
‘ partition, no longer shows the LUKS magic number, version, ‘hdr_size
‘, etc. Good so far.
Re-running `bulk_extractor
` shows that it still finds the coincidental email/domain ‘d7R@y5.LU
‘ and a new false-positive domain ‘0W.ER
‘.
But it appears that the buskill self-destruct script totally erradicated all of the json and the 21 LUKS byte sequence matches! Mission accomplished!
root@kali:~# bulk_extractor -q 19 -o bulk_out_sda6 -f $'\x4c\x55\x4b\x53\xba\xbe' -f $'\x6b\x65\x79\x73\x6c\x6f\x74' -f $'\x73\x74\x72\x69\x70\x65' -f $'\x6f\x66\x66\x73\x65\x74' -f $'\x65\x6e\x63\x72\x79\x70\x74' -f $'\x73\x65\x67\x6d\x65\x6e\x74' /dev/sda6 ... root@kali:~# root@kali:~# cd bulk_out_sda6/ root@kali:~/bulk_out_sda6# find . -name \*.txt | grep -vi histogram | xargs cat # BANNER FILE NOT PROVIDED (-b option) # BULK_EXTRACTOR-Version: 1.6.0 ($Rev: 10844 $) # Feature-Recorder: email # Filename: /dev/sda6 # Feature-File-Version: 1.1 935334046 d7R@y5.LU \x86\xEFO\xE7%睁F\x90\x05*\x9F\xC5\xD6\xC1d7R@y5.LU\xC8/\xB7\x9FP\xE0|m\xEF\x0A\x89\xA1\xAE\x0C\x80\xA2 36662462829 xhV@0W.ER y4\x07w\xE2\xE6\xEBI\x1F1f\x0C,\x9AxhV@0W.ER`\x8E\x1DZ8\x0B\xEA\xDD\xF2\x8EȈ,\x8A\x8C# # BANNER FILE NOT PROVIDED (-b option) # BULK_EXTRACTOR-Version: 1.6.0 ($Rev: 10844 $) # Feature-Recorder: domain # Filename: /dev/sda6 # Feature-File-Version: 1.1 935334050 y5.LU %睁F\x90\x05*\x9F\xC5\xD6\xC1d7R@y5.LU\xC8/\xB7\x9FP\xE0|m\xEF\x0A\x89\xA1\xAE\x0C\x80\xA2 36662462833 0W.ER \xE2\xE6\xEBI\x1F1f\x0C,\x9AxhV@0W.ER`\x8E\x1DZ8\x0B\xEA\xDD\xF2\x8EȈ,\x8A\x8C# root@kali:~/bulk_out_sda6#
And a scan on the entire ‘/dev/sda
‘ drive (not just the LUKS-encrypted parititon) shows:
root@kali:~# bulk_extractor -q 9 -o bulk_out_sda -f $'\x4c\x55\x4b\x53\xba\xbe' -f $'\x6b\x65\x79\x73\x6c\x6f\x74' -f $'\x73\x74\x72\x69\x70\x65' -f $'\x6f\x66\x66\x73\x65\x74' -f $'\x65\x6e\x63\x72\x79\x70\x74' -f $'\x73\x65\x67\x6d\x65\x6e\x74' /dev/sda bulk_extractor version: 1.6.0 ... root@kali:~# cd bulk_out_sda root@kali:~/bulk_out_sda# find . -name \*.txt | grep -Evi '_histogram|_services' | xargs tail --silent -n+6 | wc -l 3205 root@kali:~/bulk_out_sda#
Interestingly, this run after the self-destruct yielded more matches (3,205 vs 2,400) on the entire ‘/dev/sda
‘ device than before. My best guess is that this is a consequence of installing Ubuntu updates after the previous run of `bulk_extractor
` (including a kernel upgrade from 5.4.0-14
to 5.4.0-18
), which added additional files to the unencrypted ‘/boot
‘ partition ‘/dev/sda5
‘.
This is a great example of the type of data that might not be wiped when the BusKill self-destruct trigger is executed. Not only will all of these files be intact, but their metadata is also intact. Therefore, a forensics team would be able to collect enough evicence to conclude with relative or near-certainty:
- Which Linux distro you were running
- Which kernel version you were running
- What day you installed your OS
- What day you updated all previous kernel versions
Note that the metadata indicating the timestamp that kernels were downlaoded/installed might be able correlate your previous browsing activites with network survalence records, even if you were browsing on the Tor anonymitity network.
root@kali:/# mount -o ro /dev/sda5 /mnt root@kali:/# ls -lah /mnt total 194M drwxr-xr-x 5 root root 4.0K Mar 20 18:05 . drwxr-xr-x 1 root root 160 Mar 20 18:41 .. -rw-r--r-- 1 root root 233K Jan 13 11:26 config-5.4.0-1002-oem -rw-r--r-- 1 root root 233K Feb 6 22:30 config-5.4.0-14-generic -rw-r--r-- 1 root root 233K Mar 7 16:23 config-5.4.0-18-generic drwxrwxr-x 2 root root 4.0K Mar 20 13:43 efi drwxr-xr-x 4 root root 4.0K Mar 20 18:05 grub lrwxrwxrwx 1 root root 27 Mar 20 18:03 initrd.img -> initrd.img-5.4.0-18-generic -rw-r--r-- 1 root root 79M Mar 20 18:05 initrd.img-5.4.0-14-generic -rw-r--r-- 1 root root 79M Mar 20 18:05 initrd.img-5.4.0-18-generic lrwxrwxrwx 1 root root 27 Mar 20 13:46 initrd.img.old -> initrd.img-5.4.0-14-generic drwx------ 2 root root 16K Mar 20 13:42 lost+found -rw-r--r-- 1 root root 179K Feb 13 23:09 memtest86+.bin -rw-r--r-- 1 root root 181K Feb 13 23:09 memtest86+.elf -rw-r--r-- 1 root root 181K Feb 13 23:09 memtest86+_multiboot.bin -rw------- 1 root root 4.5M Jan 13 11:26 System.map-5.4.0-1002-oem -rw------- 1 root root 4.5M Feb 6 22:30 System.map-5.4.0-14-generic -rw------- 1 root root 4.6M Mar 7 16:23 System.map-5.4.0-18-generic lrwxrwxrwx 1 root root 24 Mar 20 18:03 vmlinuz -> vmlinuz-5.4.0-18-generic -rw-r--r-- 1 root root 12M Mar 9 07:53 vmlinuz-5.4.0-14-generic -rw------- 1 root root 12M Mar 7 16:25 vmlinuz-5.4.0-18-generic lrwxrwxrwx 1 root root 24 Mar 20 13:49 vmlinuz.old -> vmlinuz-5.4.0-14-generic root@kali:/# root@kali:~# cd /mnt root@kali:/mnt# stat *5.4.0* ... File: System.map-5.4.0-1002-oem Size: 4697966 Blocks: 9176 IO Block: 4096 regular file Device: 805h/2053d Inode: 13 Links: 1 Access: (0600/-rw-------) Uid: ( 0/ root) Gid: ( 0/ root) Access: 2020-01-13 11:26:40.000000000 +0000 Modify: 2020-01-13 11:26:40.000000000 +0000 Change: 2020-03-20 13:43:07.934486042 +0000 Birth: - File: System.map-5.4.0-14-generic Size: 4714929 Blocks: 9216 IO Block: 4096 regular file Device: 805h/2053d Inode: 14 Links: 1 Access: (0600/-rw-------) Uid: ( 0/ root) Gid: ( 0/ root) Access: 2020-02-06 22:30:48.000000000 +0000 Modify: 2020-02-06 22:30:48.000000000 +0000 Change: 2020-03-20 13:43:08.014486046 +0000 Birth: - File: System.map-5.4.0-18-generic Size: 4732337 Blocks: 9248 IO Block: 4096 regular file Device: 805h/2053d Inode: 316 Links: 1 Access: (0600/-rw-------) Uid: ( 0/ root) Gid: ( 0/ root) Access: 2020-03-20 18:02:55.482467287 +0000 Modify: 2020-03-07 16:23:40.000000000 +0000 Change: 2020-03-20 18:00:44.106622375 +0000 Birth: -
Limitations/Improvements/Notes
This section will describe limitations, potential improvements, and additional notes about the information that was presented in this article.
Not a suitable replacement for TAILS
If you are an investigative journalist, activist, or political dissident operating in an oppressive regime where an adversary having access to your Internet activity could cause pain, suffering, or loss-of-life, then you should consider using TAILS.
ATA Secure Erase
Ah, the good ‘ol days of magnetic spinning disks. Unfortunately, since the advent of SSDs, wear-leaveling can translate a command to “overwrite this sector” to actually “overwrite who-knows-what.”
While LUKS was specifically designed with an anti-forensics information splitter to inflate the size of the master key, making it exponentially less likely that all of the key is remapped (rather than actually overwritten) when attempting to destroy the LUKS header, this technique (quoting the LUKS2 white paper) is “no longer much effective on modern storage devices.”
The best way to ensure safe erasure of the LUKS header on an SSD is therefore to use hdparam
to initiate an ATA Secure Erase.
Unfortunately, adding an ATA Secure Erase step to your self-destruct trigger necessarily means that all of your data is permanently lost, so even restoring a backup of the LUKS header would not permit data recovery in the event of a false-positive trigger.
Block Backup/Restore
In most cases, the LUKS header could be restored (regaining access to the drive’s data) after self-destruct sequence if a backup were made of the LUKS header.
The procedure for making a backup of the LUKS header and restoring it is outside the scope of this article, but suffice it to say that such a backup should probably be encrypted using something like `gpg
`, `openssl
` or LUKS, and that backup should be physically located in safe space, such as a lock box in an free country, avoiding the 14 eyes, and any countries that limit digital encryption rights.
Detached LUKS Header
LUKS supports using a detached header. With this configuration, your actual encrypted storage drive could be setup without any LUKS header actually stored on it–giving you total plausible deniability as your entire drive’s contents would be indistinguishable from a random distribution of bits.
Using a detached LUKS header, you could in theory get a 2-32 MB USB drive to store your LUKS header on, then have BusKill just overwrite the entire drive when triggered. This is a more robust solution to ensure your master key’s destruction (less error prone, even for future iterations of LUKS), and it would also be more fail-safe to recover from a false-positive: just download your (client-side encrypted) LUKS header from the cloud or a safety deposit box located in a free country, and use that to decrypt & access your data again.
Cold Boot Attacks
One theoretical attack against an anti-forensics tool like the BusKill self-destruct trigger described in this article is a so-called Cold Boot attack.
As far as I know, there is no evidence to suggest that a single political prisoner was caught or convicted by an opressive regime due to the results of a cold boot attack. At the time of writing, cold boot attacks exist in a purely academic realm.
Nevertheless, a welcome improvement to this BusKill self-destruct trigger would be to add a step to overwrite the RAM prior to shutdown. But such an implementation should be very carefully tested to ensure that it actually functions as intended across all linux distros–and that it doesn’t prevent the machine from being able to actually power-off. Otherwise, a poor implementation could actually make your self-destruct sequence less secure.
If your risk model is high enough to require defenses against cold boot attacks, you should thoroughly consider switching to TAILS–which has a well-tested implementation of RAM shredding.
Or you could just use an ultrabook with RAM soldered in-place. Or get yourself some superglue.
Unencrypted Drives unaffected
This trigger has proven to leave zero digital personal evience on the laptop after the buskill trigger, but it was far from leaving zero digital forensic evidence.
By default, Ubuntu still has over a gig of unencrypted partitions that have a ton of useful forensic data that is not wiped by the self-destruct sequence.
I intentionally designed this self-destruct trigger not to wipe this data because
- These unencrypted partitions should only be storing generic data. While it will tell a story of what OS you’re using, which version, and potentially when you downloaded updates, it shouldn’t have any files here that are unique to you and don’t already exist on thousands of other machines around the world
- Writing >1G of random data to disk takes time. The self-destruct sequence described in this article was intentionally designed to prioritize cutting power to the RAM ASAP.
That said, a welcome improvement may be a partitioning scheme that minimizes the size of the necessarily unencrypted partitions so that they could be quickly overwritten during the self-destruct sequence.
PRs Welcome!
Don’t like this BusKill self-destruct trigger implementation? Wish it did a RAM wipe? Wish it did an ATA secure erase? Wish it wiped all drives (not just LUKS ones)? Or wish it didn’t wipe all LUKS drives (only the rootfs one)?
Feel free to fork our buskill-linux repo on github, modify the buskill-selfdestruct.sh trigger, add your own trigger, and send us your a pull request.
Just be sure to share your work with the community using #BusKill on twitter or mastodon. And if you’d like your own trigger to be featured on this blog, feel free to contact us about it.
Further Reading
- New Methods in Hard Disk Encryption (Anti-Forensics Information Splitter) by Clemens Fruhwirth, 2004.
- LUKS1 On-Disk Format Specification by Clemens Fruhwirth and Milan Broz, 2005-2018.
- Cryptsetup FAQ (outdated)
- Wipe LUKS Header – Arch Wiki
Similar Projects
- Nuke My LUKS (doesn’t work for LUKS2)
- USBKill
- Silk Guardian
- USB Guard
- Suicide Crypt
- Bus Killer (No relation)
If you’d like to purchase a BusKill cable, click here.