Thursday 15 October 2015

Advanced WiFi Attacks Using Commodity Hardware

The WiFi protocol assumes all clients behaves fairly. This means a station will give others a chance to transmit packets, before it starts transmitting itself. It's known that with a software defined radio such as a USRP, a user can implement the WiFi protocol themselves and not follow these rules.

 A desktop computer, antenna, two USRPs on top of each other, and a signal analyzer.

An attacker can also use this equipment to create a constant jammer, which continuously transmits noise, and makes the channel completely unusable. In principle an attacker could also turn on a badly shielded microwave to jam the channel :-) However, that doesn't give the attacker control over which frequency is jammed, bandwidth of the emitted noise, nor the emitted noise pattern.

It's even possible to implement a more sophisticated selective jammer. Such a jammer is capable of only jamming specific packets (e.g. only packets sent by a certain device). While it's already known this is possible using expensive hardware such as USRPs, we found that even cheap WiFi dongles can be used to implement all these attacks. It's especially surprising that a selective jammer can be implemented on a cheap WiFi dongle, since it must be fast enough to detect and subsequently jam a packet.

Our attacks were first published at ACSAC 2014 and subsequently demonstrated at BruCON 2015. Additionally our code is available on github! You can also view my presentation at BruCON online.

Selfish Behavior

When a station wants to transmit a packet, but the channel is already in use by another device, it first waits until this transmission is done. Then it waits a random period (called the backoff period), and if no one else starts transmitting during this period, the station may send the packet. We found that it's easy to disable the random backoff period on commodity devices, meaning it will instantly transmit packets. More interestingly, we found that even the original driver and firmware for our device incorrectly calculated the backoff period, giving itself an unfair advantage over other stations! It turns out that many devices do this to gain an unfair advantage:
Over six Wi-Fi cards, neither one performs as expected. In some cases, implementation issues seem to affect the proper card operation. In other cases, manufacturers rely on backoff parameters different from the standard specification, this perhaps being done on purpose to provide an unfair advantage. [Minimized conclusion from this paper]
This raises the question what would happen if there are two stations that both behave selfishly by disabling the backoff period. In other words, what happens if two selfish stations instantly transmit all packets they have queued? You may think that the packets of both selfish stations will collide, and as a result both are lost in the collision. It turns out this is not the case! Due to the capture effect, the packet having the highest signal quality and lowest bitrate will get decoded properly. You can compare this to receiving two radio stations on the same frequency, where generally one station will "win the collision" (see this demo video). This means selfish stations will abuse the capture effect, and reduce their bitrate, in order to win the collision (and have their packet decoded correctly by the receiver). Surprisingly, we now get that selfish clients wanting to maximize their throughput, will reduce their bitrate!

One of our WiFi dongles attached to a Raspberry Pi

There is a research prototype called DOMINO which can detect and punish this type of selfish behavior. Unfortunately we discovered a critical flaw in this system. The problem is that this system bases some of its decisions on unauthenticated packets. As an attacker we can abuse this by forging packets which appear to be sent by a different station. Hence we can make DOMINO believe this client is acting selfishly, meaning it will be punished and thrown off the network. Moral of the story: only base your decisions on authenticated or hard-to-forge data.

Constant Jammer

Our WiFi dongles can also be used to implement a constant jammer. The idea is straightforward: make the radio instantly transmit packets (even if someone else is transmitting), and then send an infinite amount of packets without interruption. The second part is tedious, since we can't queue many packets due to the limited amount of memory available in the WiFi dongle. However, we can simulate an infinite amount of packets. The packets queued for transmission are stored in a linked list. By turning this queue into a circular list we can simulate an infinite amount of packets.

What's interesting here is that in principle the jammer is constantly transmitting valid WiFi packets. However, because they are sent so fast after one another, other WiFi devices are unable to detect these packets. In other words, other WiFi devices operating in monitor mode only see noise, and will not detect/show and show any WiFi traffic.

Selective Jammer

Arguably the most impressive result is that our cheap dongle can be used to implement a selective (also called reactive) jammer. Such a jammer decodes the header of a packet still in the air, and based on information in the header, decides whether to jam the remaining content or not. This is not an easy feat to accomplish. The selective jammer must be fast enough to decode the header, make the decision to jam the packet, put the antenna in transmit mode, and finally jam the packet. All this needs to be done in just a few microseconds (an average WiFi packet takes ~200 microseconds to transmit)!

Jamming the end of the packet is easy, simply inject a packet like we did for the continuous jammer. But there is no support or API to be notified when a packet is in the progress of being received. How do we get around this? The important realization is that there are two chips inside our WiFi device. The first one is the radio which processes the incoming physical signal, and uses Direct Memory Access (DMA) to write the packet to memory. The second chip is the main CPU which is responsible for communicating with the host over USB and controlling the radio chip. Hence we can use the main CPU to constantly monitor the memory where the packet will be saved. Once we detect that the radio chip is writing bytes to this memory location, we know a frame is being received:

When the memory is modified, we know a frame is being received.

With this clever trick we can detect when a frame is being received. Our jammer then reads the MAC address(es) in the header, and compares it to the MAC address of the station we are targeting. If they match, the remaining content of the packet is jammed. This will cause the CRC (called the ICV in WiFi) of the packet to be wrong, meaning the packet will be dropped by the receiver.

The code of the reactive jammer is public, feel free to play around with it (against a test network).

Channel-Based Man-in-the-Middle Attack

As an example, we show how these low-layer attacks can be used to reliably manipulate encrypted traffic. Note that our goal is not to decrypt traffic. Instead, we want to be able to reliably drop, modify, and inject packets. This ability is typically required in order to launch actual (cryptographic) attacks against an higher-layer protocol. Previously when targeting wireless traffic, it was not clear how to do this: there were no known methods to obtain a (reliable) man-in-the-middle position between a client and access point if encryption is used.

In order to intercept (not decrypt) all traffic of an encrypted wireless network, we cannot simply create a rogue Access Point (AP) with a different MAC address. When a client connects to the access point, not only are the credentials verified (e.g. shared password), but also the MAC addresses of the client and access point. Hence the client and AP will detect that an attacker was forwarding packets under different MAC addresses. Setting up a rouge AP with the same mac address as the real AP is futile: the client will simply directly communicate with the real AP (unless they are out of range of each other).

Our solution is to clone the access point on a different channel, but with the same MAC address as the real access point. We forward all frames to the real AP. In other words, we forward packets between both channels. Using the constant jammer we force clients to switch to the channel where our rogue AP is located. Since we did not modify the MAC address of the AP, and also didn't modify the MAC address of the client, the client will successfully connect to the (rogue) AP. We now have a (channel-based) man-in-the-middle position, allowing reliable manipulation of encrypted traffic.

Demonstration of the Channel-Based MiTM attack against WPA-TKIP

The channel-based man-in-the-middle attack can be used to break WPA-TKIP (people commonly, but incorrectly, refer to TKIP as WPA, and AES-CCMP as WPA2). The TKIP protocol was supposed to be a temporarily replacement of WEP, designed to run on old hardware. Unfortunately, for backwards compatibility, many networks still support both TKIP and the newer AES-CCMP. If both these protocols are supported, the older TKIP protocol is used to encrypt all broadcast packets. Without going into detail, the channel-based man-in-the-middle allows us to apply existing TKIP attacks to broadcast packets. Hence you should configure your network so only (AES-)CCMP is enabled!

Wednesday 23 September 2015

Solving CSAW challenge Wyvern (rev-500) using afl-fuzz

We're given a Linux binary which asks for input:

 |    Welcome Hero       |

 [!] Quest: there is a dragon prowling the domain.
     brute strength and magic is our only hope. Test your skill.

 Enter the dragon's secret: xxxx_find_secret

 [-] You have failed. The dragon's power, speed and intelligence

     was greater.

Opening the binary in IDA shows that the main() function prints the above output. The input is processed by start_quest(std::string). If this function returns 0x1337 the level is solved.

Looking at the start_quest function, it first pushes 28 integers in a vector, does some "weird" if-tests, and finally calls sanitize_input. Interestingly, this function again contains a lot of similar and weird if-tests of the form:

    if ( y4 < 10 || (((_BYTE)x3 - 1) * (_BYTE)x3 & 1) == 0 )

It appears this is some kind of obfuscation. At this point I guessed (and hoped) that the input would be checked character by character. This means that if we guess a character correctly, the program will execute a (new) different path in the program. This is ideal for afl-fuzz, a fuzzer that detects "interesting fuzzing candidates" based on which path a program executes.

Compiling afl-fuzz with afl-qemu-trace support allows us to fuzz binaries without access to source code:

    $ mkdir indir
    $ echo "test" > indir/test1.bin
    $ afl-fuzz -Q -i indir -o sync_dir -M fuzzer01 ./wyvern
    $ afl-fuzz -Q -i indir -o sync_dir -S fuzzer02 ./wyvern
    $ afl-fuzz -Q -i indir -o sync_dir -S fuzzer07 ./wyvern  

While the fuzzer will not find crashes, it does find inputs that trigger new execution paths in the program! It took roughly half an hour of fuzzing on 7 cores to get some interesting files:

$ xxd id\:000006\,src\:000005\,op\:flip1\,pos\:4
00: 6472 3467 3034 3434 3434 3434 34cd 617c  dr4g044444444.a|
10: 6166 e803 666c 167c 6170 6116 47         af..fl.|apa.G

All generated files are 28 bytes long. That's the number of integers pushed into the vector! And it seems it must start with dr4g0n. The integers being put into the vector are:

    array = [0x64, 0xD6, 0x10A, 0x171, 0x1A1, 0x20F, 0x26E,
       0x2DD, 0x34F, 0x3AE, 0x41E, 0x452, 0x4C6, 0x538,
       0x5A1, 0x604, 0x635, 0x696, 0x704, 0x763, 0x7CC,
       0x840, 0x875, 0x8D4, 0x920, 0x96C, 0x9C2, 0xA0F]

What's interesting is that 0x64 is the ascii value of "d". Does this vector somehow encode the flag? We know the second letter is "r" with value 0x72. How can we get this value? By subtracting the first integer, 0x64, from the subsequent one: 0xD6 - 0x64 = 0x72! The solution is:

    flag = ""
    base = 0
    for num in array:
        flag += chr(num - base)
        base = num

    print flag

Flag: dr4g0n_or_p4tric1an_it5_LLVM

In hindsight it would've also been possible to use something like Intel PIN to count the number of executed instructions. The advantage of afl-fuzz is that it requires zero configuration: just give it some random start value and it works.

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,, and 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:


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... ;)