PlaidCTF Writeup: Fun with Firewire

This is a writeup of the PlaidCTF 500 pts challenge “Fun with Firewire”.

Description:

Category: forensics

All of the machines at the AED office are encrypted using the amazing TrueCrypt software.
When we grabbed one of their USB sticks from a computer, we also grabbed the memory using the Firewire port.
Recover the key using the truecrypt image and the memory dump.

http://www.plaidctf.com/chals/81d9467f812d2fbb32e9d4b915cccfe457245f25.tar.bz2

Introduction

Given is a memory dump (128 MB) of a running Windows XP SP3 machine as well as a 32 MB file containing random data (a TrueCrypt volume image, according to the problem description). The memory dump was supposedly extracted via the Firewire port: The Firewire specification allows devices to have full DMA access. This allows forensic analysts (or a malicious hacker) to plug into any running computer that has a Firewire port and gain full access to the machine within seconds. Papers describing the attack and tools can be found at http://www.hermann-uwe.de/blog/physical-memory-attacks-via-firewire-dma-part-1-overview-and-mitigation. A different way to get a dump of the memory would be to conduct a “cold boot attack” as described in this paper.

Overview

To get an overview of the memory dump we inspect it with volatility. We see that TrueCrypt was running at the moment the dump was taken … good.

Further inspection of the memory dump reveals that the Operating System is Windows XP SP3, and the latest version of TrueCrypt (7.0a) is used. We reconstruct the setup by launching a VirtualBox installation, and we extract the memory using Mantech Memory Dumper mdd http://sourceforge.net/projects/mdd/. TrueCrypt offers the possibility to cache the passwords for mounting encrypted volumes. Comparing different memory dumps let us conclude that password caching was not enabled in the TrueCrypt software.

We briefly summarize the relevant technical details of TrueCrypt. More information can be found at http://www.truecrypt.org/docs/. In order to mount an encrypted volume, TrueCrypt uses the password and/or one or more key-files in order to decrypt the header (first 512 bytes of the volume). If the header gets correctly decrypted (a magic cookie is found), TrueCrypt reads the configuration (encryption algorithm and mode, etc.) as well as the master and secondary key into memory, and safely overwrites the memory regions where the password / key-file location was stored. The extracted master and secondary key is used for any further encryption and decryption of data. Since the data is encrypted and decrypted on the fly, these keys remain in memory. (Note that recent papers suggest storing the keys in CPU registers, more specifically in SSE registers http://portal.acm.org/citation.cfm?id=1752053 or in MSR registers http://arxiv.org/abs/1104.4843 instead of in the RAM in order to mitigate against these attacks.).

The default cipher used by TrueCrypt is AES in XTS mode which uses two 256 Bit AES-keys. We have to locate these keys in the memory dump. One option would be to analyze the data-structures and locate the memory region where TrueCrypt stores the keys. But it is easier to use a generic approach to locate AES keys since a tool for that task was already written for the “cold boot attack” research: AESKeyFinder.

Once we have the right keys, we replace the header of the encrypted volume with the header of an identical volume which we created and where we set the password (so that TrueCrypt starts the mounting process correctly), but have TrueCrypt patched so that it uses the extracted keys from the memory dump instead of the ones from the newly generated header.

Finding the keys

AESKeyFinder inspects memory dumps (or actually any kind of files) and performs a simple heuristic to estimate entropy. The tool targets the expanded AES keys and tests whether a contiguous region in memory satisfies the constraints of a valid AES key schedule https://secure.wikimedia.org/wikipedia/en/wiki/Rijndael_key_schedule.

So we run the tool in verbose mode:

./aeskeyfind physmem.bin -qv
FOUND POSSIBLE 256-BIT KEY AT BYTE 1166008

KEY: f0cbf260e0ca8ec2431089fb393a1c29513aaaa5847d13e8be84760968e64dc6

EXTENDED KEY:
f0cbf260e0ca8ec2431089fb393a1c29
513aaaa5847d13e8be84760968e64dc6
7f2846259fe2c8e7dcf2411ce5c85d35
88d2e6330caff5dbb22b83d2dacdce14
c0a3bc725f41749583b33589667b68bc
bbf3a356b75c568d0577d55fdfba1b4b
300c0fec6f4d7b79ecfe4ef08a85264c
c564547f723802f2774fd7ada8f5cce6
de47812eb10afa575df4b4a7d77192eb
cbc71b96b9ff1964ceb0cec96645022f
a030941d113a6e4a4ccedaed9bbf4806
dfcf49f96630509da8809e54cec59c7b
26eeb59637d4dbdc7b1a0131e0a54937
3ec9726358f922fef079bcaa3ebc20d1
03598b24348d50f84f9751c9af3218fe

CONSTRAINTS ON ROWS:
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
00000000000000000000000000000000
a4ba4e5eec12a4d672ca77143c4062874ae580efb9fe97bde3b3e6a81897e19b
1c2d49fc319ab86e317a676a77adecd005c26ac2f92330f4bf57e7fd25517be4
f0887dbdb886bbce1d09192c46d78bba7767303042f20f9e97f4a2ee9a069c19
896fc79ff18f46ec0300545c5bde9296ad29fd8abf019cbcc4286d680df23ef7
374fb5bf43bcc26f310dd6dd58dec6ca33047ae03810315e969c3149c9da539f
2d01ca16d2ec47826d5b7f7b69d31017a8d05433be7447d9e50989fc5f4662d6
461e700719d173152baa731904886f6c53e82a369c82e066c6575955a70678ed

FOUND POSSIBLE 256-BIT KEY AT BYTE 11674d4

KEY: 9b18635534875fc2ba1a74616e961caaaa907d8b285c7625bb44eb256b8de59d

EXTENDED KEY:
9b18635534875fc2ba1a74616e961caa
aa907d8b285c7625bb44eb256b8de59d
c7c13d2af34662e8495c168927ca0a23
66e41aad4eb86c88f5fc87ad9e716230
666b3921952d5bc9dc714d40fbbb4763
690eba5627b6d6ded24a51734c3b3343
80a82308158578c1c9f43581324f72e2
4a8aface6d3c2c10bf767d63f34d4e20
6b8794057e02ecc4b7f6d94585b9aba7
dddc9892b0e0b4820f96c9e1fcdb87c1
c290ecb5bc9200710b64d9348edd7293
c41dd84e74fd6ccc7b6ba52d87b022ec
050322a2b99122d3b2f5fbe73c288974
2f297fdc5bd4131020bfb63da70f94d1
33211cfe8ab03e2d3845c5ca046d4cbe

CONSTRAINTS ON ROWS:
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
00000000000000000000000000000000
d9ea24470c5bf1b15f3fe8d33eb683089a7ff9f198bb75cd3d2d8bed76e54625
f3acc19f88a6775a9e5c1d35828683225f9eebc3f912bd22c286ca034f297f9f
60f8969f3f106db49ffe4e6b1cda9e1776e957cf4dc7c9544c8871c38dafb59c
05a596765f1e018fb150a1bf8324d07caadd339decc14ac9b02f10f1c127c45f
5738b9015cbe40304bcdd62f327471c33b9672c7ada60c16d749078f7108d4ae
ca866774b97f05196d03a57579b9a7ec241885799511a598317b9cd2a641d321
b0823347a1175dd64d710fca14ba0299489e0a17bc3d358e83c3ff1b3c9ac97e

FOUND POSSIBLE 256-BIT KEY AT BYTE 7d852cc

KEY: 000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f

EXTENDED KEY:

000102030405060708090a0b0c0d0e0f
101112131415161718191a1b1c1d1e1f
a573c29fa176c498a97fce93a572c09c
1651a8cd0244beda1a5da4c10640bade
ae87dff00ff11b68a68ed5fb03fc1567
6de1f1486fa54f9275f8eb5373b8518d
c656827fc9a799176f294cec6cd5598b
3de23a75524775e727bf9eb45407cf39
0bdc905fc27b0948ad5245a4c1871c2f
45f5a66017b2d387300d4d33640a820a
7ccff71cbeb4fe5413e6bbf0d261a7df
f01afafee7a82979d7a5644ab3afe640
2541fe719bf500258813bbd55a721c0a
4e5a6699a9f24fe07e572baacdf8cdea
24fc79ccbf0979e9371ac23c6d68de36

CONSTRAINTS ON ROWS:
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
00000000000000000000000000000000
6948172fbb0d7ded3b16ce30696cda326d54b8480a0e0a0e0a0e0a0e0a0e0a0e
b29a81a5000000000000000000000000720676bd000000000000000000000000
69b5cd83000000000000000000000000fec82ba5000000000000000000000000
58fbba6f000000000000000000000000e2d69177000000000000000000000000
1fe3a63900000000000000000000000031467b85000000000000000000000000
b6a85bf0000000000000000000000000deaed73f000000000000000000000000
7cdc8bf900000000000000000000000045804db8a3b9352ffd620c9386f2fa8e


The “constraint on rows”-output tells us that the expanded keys are valid according to the AES key schedule. If we had bit errors in the respective memory regions (likely in cold boot attacks), not all constraints would have been met and AESKeyFinder would have calculated a guess for the original valid key.

So we have three keys after only a few of seconds of runtime - so far so good.

  1. f0cbf260e0ca8ec2431089fb393a1c29513aaaa5847d13e8be84760968e64dc6
  2. 9b18635534875fc2ba1a74616e961caaaa907d8b285c7625bb44eb256b8de59d
  3. 000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f

The entropy of (3) is really low, and we can definitely exclude it if we assume TrueCrypt is not totaly broken. This is good news since we have exactly two remaining 256-bit AES keys, as used by TrueCrypt in default configuration (AES in XTR mode).

Patching TrueCrypt

Next we read the source of TrueCrypt. Remember that TrueCrypt first decrypts the header with the password, and then reads the AES-key from the decrypted header. Reading in the header is done in Volume/VolumeHeader.cpp:VolumeHeader::Deserialize(.,.,.). We patch the code there, right after the master and secondary key was read from the decrypted header, and replace it with the hard-coded key value we found in the previous step. Our quick and dirty patch looks as follows:

--- truecrypt-7.0a-source/Volume/VolumeHeader.cpp
+++ truecrypt-7.0a-source.patched//Volume/VolumeHeader.cpp
06:00:20.000000000 -0700
@@ -6,6 +6,10 @@
+#include <iostream>
+#include <cstdlib>
+#include <cstdio>
+#include <fstream>
#include "Crc32.h"
#include "EncryptionModeXTS.h"
#include "Pkcs5Kdf.h"
@@ -201,8 +206,19 @@ namespace TrueCrypt
if (typeid (*mode) == typeid (EncryptionModeXTS))
{
-  ea->SetKey (header.GetRange (offset, ea->GetKeySize()));
-  mode->SetKey (header.GetRange (offset + ea->GetKeySize(), ea->GetKeySize()));
+
+  char * buffer = (char *)malloc(65);
+  buffer[64] = '\x00';
+  memcpy(buffer, "\xf0\xcb\xf2\x60\xe0\xca\x8e\xc2\x43\x10\x89\xfb\x39\x3a\x1c\x29\x51\x3a\xaa\xa5\x84\x7d\x13\xe8\xbe\x84\x76\x09\x68\xe6\x4d\xc6\x9b\x18\x63\x55\x34\x87\x5f\xc2\xba\x1a\x74\x61\x6e\x96\x1c\xaa\xaa\x90\x7d\x8b\x28\x5c\x76\x25\xbb\x44\xeb\x25\x6b\x8d\xe5\x9d", 64);
+  //ea->SetKey (header.GetRange (offset, ea->GetKeySize()));
+
+  ConstBufferPtr cbp = (ConstBufferPtr( (TrueCrypt::byte*) buffer, 32));
+  ea->SetKey (cbp);
+
+  ConstBufferPtr cbpm = (ConstBufferPtr( (TrueCrypt::byte*) buffer +32, 32));
+  //mode->SetKey (header.GetRange (offset + ea->GetKeySize(), ea->GetKeySize()));
+  mode->SetKey (cbpm);
+

Mounting the Volume

In order for TrueCrypt to reach the patched code it must first correctly decrypt a valid header. So we copy the header from an identically sized TrueCrypt volume configured with the default parameters:

$ dd of=ppp.challenge.vol if=weknowthepasswd.vol bs=512 count=1 conv=notrunc

and open ppp.challenge.vol with the patched TrueCrypt software and find the file KEY.TXT in the correctly decrypted volume.

Summary

This was a really nice challenge letting us explore TrueCrypt internals. If you think this is too complicated - you are right. You can also solve the challenge with available tools.

People involved in solving this challenge: Clemens Hlauschek, Michael Weissbacher