Outbreak Labs

I can do anything I want to. And so can you.

Decrypting WEP using C#

The WPA2 decryption from the earlier posts was challenging to implement. I knew that WEP used a simple RC4 decryption, and didn't think its implementation would warrant a post. And while it's true that it was trivial to implement the decryption, less so was performing the CRC validation. The display of the WEP ICV in Wireshark was very misleading here, because it is *not* the expected CRC. As it turns out, that value is also encrypted, so it must be decrypted along with the data. Here's the implementation:


public public static bool TryDecryptWEP(IEEE_802_11<Generic> encryptedPacket, byte[] key, out IPacket decrypted)
        {
            
            if (key.Length != 5 && key.Length != 13 && key.Length != 16 && key.Length != 29 && key.Length != 61)
            {                
                decrypted = null;
                return false;
            }

            if (encryptedPacket.FrameType != (int)FrameTypes.Data || !encryptedPacket.IsWep)
            {
                decrypted = null;
                return false;
            }

            var keyWithIV = new byte[key.Length + 3];
            Array.Copy(encryptedPacket.CCMP_WEP_Data, keyWithIV, 3);
            Array.Copy(key, 0, keyWithIV, 3, key.Length);

            var rc4 = new RC4(keyWithIV);
            var encryptedPayload = encryptedPacket.Payload.Concat(BitConverter.GetBytes(ByteOrder.NetworkToHostOrder(encryptedPacket.WEP_ICV))).ToArray();
            var dec = new byte[encryptedPayload.Length];
            rc4.decrypt(encryptedPayload, encryptedPayload.Length, dec);

            var expectedCRC = BitConverter.ToUInt32(dec, dec.Length - 4);
            dec = dec.Take(dec.Length - 4).ToArray();


            if (Crc32.Crc32Algorithm.Compute(dec) == expectedCRC)
            {
                var decryptedBytes = encryptedPacket.Take(26).Concat(dec).ToArray();
                decryptedBytes[1] &= 191; //Everything except protected
                decrypted = Generic.ParseAsNew(decryptedBytes, IEEE_802_11.ParseAsNew);
                return true;
            }
            else
            {
                decrypted = null;
                return false;
            }
        }
First, some simple sanity checks - a valid key length, and a data packet encrypted under WEP. Next, we append the key to the IV from the packet. The WEP_ICV is then appended to the data, and decrypted using the IV+Key. The result is our data + expected CRC. We slice off the expected CRC, then compute a standard CRC32 checksum over the allegedly decrypted data. If the calculated CRC matches the expected CRC, then the decryption was successful. As with the WPA2 decryption implementation, we have to fudge the packet header a bit, flipping the protected bit to 0 and excluding the encryption information. PacketGremlin can now handle WEP and WPA2/AES, only WPA/TKIP remains.

WiFi Decryption resources and test data

I thought I'd share some of the resources I used in the recent WiFi decryption series of posts.

First, much time was saved by my friend Daniel Smullen, who put together a set of clean captures of WEP, WPA, and WPA2 for me. The captures and the associated passwords are available for download here: MyTestWAP.zip (151.2KB)

The Aircrack-ng project was invaluable; it likely would have taken many months to accomplish what I did, were it not for the source code of this tool suite being available.

Finally, here are some miscellaneous resources that were useful in understanding the various protocols and encryption schemes involved:

http://www.willhackforsushi.com/papers/80211_Pocket_Reference_Guide.pdf
http://svn.fonosfera.org/fon-ng/trunk/openwrt/package/broadcom-wl/src/driver/proto/eapol.h
http://www.xirrus.com/cdn/pdf/wifi-demystified/documents_posters_encryption_plotter
http://my.safaribooksonline.com/book/networking/wireless/0596001835/802dot11-framing-in-detail/wireless802dot11-chp-4-sect-3
https://chromium.googlesource.com/chromiumos/third_party/hostap/+/0.12.369.B/wlantest/ccmp.c
http://security.stackexchange.com/questions/46670/does-using-wpa2-enterprise-just-change-the-attack-model-vs-wpa2-psk
http://www.seas.gwu.edu/~cheng/388/LecNotes/CCMP.pdf
http://stackoverflow.com/questions/12018920/wpa-handshake-with-python-hashing-difficulties
http://stackoverflow.com/questions/19144775/4-way-handshake-simulation-in-c-sharp?rq=1
http://stackoverflow.com/questions/2465690/pbkdf2-hmac-sha1
http://hashcat.net/forum/thread-1745.html

Decrypting CCMP using C#

The final step in the WiFi decryption saga is to deal with Counter Mode Cipher Block Chaining Message Authentication Code Protocol, commonly abbreviated to CCMP. It is the encryption standard used by WPA2, and requires everything done up to this point to generate the temporal key used as input. Here is the code.

public static bool TryDecryptCCMP(IEEE_802_11 encryptedPacket, byte[] temporalKey, bool strip80211Header, out IPacket decrypted)
        {
            if (temporalKey.Length != 16) throw new ArgumentException("temporalKey must be 16 bytes");
            var decryptedBytes = encryptedPacket.ToArray();
            int z, data_len, blocks, last, offset;
            bool is_a4, is_qos;

            var B0 = new byte[16];
            var B = new byte[16];
            var MIC = new byte[16];
            var PacketNumber = new byte[6];
            var AdditionalAuthData = new byte[32];

            is_a4 = (decryptedBytes[1] & 3) == 3;
            is_qos = (decryptedBytes[0] & 0x8C) == 0x88;
            z = 24 + 6 * (is_a4 ? 1 : 0);
            z += 2 * (is_qos ? 1 : 0);

            PacketNumber[0] = decryptedBytes[z + 7];
            PacketNumber[1] = decryptedBytes[z + 6];
            PacketNumber[2] = decryptedBytes[z + 5];
            PacketNumber[3] = decryptedBytes[z + 4];
            PacketNumber[4] = decryptedBytes[z + 1];
            PacketNumber[5] = decryptedBytes[z + 0];

            data_len = decryptedBytes.Length - z - 8 - 8;

            B0[0] = 0x59;
            B0[1] = 0;
            Array.Copy(decryptedBytes, 10, B0, 2, 6);
            Array.Copy(PacketNumber, 0, B0, 8, 6);
            B0[14] = (byte)((data_len >> 8) & 0xFF);
            B0[15] = (byte)(data_len & 0xFF);

            AdditionalAuthData[2] = (byte)(decryptedBytes[0] & 0x8F);
            AdditionalAuthData[3] = (byte)(decryptedBytes[1] & 0xC7);
            Array.Copy(decryptedBytes, 4, AdditionalAuthData, 4, 3 * 6);
            AdditionalAuthData[22] = (byte)(decryptedBytes[22] & 0x0F);

            if (is_a4)
            {
                Array.Copy(decryptedBytes, 24, AdditionalAuthData, 24, 6);

                if (is_qos)
                {
                    AdditionalAuthData[30] = (byte)(decryptedBytes[z - 2] & 0x0F);
                    AdditionalAuthData[31] = 0;
                    B0[1] = AdditionalAuthData[30];
                    AdditionalAuthData[1] = 22 + 2 + 6;
                }
                else
                {
                    AdditionalAuthData[30] = 0;
                    AdditionalAuthData[31] = 0;

                    B0[1] = 0;
                    AdditionalAuthData[1] = 22 + 6;
                }
            }
            else
            {
                if (is_qos)
                {
                    AdditionalAuthData[24] = (byte)(decryptedBytes[z - 2] & 0x0F);
                    AdditionalAuthData[25] = 0;
                    B0[1] = AdditionalAuthData[24];
                    AdditionalAuthData[1] = 22 + 2;
                }
                else
                {
                    AdditionalAuthData[24] = 0;
                    AdditionalAuthData[25] = 0;

                    B0[1] = 0;
                    AdditionalAuthData[1] = 22;
                }
            }

            using (RijndaelManaged aesFactory = new RijndaelManaged())
            {
                aesFactory.Mode = CipherMode.ECB;
                aesFactory.Key = temporalKey;
                ICryptoTransform aes = aesFactory.CreateEncryptor();

                aes.TransformBlock(B0, 0, B0.Length, MIC, 0);
                XOR(MIC, 0, AdditionalAuthData, 16);
                aes.TransformBlock(MIC, 0, MIC.Length, MIC, 0);
                XOR(MIC, 0, AdditionalAuthData.Skip(16).ToArray(), 16);
                aes.TransformBlock(MIC, 0, MIC.Length, MIC, 0);

                B0[0] &= 0x07;
                B0[14] = 0;
                B0[15] = 0;
                aes.TransformBlock(B0, 0, B0.Length, B, 0);
                XOR(decryptedBytes, decryptedBytes.Length - 8, B, 8);

                blocks = (data_len + 16 - 1) / 16;
                last = data_len % 16;
                offset = z + 8;

                for (int i = 1; i <= blocks; i++)
                {
                    var n = (last > 0 && i == blocks) ? last : 16;

                    B0[14] = (byte)((i >> 8) & 0xFF);
                    B0[15] = (byte)(i & 0xFF);

                    aes.TransformBlock(B0, 0, B0.Length, B, 0);
                    XOR(decryptedBytes, offset, B, n);
                    XOR(MIC, 0, decryptedBytes.Skip(offset).ToArray(), n);
                    aes.TransformBlock(MIC, 0, MIC.Length, MIC, 0);

                    offset += n;
                }
            }
            if (strip80211Header)
            {
                decryptedBytes[1] &= 191; // Remove the protected bit
                var decryptedBytesList = decryptedBytes.ToList();
                decryptedBytesList.RemoveRange(34 - 8, 8); // Remove CCMP Parameters (otherwise, without the protected bit it will break the parsing)
                decrypted = Generic.ParseAsNew(decryptedBytesList, IEEE_802_11.ParseAsNew).Layers().ElementAt(1); // Remove the altered 802.11 header
            }
            else
            {
                decrypted = Generic.ParseAsNew(decryptedBytes, IEEE_802_11.ParseAsNew);
            }

            return memcmp(decryptedBytes.Skip(offset).Take(8).ToArray(), MIC.Take(8).ToArray()) == 0;

        }
This is nearly a direct port from airdecap-ng, except for the actual cryptography which of course uses the managed equivalents. First let's get the most obvious parameters out of the way: We need an 802.11 packet to decrypt; the "encryptedPacket" parameter. As before, this isn't the entirety of the frame, which might have radiotap or other things attached, it's just the 802.11 layer and above. We also need a place to put the decrypted result, that's the "decrypted" parameter.

The "strip80211Header" option is one that I'm not too happy with. When the decryption is complete, you're left with the 802.11 header essentially untouched, which indicates that the contents is encrypted. I need to be able to return the full packet that was inputted for verification purposes, but beyond that the more useful output is the actual decrypted contents. So this parameter allows the consumer of the function to choose whether the output will be at the 802.11 layer, or at the decrypted protocol layer.

The temporalKey parameter is of course the most interesting. The Temporal Key is 16 bytes long, 32 bytes into the Pairwise Transit Key (PTK). I discussed how to get the PTK in a previous post.

I won't go into detail about the implementation of this method, as it's largely just data manipulation, but I'll discuss it at a high level. We start by moving bytes around to come up with the B0 and AdditionalAuthData arrays. Next, we prepare an AES encryptor in ECB mode. This mode is important, because normally you'd need to provide an Initialization Vector as well as a key to AES. We use the encryptor to come up with the MIC which we can use to validate our results, and also to perform the actual decryption of the data (which is done not all at once, but in blocks). As a final processing step, remove the 802.11 header if requested, and parse the resulting decrypted packet data. Lastly, we use the MIC we calculated to verify if the decryption was successful.

I was able to put together the PMK, PTK, and CCMP functions that I've gone over in this series to successfully decrypt my WPA2 test capture. Before I can call Packet Gremlin's WiFi decryption support complete, I'll need to also handle TKIP and WEP. There's a lot of overlap between these two; they both use the RC4 encryption algorithm, so it shouldn't take much time to implement. Later, I will post the test captures I've used, as well as links to many of the resources that were helpful in coming up with these implementations.

Validating the WPA2 PTK using C#

Continuing on the quest to decrypt WiFi traffic, I've written code to validate the WPA PTK. This wasn't strictly necessary, and I tried to avoid doing so if only to get to the end goal faster, but when I started implementing the CCMP decryption and it wasn't working, I needed more visibility into what was going on. As before, I arrived at this code by studying various online resources and the Aircrack-ng source code.


public static bool PtkIsValid(byte[] ptk, IEEE_802_1X<IEEE_802_1X.Key> eapolKey)
        {
            bool tkip = (eapolKey.Payload.KeyInformation & 7) == 1;
            bool isValid;
            var MIC = eapolKey.Payload.MIC;
            eapolKey.Payload.MIC = new byte[MIC.Length];
            if (tkip)
            {
                var hmacmd5 = new HMACMD5(ptk.Take(16).ToArray());
                hmacmd5.ComputeHash(eapolKey.ToArray());
                isValid = hmacmd5.Hash.Take(16).SequenceEqual(MIC.Take(16));
            }
            else
            {
                var hmacsha1 = new HMACSHA1(ptk.Take(16).ToArray());
                hmacsha1.ComputeHash(eapolKey.ToArray());
                isValid = hmacsha1.Hash.Take(16).SequenceEqual(MIC.Take(16));
            }
            eapolKey.Payload.MIC = MIC;
            return isValid;
        }
We take the PTK (of course) and an EAPOL Key packet as parameters. This EAPOL packet is part of the four way authentication handshake. Of the four messages in the handshake, any of the latter three will do, though the fourth would probably be quickest to process because it contains no WPA key data. The first message in the handshake is insufficient, as it has no MIC with which we can verify. It's important to note that this isn't the entirety of the frame, which would include the 802.11 header as well as any cruft from the wireless driver (such as the radiotap header); it's just the 802.1x and above. This is one of the nice things about the Packet Gremlin packet structure - instead of accepting a large array and getting offsets into it, I can more clearly express what data I need.

We start by determining if the handshake was done with AES or TKIP. This is done by checking the KeyInformation field of the EAPOL key packet. We need to know this to decide whether to use the MD5 or SHA1 algorithm.

Next, we take the MIC out of the EAPOL packet and zero it out. Figuring out that this needed to be done was tricky; the Aircrack-ng unit test has the frame data with this already zeroed out, and the MIC stored separately.

If the handshake was done with TKIP, we initialize the HMACMD5 algorithm with the first 16 bytes of the PTK, and use it to compute the hash over the EAPOL frame with the MIC zeroed out. We compare the first 16 bytes of the resulting hash with the first 16 bytes of the MIC we copied out of the EAPOL packet. If they are equal, then the PTK is valid.

Similarly, if the handshake was done with AES, we initialize the HMACSHA1 algorithm with the first 16 bytes of the PTK, and use it to compute the hash over the EAPOL frame with the MIC zeroed out. We compare the first 16 bytes of the resulting hash with the first 16 bytes of the MIC we copied out of the EAPOL packet. If they are equal, then the PTK is valid.

Before we're done, we put the MIC back in the EAPOL packet. I don't like having to do this temporary corruption of the packet. I could make a copy first, but I think that would be more of a performance hit than it's worth.

And there you have it! In the next post, I'll go over CCMP decryption, the final step in the process.

Calculating the WPA2 PTK using C#

Continuing with the quest to decrypt wifi traffic, I needed to be able to calculate the PTK (Pairwise Transient Key). As before, I studied the airdecap-ng source code as well as various online resources, to come up with the following code:

public static byte[] CalculatePTK(byte[] pmk, byte[] stmac, byte[] bssid, byte[] snonce, byte[] anonce)
        {           
            var pke = new byte[100];
            var ptk = new byte[80];

            using (var ms = new System.IO.MemoryStream(pke))
            {
                using (var bw = new System.IO.BinaryWriter(ms))
                {
                    bw.Write(new byte[] { 0x50, 0x61, 0x69, 0x72, 0x77, 0x69, 0x73, 0x65, 0x20, 0x6b, 0x65, 0x79, 0x20, 0x65, 0x78, 0x70, 0x61, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0 });/* Literally the string Pairwise key expansion, with a trailing 0*/

                    if (memcmp(stmac, bssid) < 0)
                    {
                        bw.Write(stmac);
                        bw.Write(bssid);
                    }
                    else
                    {
                        bw.Write(bssid);
                        bw.Write(stmac);
                    }

                    if (memcmp(snonce, anonce) < 0)
                    {
                        bw.Write(snonce);
                        bw.Write(anonce);
                    }
                    else
                    {
                        bw.Write(anonce);
                        bw.Write(snonce);
                    }

                    bw.Write((byte)0); // Will be swapped out on each round in the loop below
                }
            }
                        
            for (byte i = 0; i < 4; i++ )
            {
                pke[99] = i;
                var hmacsha1 = new HMACSHA1(pmk);                
                hmacsha1.ComputeHash(pke);
                hmacsha1.Hash.CopyTo(ptk, i * 20);                
            }
            return ptk;
        }
Perhaps the most unexpected part of this algorithm was that the string "Pairwise key expansion" is actually part of it. The airdecap-ng implementation is more efficient in a number of ways, but performance was not my goal here. It takes advantage of the fast memcmp c function in order to compare some of the variables not just for equality, but also "order". This behavior of memcmp is often overlooked; most "ports" of the function to other languages only determine equality. Here is the implementation I used, I'm afraid I've lost the original source:

 for (int i = 0; i < b1.Length; i++)
            {
                if (b1[i] != b2[i])
                {
                    if ((b1[i] >= 0 && b2[i] >= 0) || (b1[i] < 0 && b2[i] < 0))
                        return b1[i] - b2[i];
                    if (b1[i] < 0 && b2[i] >= 0)
                        return 1;
                    if (b2[i] < 0 && b1[i] >= 0)
                        return -1;
                }
            }
            return 0;
          }
The algorithm for calculating the PTK is roughly as follows...

Part 1:
1. Construct a 100 element array beginning with the null terminated ascii string "Pairwise key expansion".
2. Next, write the following pairs, with the smaller of each element first:
(stmac, bssid)
(snonce, anonce)
3. Finally, write an additional 0 byte, which will be swapped out in the next part of the algorithm.

Part 2:
1. Using the HMACSHA1 algorithm initialized with the PMK as data (see the previous post for calculating the PMK), compute the hash of the array. This hash is this first 20 bytes of the PTK.
2. Swap out the last byte of the array you constructed (the 0 byte) for a 1.
3. Repeat the hash in step 1. This is the second 20 bytes of the PTK.
4. Swap out the last byte of the array you constructed (now a 1) for a 2.
5. Repeat the hash in step 1. This is the third 20 bytes of the PTK.
6. Swap out the last byte of the array you constructed (now a 2) for a 3.
7. Repeat the hash in step 1. This is the final 20 bytes of the PTK.

The parameters for the PTK calculation consist of the the PMK, which we already know how to calculate, the station MAC and bssid, which are obtained easily enough, and the snonce and the anonce. How do we get those nonces? From the EAPOL four-way handshake. This is one of the aspects of WPA2 that is more secure than WEP: Traffic cannot be decrypted, even if you know the PSK, without also capturing the authentication handshake to obtain the nonces.

As part of this adventure, I've had to add support for Radiotap, 802.1X, and EAPOL to PacketGremlin. I don't think any of that is deserving of a post, but I just wanted to mention it. I've got nearly all the pieces required for decryption in place!

Calculating the WPA2 PMK using C#

One of the features I've wanted to implement for Packet Gremlin is the ability to decrypt encrypted WiFi traffic. I only know of two tools capable of this: Airdecap-ng, which can't do it on a live capture, and Wireshark, which can't capture wireless traffic on Windows due to a limitation of WinPCap. One of the things needed for implementing this feature is to calculate the PMK (Pairwise Master Key). By studying the source code of airdecap-ng and various online resources, I found that this is actually trivial:

Update 6/1/14: The code as originally posted was technically correct, and did pass the AirCrack unit tests and validate with other tools, however I found that the result was causing me to generate invalid PTKs. It turns out that while the PMK can be generated to an arbitrary length, and the Aircrack unit test used 40 bytes, the length expected for use in PTK calculation is actually 32 bytes. I have updated the code with this option and default accordingly.
       
public static byte[] CalculatePMK(byte[] psk, byte[] ssid, int pmkLength = 32)
        {
            Rfc2898DeriveBytes pbkdf2 = new Rfc2898DeriveBytes(psk, ssid, 4096);
            return pbkdf2.GetBytes(pmkLength);
        }


I was pleased to find a small set of unit tests accompany the aircrack suite, including one for PMK calculation. I ported it to C# and... it crashed. It turns out that, for reasons unknown to me, the Rfc2898DeriveBytes class requires that the salt (in this case, the ssid) be at least 8 bytes in length. Since ssids (including the one in this unit test) can be fewer than 8 bytes in length, I needed to work around this artificial limitation. I used Reflector to examine the source of this class, and found that the exception is thrown in the setter for the Salt property, which is invoked in the constructor. The setter does three things:

1. Validates the length of the salt
2. Copies the salt to the private byte[] m_salt
3. Calls the private method Initialize()

I found that the public method Reset calls the private method Initialize, but upon further inspection of the Initialize method itself, I realized that in this particular case, I don't need to do it since this is a fresh instance of the class. All I really needed to do was fill in m_salt.

I used an empty byte[8] as a placeholder for the salt in the constructor, then used reflection to get a reference to the private m_salt field, and set it with the ssid:

       
public static byte[] CalculatePMK(byte[] psk, byte[] ssid, int pmkLength = 32)
        {
            Rfc2898DeriveBytes pbkdf2 = new Rfc2898DeriveBytes(psk, /*ssid*/ new byte[8], 4096);
            //This reflection is required because there's an arbitrary restriction that the salt must be at least 8 bytes
            var saltProp = pbkdf2.GetType().GetField("m_salt", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
            saltProp.SetValue(pbkdf2, ssid);

            //pbkdf2.Reset(); 
            //To officially complete the reflection trick, the private method Initialize() should be called. That's all Reset() does. 
            //But I don't think it's needed because we haven't hashed anything yet.

            return pbkdf2.GetBytes(pmkLength);
        }


With that change, the unit test passed, and the PMK calculation was complete. In a subsequent post I will discuss the less trivial implementation of the PTK calculation.