Outbreak Labs

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

LibPacketGremlin is Open Source!

The full source code to LibPacketGremlin is now publicly available, including all of the wireless decryption work that I've posted about in 2014. It is also available as a NuGet package, for use in your own software. My hope is that other developers will want to contribute to this project, but even if they don't, having this code in the public domain will allow me to release some of the other projects I've been working on that use it as a base.

EasyNetQ on CoreCLR / DNXCore50, removing port of Protobuf-net

Disappointed with the seeming disinterest of the official RabbitMQ team in supporting CoreCLR, I was pleased to see that someone had gone and done it themselves. The logical next step once we've got the base RabbitMQ client available is to port EasyNetQ. I've done just that: https://github.com/SapientGuardian/EasyNetQ

I'm also pleased to note that there's an alpha build of Protobuf-net that's at least as good as the one I had going, so I've removed my fork from github so as not to confuse anyone into thinking it had value at this point.

MySQL Connector/Net for CoreCLR / DNXCore50

After several failed attempts, I have managed to strip down and modify the MySQL Connector/Net library enough to get it working on DNXCore50. I would strongly advise against using this in production, but it may work for testing purposes while waiting for an official release from Oracle. Here is the repo.

The following basic scenario works, which was my goal:

string connectionString = "server=127.0.0.1;userid=my_user;password=my_password;database=my_db;default command timeout=10;Connection Timeout=5;";

    using (var conn = new MySqlConnection(connectionString))
    {
        conn.Open();
        var cmd = new MySqlCommand(@"select col1, col2 from data where id = @id", conn);
        cmd.Parameters.AddWithValue("@id", "myId");
        var reader = cmd.ExecuteReader();

        string col1data;
        string col2data;

        if (reader.Read())
        {

            if (!reader.IsDBNull(0))
            {
                col1data = reader.GetString(0);
            }

            if (!reader.IsDBNull(1))
            {
                col2data = reader.GetString(1);
            }
        }
    }

Debugging AvalonDock 1.3 applications in Windows 8 and Windows 10

I recently upgraded from Windows 7 to Windows 10. Among the myriad problems I've run into, was the inability to run my Progression toolkit from within Visual Studio. Progression uses AvalonDock 1.3, which doesn't seem to get along with Windows 10 (or Windows 8, based on my research). Curiously, the program builds and can run outside of Visual Studio, but when run with the debugger, this exception is thrown:

Exception thrown: 'System.IO.FileNotFoundException' in mscorlib.dll

Additional information: Could not load file or assembly 'AvalonDock.Aero2, Version=1.3.3600.0, Culture=neutral, PublicKeyToken=85a1e0ada7ec13e4' or one of its dependencies. The system cannot find the file specified.
This appears to be the result of Windows 10 loading a new Aero2 theme that AvalonDock was unprepared for. After spending a few minutes trying to cheat by copying the existing DLL to match the new name, I decided to take the approach of forcing my application to use the old Windows 7 Aero theme. The top results on StackOverflow, which involved adding a reference to the PresentationFramework.Aero dll and editing the resource XAML, did not solve my problem. Then I found an article appropriately named How to Actually Change the System Theme in WPF. The article is worth a read, but here's the solution, shamelessly copied:

public partial class App : Application
    {
        public App()
        {
            SetTheme("aero", "normalcolor");
        }

        /// 
        /// Sets the WPF system theme.
        /// 
        /// The name of the theme. (ie "aero")
        /// The name of the color. (ie "normalcolor")
        public static void SetTheme(string themeName, string themeColor)
        {
            const BindingFlags staticNonPublic = BindingFlags.Static | BindingFlags.NonPublic;

            var presentationFrameworkAsm = Assembly.GetAssembly(typeof(Window));

            var themeWrapper = presentationFrameworkAsm.GetType("MS.Win32.UxThemeWrapper");

            var isActiveField = themeWrapper.GetField("_isActive", staticNonPublic);
            var themeColorField = themeWrapper.GetField("_themeColor", staticNonPublic);
            var themeNameField = themeWrapper.GetField("_themeName", staticNonPublic);

            // Set this to true so WPF doesn't default to classic.
            isActiveField.SetValue(null, true);

            themeColorField.SetValue(null, themeColor);
            themeNameField.SetValue(null, themeName);
        }
    }
With that in place, the application no longer crashes when Visual Studio starts it.

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.

Debugging Scala code inside SBT inside Vagrant, using IntelliJ

For Scala, a common development environment is the combination of IntelliJ, Vagrant, and SBT. IntelliJ to edit Scala code, Vagrant to keep a consistent, reproducible, VM-based work area, and SBT to build and run Scala code. It isn't obvious how to use the debugger in IntelliJ against the code executing under SBT within the VM. Here's how you do it.

1. In your project's SBT build settings, locate the area in which options are passed to the JVM. This likely includes such options as "-Djava.library.path=lib".
2. To this section, add the following options:
"-Xdebug",
"-Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=5005"
3. Rebuild and run your SBT project.
4. Open your project in IntelliJ, and go to Run> Edit Configurations


5. Click the plus icon in the upper left to add a new configuration, and select Remote

6. Name this configuration whatever you'd like (I called mine Vagrant), but enter your vagrant box's hostname or IP address where indicated

7. Press "OK". You should now see this new configuration selectable and debuggable in the upper right corner of the IDE.



If you've done everything correctly, IntelliJ should attach to your running Scala program.

Things that could've gone wrong:
  • If the Java options were not correctly applied, the JVM won't listen on port 5005 for the remote debugger connection. You should see a message in your console indicating that it's listening on that socket.
  • Your Vagrant instance might not be set up for networking, or to forward ports.
  • Your guest or host OS might have a firewall preventing the connection from going through

Good luck!