Matt Winkler at Bloomberg in anger put it best:
"The enemy is the human!"
This was shortly after firing a reporter. I don't think he was up on the nauances of bad crypto. It is not a surprise that this statement applies to software systems. Cryptography seldom fails on its own. To avoid argument, there are a few notable counterexamples:
- DES got long in the tooth, and as we learned, hardware got fast enough to brute force guess DES keys;
- There is evidence to suggest that the NSA has found direct attacks against RC4, used in TLS.
But, as a general statement, direct attacks on AES or other modern block ciphers are a waste of time. A human built that implementation of AES though. And the human builds systems based around AES. The enemy, truly, is the human, specifically human error. With errors like picking the wrong block cipher modes, picking weak keys or fixing an IV, the security offered by AES becomes useless.
We've stolen the keys for our 88MC200-based device, so now let's look into the abyss of some PCAPs. Hopefully we can apply what we've learned.
Key Mismanagement
Let's fire up Wireshark and look at our PCAPs from the rogue access point. Filtering by our device (on my rogue network, IPv4 address 172.16.1.15):
Time to try to figure out what the protocol looks like for this device. It's mostly UDP, so analysis should be easy. As well, we'll focus on the post-provisioning workflow for now. Here's what the typical message datagram looks like:
Let's look at a few other similar looking datagrams, and look for the difference:
So it seems that the high entropy data starts at offset 0x62 in the complete datagram. For this example, the high entropy data is 0x150 bytes long. Since 16 divides 0x150, there's a good chance this was encrypted with AES-CBC. There are a few obvious fields in the datagram that I've called out as well:
- MAC, IPv4 and UDP headers. The MAC header in this case is synthetic, since we captured this from an 802.11g network;
- A deviceType ID field, showing what kind of device this is (10039);
- A MAC address field, in reverse byte order, presumably to identify the device.
I didn't bother boxing it in, but there's also a magic 8 byte sequence at the start of the datagram payload. This makes it easy to identify what are likely control sequences.
There is also an unlock sequence that needs to be reverse engineered as well. This relies on another field that was in our SQLite3 database, the devicePassword field. This should be easy once we get a better grip on how the device expects data to be formatted.
Deciphering the Encrypted Block
Let's borrow the mbedTLS implementation of AES. We know it works, it's easy to use and our device embeds pre-mbed polarSSL. From our rudimentary static analysis of the decryption code, we know that we're using AES-128-CBC. We have the IV, also from static analysis, and we have the key, snagged from my victim Android tablet. So, let's just try it, right?
Let's knock out a simple tool to analyze the crypto. We'll export packets from Wireshark as C arrays* in a header file, then include them in our tool. I'm a C programmer by inclination, so I'll stick with what I know best See the full code at the end of the blog post.
We guessed well. On our first run we get a rather sparse message:
We need to do some more work to understand what the contents of the payload is, but this is good progress. Next step is to batch decrypt the datagrams and start analyzing the contents. There are also several header fields we don't completely understand yet. We'll get there with an enhanced version of our decryption tool next time.
* This feature in Wireshark does not get enough love. It has made my life easier on innumerable occasions. Thanks guys! <3
Full Code for the Payload Decrypting App
#include "aes.h" #include "enc_packet_1.h" // From Wireshark #include <stdio.h> #include <ctype.h> #include <stdlib.h> #include <string.h> const unsigned char key[16] = { 0xF2, 0xF1, 0x67, 0x20, 0x28, 0x65, 0xDE, 0x0F, 0x5F, 0xD8, 0x54, 0x7F, 0x95, 0x4B, 0xCB, 0x6E }; unsigned char iv[16] = { 0x56, 0x2e, 0x17, 0x99, 0x6d, 0x09, 0x3d, 0x28, 0xdd, 0xb3, 0xba, 0x69, 0x5a, 0x2e, 0x6f, 0x58 }; void hexdump(const uint8_t *in, size_t len) { for (size_t i = 0; i < len; i += 16) { printf("%04zx: ", i); for (size_t j = 0; j < 16 && i + j < len; j++) { printf("%02x ", in[i + j]); } printf(" |"); for (size_t j = 0; j < 16 && i + j < len; j++) { printf("%c", isprint(in[i + j]) ? in[i + j] : '.'); } printf("|\n"); } } #define PACKET pkt7426 int main(int argc, char *argv[]) { mbedtls_aes_context ctx; int ret = EXIT_FAILURE; int err = 0; unsigned start = 0x62; unsigned char output[sizeof(PACKET) + 4]; memset(output, 0xa5, sizeof(output)); if (argc > 1) { // Allow the user to override the starting point to decrypt start = atoi(argv[1]); } printf("Starting analysis at 0x%08x\n", start); mbedtls_aes_init(&ctx); if (0 != mbedtls_aes_setkey_dec(&ctx, key, 128)) { perror("mbedtls_aes_setkey_dec"); goto done; } if (0 != (err = mbedtls_aes_crypt_cbc(&ctx, MBEDTLS_AES_DECRYPT, sizeof(PACKET) - start, iv, PACKET + start, output))) { printf("mbedtls_aes_crypt_cbc error: %d\n", err); goto done; } hexdump(output, sizeof(PACKET) - start); mbedtls_aes_free(&ctx); ret = EXIT_SUCCESS; done: return ret; }