Monday, 23 March 2015

Codegate 2015 Good_Crypto: Advanced WEP Cracking

In the Codegate CTF there was challenge based on a wireless network capture. Since I do research on wireless security, I had to solve this one! The challenge was:

We recently intercepted some kind of encrypted traffic, can you help us recover the password?

Update: Due to a crappy javascript programmer there's one line of code missing, but I'm sure you can figure out which one

Step 1: Decrypting the Traffic

Inspection of a few packets in Wireshark hint that there is only one network, and that it uses WEP. To verify this we go to "Statistics > WLAN traffic":


We have a network called cgnetwork which uses WEP. The only client is an Apple device, which communicates with the EfmNetwo router. Let's try to decrypt the traffic using aircrack-ng:


Thanks to all the research on RC4, that wasn't hard :) Note that we got the 40-bit HEX key A4:3D:F6:F3:74. This is different from the WEP passphrase! In practice the WEP passphrase is converted to a HEX key, hence users only need to remember their passphrase. Though there is no official standard, there are quite popular algorithms to convert the WEP passphrase to a 40-bit HEX key. We will discuss this algorithm later!

Using the HEX key we can decrypt all the traffic:


This creates the file file-dec.pcap containing all the decrypted packets. The decrypted traffic can now be further inspected. There is a lot of traffic to m.reddit.com, imgur.com, and phisme.com. It was likely generated so we were able to crack the WEP encryption, since that requires many packets.

The challenge description mentioned "Due to a crappy javascript programmer there's one line of code missing, but I'm sure you can figure out which one". Unfortunately, using NetworkMiner, we were not able to find a useful JavaScript file. With Wireshark we see most of the traffic is TCP. Using "Statistics > Conversation List > TCP" we can list all TCP connections:


As expected port 80 and 443 are heavily used. More interestingly, a few connections also use the non-standard port 5000! Manual inspection reveals that this is HTTP traffic towards a custom router page. On this webpage the user can change the WEP passphrase. The page contains the following JavaScript code (I simplified it a little bit):


So we need to find the WEP passphrase, which consists of lowercase and uppercase characters. And we're given the prefix of the SHA1 of this passphrase. We tried to brute-force small passphrases using the SHA1 hash alone, but without success.


Step 2: Obtaining the Passphrase

Our goal is to find the WEP passphrase. We know its derived 40-bit HEX key, and are given the prefix of its SHA1 hash. Unfortunately we can't simply invert the algorithm which converts a passphrase to a 40-bit HEX key. The problem is that one HEX key corresponds to many passphrases. This is why we are also given the SHA1 prefix: we can iterative over all passphrases corresponding to the HEX key, until we find one with the correct hash.

First we try to generate all passphrases which lead to our HEX key. For a detailed background on how the passphrase is converted to a key, see this presentation, and this stackoverflow question. Essentially the characters of the passphrase are XORed together in four groups, resulting in a seed for a PRNG. The key is constructed by letting the PRNG output five numbers, and taking the third significant byte of each output. An illustration makes this easier to understand:


Notice that the seed of the PRNG defines the relations between the passphrase characters which must hold. So we want to recover the seed value, and to do this we need to know precisely which PRNG is being used. In this case a linear congruential generator (LCG) is used. At each iteration it outputs a 32-bit word, from which the third most significant byte is added to the key. So the most significant byte of the outputs is not used, which is equivalent to generating only 24-bits words at each iteration. Hence the most significant byte of the seed is also unused (and can be any value). Using the appropriate constants for the linear congruential generator we get:


The initial value of X is the seed. We know the third most significant byte of the first five outputs (i.e., the next five values of X after setting the seed). In a sense, each output is truncated to one byte. Hence this is actually a truncated linear congruential generator. While it's possible to efficiently break these, and to determine the seed from the truncated outputs, it would take some time to implement such attacks. Instead, we simply try all possible 2^24 seeds until we get the correct one:


This prints out the value 12766B, so the seed is 6B:76:12:??. Recall that we don't know the last byte because the most significant byte of the PRNG outputs are never used. We know that each byte of the seed is the XOR of an unknown amount of [A-Za-z] characters. By looking at the ASCII table, we can derive that the byte 0x12 can only be generated by an even number of [A-Za-z] characters. Indeed, if an uneven number of [A-Za-z] characters were used, the resulting XOR value would always have more of its high order bits set. Similarly, the other two bytes 0x6B and 0x76 correspond to an uneven number of characters. This leaks information about the length of the password (look at the 40-bit key generator illustration to realize this). In particular the length can only be 10, 18, 26, and so on. Any length higher than 10 can't be brute-forced, so we assume the length is 10.

Using the seed value, and that the length of the passphrase P is 10, we get the following relations must hold between the differen characters:


This means we only need to brute-force 7 characters out of 10. The inferred characters (according to the relations above) must also be either uppercase or lowercase characters. We calculate the SHA1 of every guess, and compare it to the hash in the JavaScript code. Using 52 parallel instances this took less than an hour to brute-force. The passphrase was:

cgwepkeyxz

My final remark is that the bytes 0x6B and 0x76 implied that there were at least two lowercase characters. In hindsight, this could have been used to guess that all other characters were lowercase too. But I don't like guessing, and we had some powerful servers that were idle anyway... ;)

Wednesday, 24 September 2014

CSAW 2014: xorcise challenge

Last weekend our CTF team participated in CSAW. One of the challenges was particularly interesting, and this blog post gives a (somewhat) detailed overview on how to solve it. The source code and binary executable were given. This write-up is also available on the awesome write-ups repository.

Length checking vulnerabilities: Part One

By inspecting the source code we see the binary is a service which listens on port 24001. New clients are handled in int process_connection(int sockfd). This function reads one single packet, which is expected to be in the following format:
struct cipher_data {
    /** Header: Unencrypted and unauthenticated */
    uint8_t length; /** Length of the bytes array */
    uint8_t key[8];
    /** Payload: Encrypted and (partly) authenticated */
    uint8_t bytes[128];
};
As the comments indicate, the header is sent unencrypted. In particular this header includes an 8-byte key (which is chosen by the client). Roughly speaking the bytes array is XORed with the key in blocks of 8 bytes. The function doing this decryption is decipher(data, output), which will be investigated in more detail later on (because it contains a vulnerability). The length field the header contains the actual size of the bytes array. This must be smaller or equal to 128. An attempt is made to assure the given length is valid:
cipher_data encrypted;
ssize_t bytes_read = recv(sockfd, (uint8_t *)&encrypted, sizeof(encrypted), 0);
if (encrypted.length > bytes_read)
    return -1;
However this check is flawed. Variable bytes_read is the length of the packet including the header, while the program will treat the length field as the size of the bytes array. This means that as an attacker we can force the length to be bigger than 128.
The second vulnerability is in decipher(data, output). This function decrypts the bytes array using the key. Somewhat simplified we have the following code:
uint32_t decipher(cipher_data *data, uint8_t *output)
{
    uint8_t buf[MAX_BLOCKS * BLOCK_SIZE];    
    uint32_t loop, block_index;

    memcpy(buf, data->bytes, sizeof(buf));

    if ((data->length / BLOCK_SIZE) > MAX_BLOCKS)
        data->length = BLOCK_SIZE * MAX_BLOCKS;

    // Block-decryption loop
    for (loop = 0; loop < data->length; loop += 8)
        for (block_index = 0; block_index < 8; ++block_index)
            buf[loop+block_index] ^= (0x8F ^ data->key[block_index]);

    memcpy(output, buf, sizeof(buf));
}
Note that data->bytes is copied to a local buffer, and this local buffer is then processed. The first if-test is an attempt to assure that data->length is not bigger than the local buffer. However this checked is flawed because in (data->length / BLOCK_SIZE) the intermediate result will be rounded down. In particular, the following value will pass the length check:
data->length = BLOCK_SIZE * MAX_BLOCKS + (BLOCK_SIZE - 1) = 135
And due to the first length check vulnerability, we know that data->length can indeed contain such values. The consequence of both vulnerabilities means that we can force the block-decryption loop to decrypt an extra block of 8-bytes. Since the local buffer is too small for this, we are capable of modifying local variables placed after the buffer. In particular we can overwrite loop and block_index.

Length checking vulnerabilities: Part Two?

Interestingly, decipher(data, output) is the only function that uses the length field in the header. All other functions are coded in such a way that knowing this length is not required. This causes another peculiar observation: when setting length to zero, no decryption takes place, and the packet is processed as-is. This observation is not required to solve the challenge though (but it does make it easier to construct packets).

Authentication and Commands

The above two vulnerabilities are sufficient to exploit the challenge. However we had some problems doing this, and instead opted for another approach. Our approach relies on additional functionality of the challenge: letting it execute commands. We can execute three commands: (1) getting the current server time; (2) reading an arbitrary file; (3) executing a system command. The latter two commands required the packet to be authenticated.

The client must provide a checksum equal to H(H(password||key) || H(data||password)) for the packet to be authenticated. Here password is a secret loaded at startup, and key and data are chosen by the client. The function H() seems to be a custom (and insecure?) hash function.

Exploitation

We use both length-check vulnerabilities to read the secret password. Once we have this, we can execute arbitrary system commands on the server, and exploitation becomes trivial.
In the decipher(data, output) function we overwrite the return address. This is done by first overwriting the local variable loop, such that the next xor-decrypt operation will point to the return address. We prevent other variables from being overwritten by XORing with zero. Recall that we can XOR 8 bytes in total, and in particular we XOR these bytes with the key in the header of the packet.
Where will we point the return address to? First observe that, because of the last mempcy call, the eax register contains a pointer to the local buffer when returning from the function. Hence the content pointed to by eax is under our control. If we now exploit the binary for interesting gadgets, we spot the following:
.text:08049290                 mov     eax, [ebp+packet]
.text:08049293                 add     eax, 8
.text:08049296                 sub     esp, 8
.text:08049299                 push    eax             ; filename
.text:0804929A                 push    [ebp+fd]        ; fd
.text:0804929D                 call    read_file
This is the code that calls read_file(sockfd, name). When returning from decipher the ebp register is restored, hence it correctly points to the local variables. However, variable packet is not yet initialized by the program, hence we can't use it. Instead we redirect code to push eax; push [ebp+fd]. Since eax points to the beginning of the buffer this code will successfully be executed. Putting the stringpassword.txt in the start of the buffer will now make the service print out the password pass123. We can now send authenticated commands and execute arbitrary shell commands. The flag was in the fileflag.txt in the same directory as password.txt (i.e. the current working directory).
Remark: the problem we encountered when directly calling system(cmd) was that it would overwrite the local buffer used in decipher (since it was saved on the stack). Recall that the pointer to this buffer was stored in eax. Luckily, in the read_file function, the stack is used in such a way so this didn't occur.

Friday, 30 May 2014

ApBleed: Heartbleed over WPA1/2 Enterprise

Tl;dr: ApBleed is my proof-of-concept to test heartbleed against wireless networks. Patches welcome.

Once the heartbleed vulnerability in OpenSSL was made public, most focused on its applicability to web servers. Other targets such as SVN servers, VPNs, etc. were also mentioned. However, there was little public discussion about the impact of heartbleed on wireless networks.

Nevertheless, it was clear heartbleed was also exploitable against WPA1/2 enterprise networks, even if it wasn't discussed as publicly as other heartbleed stories. Normally enterprise networks use one of the many EAP methods inside an SSL tunnel to authenticate users. If OpenSSL is being used, this SSL tunnel might be vulnerable to heartbleed.

Of particular interest are networks like eduroam. Eduroam is a world-wide roaming access service between research institutions. This means that I can go to a different country, see an eduroam hotspot, and connect to it using the credentials of my home institution. What's interesting is how my credentials are checked. It's done by setting up an SSL tunnel to the RADIUS server of my own institution. The eduroam network will take care of the necessary packet routing. The image below illustrates this (taken from eduroam website):


Let's assume I'm a student at lsu.edu and currently visiting an institution of utk.edu. If I now connect to an eduroam hotspot, an SSL tunnel will be set up between my device and the RADIUS server of lsu.edu (i.e. my own institution). If my credentials are valid, lsu.edu will notify utk.edu that I should be allowed on the network.

Why is this interesting for an attacker? Because the SSL tunnel is set up before user authentication, and even before you are assigned an IP. The only thing known about you is your MAC address and your "home" institution (i.e. the realm defining your home institution). Of course, an attacker can spoof both the MAC address and the home institution. The attacker only has to be within range of an eduroam hotspot, and he or she can pick any eduroam hotspot at will. The eduroam network will then forward packets to the RADIUS server the attacker specified (i.e. the realm spoofed by the attacker). This is what allows a user to directly set up an SSL tunnel with the radius server of their home institution. However, this means we can anonymously connect to any institution we want!

The guys from eduroam quickly responded to this. Hours after heartbleed got public they posted a warning on their website. One day after heartbleed, with some help from the HostApd mailing list, they had a working proof of concept to test whether institutions were vulnerable. To quote eduroam:
Following up on the heartbleed vulnerability in OpenSSL: it is confirmed that EAP-authentication in RADIUS servers is vulnerable to the attack. It is therefore extremely important to upgrade OpenSSL and restart RADIUS services as soon as possible.
The attack is feasible from any public eduroam hotspot, not just your RADIUS peers.
Federation-level admins in Europe will receive notice from the eduroam OT with a list of vulnerable realms. [..]
While the tools are not publicly available for security reasons, we're providing the service of scanning a realm list for global federation admins on their request. [..]
Though I focus on eduroam, every networking using enterprise authentication with an EAP method inside an SSL tunnel might be vulnerable to heartbleed. Nevertheless, eduroam is more interesting than your ordinary network, because it allows you to anonymously connect to any institution which has joined the eduroam federation.

Few public proof-of-concepts were available (if any at all). I can think of three reasons for this. First, not everyone was aware enterprise authentication was vulnerable to heartbleed. Second, it's a little bit harder to make a working proof-of-concept against wireless networks. Third reason might have to do with the impact of public exploits. Because authentication (and hence the SSL tunnel) takes place before getting access to a network, it's not possible to consult a certificate revocation list (CRL) or query the certificate status online (with OCSP) [eduroam]. So revoking certificates is not possible.

However, recently proof-of-concepts are being released. So it's time to release my own proof-of-concept as well :) I modified wpa_supplicant so, once connected to an AP and ready to talk to the radius server, it opens a local socket. This means you can now connect to this socket and pretend it's the RADIUS server. The big advantage of this approach is that you can use any existing heartbleed tool to test the radius server. Simply let the heartbleed tool connect to the socket created by my proof-of-concept. The code has not been tested thoroughly, but worked in all my small experiments. Get a copy of my PoC (called ApBleed) at github. Patches are welcome!

Some example outputs of running my tool:


Using existing heartbleed tools to connect to the radius server: