Content Security Policy – Trends and Challenges

July 14th, 2014 No comments

In December 2012, I was curious who is using Content Security Policy, and how are they using it?

Content Security Policy (CSP) can help websites to get rid of most forms of content injection attacks. As it is standardized and supported by major browsers, we expected websites to implement it. To us, the benefits of CSP seemed obvious.

However, as we started to look into the CSP headers of websites, we noticed that few of them actually used it. To get a better overview we started crawling the Alexa Top 1M every week. What started out as a just-for-fun project escalated to a data collection of over 100GB of HTTP header information alone. We are publishing the results in our (Michael Weissbacher, Tobias Lauinger, William Robertson) paper Why is CSP Failing? Trends and Challenges in CSP Adoption at RAID 2014.

We investigated three aspects of CSP and its adoption. First, we looked into who is using CSP and how it is deployed. Second, we used report-only mode to devise rules for some of our websites. We tried to verify whether this is a viable approach. And third, we looked into generating content security policies for third-party websites through crawling, to find obstacles that prevent wider deployment.

CSP headers in comparison to other security relevant headers

CSP headers in comparison to other security relevant headers

We have found that CSP adoption significantly lags behind other web security mechanisms and that, even when it has been adopted by a site, it is often deployed in a way that negates its theoretical benefits for preventing content injection and data exfiltration attacks. While more popular websites are more likely to use it, only 1% of the 100 most popular websites use it on their front page.


To summarize our findings:

  • Out of the few websites using CSP, the policies in use did not add much protection, marginalizing the possible benefits.
  • The structure of sites, and in particular integration of ad networks, can make deployment of CSP harder.
  • CSP can cause unexpected behavior with Chrome extensions, as we detail below.
  • The project resulted in fixes to phpMyAdmin, Facebook and the GitHub blog.

In our paper, we suggest several avenues for enhancing CSP to ease its adoption. We also release an open source CSP parsing and manipulation library.

Below, we detail on some topics that did not fit into the paper, including bugs that we reported to impacted vendors.

Chrome Extensions

Chrome enforces CSP sent by websites on its extensions. This seems well known, but comes with a couple of side effects. The implications are that CSP from websites can break functionality of extensions, intentionally or unintentionally. Other than that, it makes devising CSP rules based on report-only mode very cumbersome, due to lots of bogus reports. Enforcing rules on extensions seems surprising, especially since they can request permission to modify HTTP headers and whitelist themselves. In fact, we found one such extension that modifies CSP headers in flight.

Recovering granularity of CSP reports

While older versions of Firefox will report specifically whether an eval or inline violation occurred, newer versions of any browser won’t. We provide a work-around to detect such errors in the paper, this involves sending multiple headers and post-processing the reports.

Facebook

With Facebook, we noticed that headers (including CSP) were generated based on the user agent. This has some advantages, e.g., sending less header data to browsers that don’t support certain features. Also, Firefox and Chrome were sent different CSP headers (the difference being, the Skype extension was whitelisted for Chrome.) We also noticed that for some browser versions, no CSP rules were sent out. The likely reason is that CSP handling in some browser versions is buggy. For example, Chrome enforces eval() even in report-only mode in some versions. However, due to misconfiguration, CSP was only served to browser versions before the bugs were introduced, and none after. As a result, CSP was only in use for a fraction of browsers that in fact support it. After we informed them of this, Facebook quickly fixed the issue. Now, CSP is being served to a wider audience of browsers than before. Also, we were added to the Whitehat “Thanks” list.

phpMyAdmin

We found that phpMyAdmin, which serves CSP rules by default, had a broken configuration on it’s demo page. The setup prevented loading of Google Analytics code. This turned out to be interesting, as the script was whitelisted in the default-src directive, but script-src was also specified and less permissive. Those two are not considered together, and the more specific script-src directive overrode default-src. Hence, including the Google analytics code was not allowed and resulted in an error. We pointed out the issue and it resulted in a little commit.

GitHub

We used several sites that deploy CSP as benchmark to test our tool for devising CSP rules. With GitHub, we noticed that our tool came up with more directives than the site itself. After investigating, we found that one of the sites on their blog was causing violations with the original rules, as it tried to include third party images. This was interesting, as any site which specifies a report-uri would have caught this, but GitHub doesn’t use the feature. While this caused no security issue, it stopped the blog post from working as intended. With report-uri enabled that mistake would have popped up in the logs and could have been fixed instantly. We think this highlights how important usage of the report-uri is. In summary, this was more of an interesting observation about report-uri to us than a problem on their side.

Categories: CSP, JavaScript, Security Tags:

CSAW CTF Qualifications 2012 – Networking 300

October 1st, 2012 No comments

The 2012 Qualification round for CSAW CTF was fun. I was playing with the Northeastern Seclab hacking group – PTHC. One of the more interesting challenges was networking 300.

As input we received a file called “dongle.pcap”, no further description.

The first thing to do with pcaps is to load them in wireshark. The type for most packets is URB_Interrupt or URB_Control (URB is a USB request block). Most of the packets don’t look interesting – by browsing we found packet 67 which contains the following string: “Teensy Keyboard/Mouse/Joystick”, this made us assume that we have to deal with recovering key presses.

Some quick googling lead us to this website, we downloaded the source files and inspected the code to analyze the protocol. We figured out that the packets we are interested in are pretty specific, they should be:

– 72 bytes long
– the 10th byte is \x01
– the 12th one is \x1a
– the 66th byte should be non-zero
– also we only care about the last 8 bytes and can disregard the rest

This sounds like a perfect job for scapy:

import binascii
from scapy.all import *
dongle = rdpcap("dongle.pcap")

for d in dongle:
    sd = str(d)
    if len(sd) == 0x48 and sd[9] == '\x01' and sd[11] == '\x1a':
        x = sd[len(sd) -8:]
        print binascii.hexlify(x)

This leaves us with 1338 packets, the last four bytes are all \x00. We inspect the file usb_keyboard_debug.h from the keyboard’s source file and can see the key mapping. “A” = 4 etc. We created a mapping keycode-output and prepended it to our script so we would see the keyboards behavior. By inspecting the pcap we found that there were no ALT or STRG key presses, only shift and only for a couple of keys (3). For the sake of simplicity we decided just to check for these three cases in additional if clauses without making a generalized upper-case function. What we get from running the updated program:

XTERM -GEOMETRY 12X1+0+0
ECHO K
RXTERM -GEOMETRY 12X1+75+0
ECHO E
RXTERM -GEOMETRY 12X1+150+0
ECHO Y
RXTERM -GEOMETRY 12X1+225+0
ECHO {
RXTERM -GEOMETRY 12X1+300+0
ECHO C
RXTERM -GEOMETRY 12X1+375+0
ECHO 4
RXTERM -GEOMETRY 12X1+450+0
ECHO 8
RXTERM -GEOMETRY 12X1+525+0
ECHO B
RXTERM -GEOMETRY 12X1+600+0
ECHO A
RXTERM -GEOMETRY 12X1+675+0
ECHO 9
RXTERM -GEOMETRY 12X1+0+40
ECHO 9
RXTERM -GEOMETRY 12X1+75+40
ECHO 3
RXTERM -GEOMETRY 12X1+150+40
ECHO D
RXTERM -GEOMETRY 12X1+225+40
ECHO 3
RXTERM -GEOMETRY 12X1+300+40
ECHO 5
RXTERM -GEOMETRY 12X1+450+40
ECHO C
RXTERM -GEOMETRY 12X1+375+40
ECHO 3
RXTERM -GEOMETRY 12X1+525+40
ECHO A
RXTERM -GEOMETRY 12X1+600+40
ECHO }

The string “KEY{…}” made us assume we finished, but the scoreboard disagreed. It took us a while to figure out that we also have to take the RXTERM lines into account – the last two numbers (separated by “+” are the coordinates). That was the last part of the puzzle and we got the points.

The following program is the cleaned up, final version that will parse the pcap file, pull out the key presses, write the keys into a matrix with the corresponding coordinates and print them in order.

from scapy.all import *
import binascii

keys = {}
keys[4]='A'
keys[5]='B'
keys[6]='C'
keys[7]='D'
keys[8]='E'
keys[9]='F'
keys[10]='G'
keys[11]='H'
keys[12]='I'
keys[13]='J'
keys[14]='K'
keys[15]='L'
keys[16]='M'
keys[17]='N'
keys[18]='O'
keys[19]='P'
keys[20]='Q'
keys[21]='R'
keys[22]='S'
keys[23]='T'
keys[24]='U'
keys[25]='V'
keys[26]='W'
keys[27]='X'
keys[28]='Y'
keys[29]='Z'
keys[30]='1'
keys[31]='2'
keys[32]='3'
keys[33]='4'
keys[34]='5'
keys[35]='6'
keys[36]='7'
keys[37]='8'
keys[38]='9'
keys[39]='0'
keys[41]='ESC'
keys[43]='TAB'
keys[45]='-'
keys[46]='EQUAL'
keys[47]='LEFT_BRACE'
keys[48]='RIGHT_BRACE'
keys[49]='BACKSLASH'
keys[50]='NUMBER'
keys[51]='SEMICOLON'
keys[52]='QUOTE'
keys[53]='TILDE'
keys[54]='COMMA'
keys[55]='PERIOD'
keys[56]='SLASH'
keys[57]='CAPS_LOCK'
keys[58]='F1'
keys[59]='F2'
keys[60]='F3'
keys[61]='F4'
keys[62]='F5'
keys[63]='F6'
keys[64]='F7'
keys[65]='F8'
keys[66]='F9'
keys[67]='F10'
keys[68]='F11'
keys[69]='F12'
keys[72]='PAUSE'
keys[74]='HOME'
keys[75]='PAGE_UP'
keys[76]='DELETE'
keys[77]='END'
keys[79]='RIGHT'
keys[80]='LEFT'
keys[81]='DOWN'
keys[82]='UP'
keys[83]='NUM_LOCK'
keys[89]='KEYPAD_1'
keys[90]='KEYPAD_2'
keys[44]=' '
keys[40]='ENTER'

dongle = rdpcap("dongle.pcap")

buf = {}
buf[1] = ""

for d in dongle:
    sd = str(d)
    if len(sd) == 0x48 and sd[9] == '\x01' and sd[11] == '\x1a' and sd[0x42] != '\x00':
        x = sd[len(sd) -8:]
        # only these three keys get used with shift
        if x[0] == '\x02':
            if keys[ord(x[2])] == "EQUAL":
                key = "+"
            elif keys[ord(x[2])] == "RIGHT_BRACE":
                key = "}"
            elif keys[ord(x[2])] == "LEFT_BRACE":
                key = "{"
        else:
            key = keys[ord(x[2])]

        if key == "ENTER":
            print buf[len(buf)]
            buf[len(buf)+1]=""
            key = "\n"
        else:
            buf[len(buf)] += key

# putting keys on their corresponding coordinates
matrix = {}
for i in range(1,len(buf),2):
    (a,b,c) = (buf[i]).split("+")
    if int(c) not in matrix.keys():
        matrix[int(c)] = {}
    # look ahead for one line - the echo command
    matrix[int(c)][int(b)] = buf[i+1][-1:]

# print pressed keys in order
for x in sorted(matrix.keys()):
    for y in sorted(matrix[x].keys()):
        print "%03d-%03d-%s" % (x,y,matrix[x][y])

PlaidCTF 2012 Challenge Torrent – Practical Packets writeup (200 points)

April 30th, 2012 No comments

This weekend PPP organized its second PlaidCTF which was a lot of fun. Below is a quick writeup for the bittorrent forensics challenge.

Description:

It turns out that robots, like humans, are cheap and do not like paying for their movies and music. We were able to intercept some torrent downloads but are unsure what the file being downloaded was. Can you figure it out?

Provided was a file torrent.pcap, we used tshark (the command line tool for wireshark) to extract data from the packet capture. The only interesting data points are bittorrent.piece, from those we only need index, begin and data. By printing them in this order we can run a simple sort to make sure the file contents are in order.

Next we strip everything but the data field and the colons. Finally we use translate and sed to turn the hex representation into binary. After running the below script we have a file binout.


tshark -r torrent.pcap -R 'bittorrent.piece.data and ip.dst_host == 128.237.112.101' -T fields -e bittorrent.piece.index -e bittorrent.piece.begin -e bittorrent.piece.length -e bittorrent.piece.data -E separator=+ | sort | sed -re 's!.*\+!!' | sed -re 's!:!!g' | echo -n -e $(tr -d '[:space:]' | sed 's/../\\x&/g') > binout

By using the file command and consequently unpacking we figure out its a bz2-ed tar file. Inside we find the files key.mp3 and key.txt. key.txt contains “t0renz0_v0n_m4tt3rh0rn”, which turned out to be the valid key. We couldn’t extract any hidden information from key.mp3 :-)

Note: if you are trying to reconstruct a file from a bittorrent pcap you might want to check for retransmits, missing indices, multiple files in one capture etc. It would make sense not to strip the headers directly with sed but keep them and run some script to analyze them.

Categories: CTF, Linux, Security Tags: , , , , ,

Finding differences in JavaScript programs with the help of parse trees

February 18th, 2012 No comments

I’d like to present a quick and dirty solution to comparing two JavaScript programs that can’t be compared with a simple diff. This can be useful to check whether programs are mostly equal and can help in figuring out where these differences are. The result will be achieved by performing a makeshift parse tree comparison.

Tools that will be used:

  • Google Closure Compiler
  • a small Perl script
  • vimdiff

First, Google’s Closure Compiler will be used to generate the parse trees.


$ java -jar compiler.jar --print_tree --js original.js > original.tree.txt
$ java -jar compiler.jar --print_tree --js modified.js > modified.tree.txt

The sourcename attribute should be stripped from the resulting file. Actually for most comparisons I did it was sufficient to keep only the first word of each line. This script can be used to print only the first word, but preserve heading whitespace (whitespace should be kept to keep a better overview later):


# treestrip.pl
while (<>) {
m/(^\W*\w+)\W*/;
print $1."\n";
}

Next:

$ perl treestrip.pl < original.tree.txt > original.stripped.tree.txt
$ perl treestrip.pl < modified.tree.txt > modified.stripped.tree.txt

Finally the stripped down parse trees can be compared with vimdiff. The iwhite option makes vimdiff ignore differences in whitespace:

$ vimdiff -c 'set diffopt+=iwhite' original.stripped.tree.txt modified.stripped.tree.txt

Suspicious blocks can be traced back to the parse tree before it was stripped (same line number). From there the surrounding function or variable names will lead back to the code in the JavaScript file.

Categories: JavaScript, obfuscation Tags:

Connecting seamlessly with ssh through intermediate hosts

December 21st, 2011 No comments

Having machines in a network that is only reachable through an intermediate machine (e.g. a firewall) can make using ssh less comfortable. When using VPN or changing the network layout is not an option – the ssh ProxyCommand option can help. The result is being able to connect to hosts with one command through the intermediate machine. This also enables using rsync or versioning systems over ssh which otherwise wouldn’t work directly.

The intermediate machine is named “intermediate” and the target machine (which is in the not directly reachable network) “target”. The ~/.ssh/config entries should look like this:

Host intermediate
Hostname 192.168.1.1
....

Host target
ProxyCommand ssh -e none intermediate exec nc -w 1000 %h %p
....

So when you execute:

$ ssh target

An ssh connection to “intermediate” is opened. netcat is started on “intermediate” to forward the ssh session to “target”. nc -w sets a connection timeout, this should make sure that “intermediate” doesn’t run abandoned nc processes. ssh -e none disables escape chars, they are not useful in this case and can only cause problems.

Categories: Linux, ssh Tags:

iCTF 2011 challenge 15 writeup (150 points)

December 20th, 2011 No comments

One of my iCTF challenges was a simple JavaScript obfuscation, a backup of the code is available here. What happens is obvious, window.alert is triggered with the message “why?”. “Why” is less obvious since the code was encoded with jjencode. There are no other visible hints.

To further look into window.alert, we can overwrite the function:

window.alert = function(e) { console.log(JSON.stringify(e)); };

After re-running the code we see that window.alert is not being called with a String as argument, but with an object which contains the attribute:

{"secret":"Angelina Jolie's only good movie, in leet speak, reverse is the key"}

The solution is obviously: Hackers

FYI: Before the obfuscation the code looked like this:

var obj = { };
obj.secret = "Angelina Jolie's only good movie, in leet speak, reverse is the key";
obj.toString = function(e) { return "why?"; };
obj.toSource = function(e) { return "function toSource() {\n" +
" [native code]\n" +
"}\n"
};
window.alert(obj);

Categories: CTF, JavaScript, obfuscation, Security Tags: ,

Personalizing shell accounts that are used by multiple users

December 19th, 2011 No comments

Problem description

Although it is generally discouraged to share a shell account with multiple users, sometimes its necessary. When I recently had to share a login I wanted to recreate my personalized environment, but then again not mess with other peoples environment. Also I wanted to preferably create something they could use for themselves without messing in turn my environment up (for example by configuring emacs as the default text editor). I wanted to have my own .bash_history file, load my own .vimrc etc. I used Debian 6.0 with current ssh/sshd versions.

Overview

What I did was exporting an LC_* environment variable on the client machine; those get forwarded by the ssh client to the server. On the server I would check in the .bashrc file whether the variable was set, if it was set I would load the custom configuration files. Any of the users who share the account are then able to configure the server environment for themselves.

Implementation

I added this line to ~/.bashrc

.....
export LC_USER=michael
.....

Don’t forget to source the .bashrc file after it was edited, otherwise it will have no effect in the active session, this can be done with

$ source ~/.bashrc

You should have SendEnv set to forward the LC_* variables (this is usually enabled). This can be done in either the global ssh config /etc/ssh/ssh_config or the user ssh config: ~/.ssh/config

....
SendEnv LANG LC_*
....

On the server where you want to connect to, the file /etc/ssh/sshd_config should have the following option enabled (this is often enabled by default)

....
AcceptEnv LANG LC_*
....

To verify, ssh into the target machine and try this, the result should be similar. If you get no response check whether the variable was even exported locally (use the same command).

$ env | grep LC_
LC_USER=michael

Once this is done, upload your personalized .bashrc file to the remote server into ~/.yourusername/.bashrc .

Finally edit the ~/.bashrc file on the server so it sources the new custom file:


if [ `expr match "$LC_USER" '^[a-zA-Z0-9]\+$'` -gt 0 ] ;
then
echo "Welcome ${LC_USER}"
source ~/.${LC_USER}/.bashrc
fi

To keep a personal .bash_history and .vimrc file, put the following into the personal .bashrc file (don’t forget to upload your .vimrc file into ~/.${LC_USER}).

HISTFILE=./.bash_history
alias vim='vim -u ~/.${LC_USER}/.vimrc'

Conclusion

This proved to be a noninvasive and reliable way to share accounts with custom configurations. Other ways to solve this problem might have been the usage of IP addresses. I looked into this first, but then decided that its not very good since connecting through intermediate machines would render discrimination of users impossible.

Categories: bash, Debian, Linux, ssh Tags:

iCTF challenge animation

December 16th, 2011 No comments

Now that the iCTF is over I wanted to get a better overview over how the challenges got solved. I made a little animation that can be found here.

Categories: CTF Tags:

PlaidCTF Writeup: Fun with Firewire

May 17th, 2011 No comments

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: http://citp.princeton.edu/pub/coldboot.pdf.

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 by Jacob Applebaum: AESKeyFinder http://citp.princeton.edu/memory/code/.

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);
+
}
else
{

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: http://www.lestutosdenico.com/tutos-de-nico/write-up-fun-with-firewire-plaidctf 

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

Categories: CTF, Security Tags: , , ,