diff options
Diffstat (limited to 'aesscan.c')
-rw-r--r-- | aesscan.c | 496 |
1 files changed, 496 insertions, 0 deletions
diff --git a/aesscan.c b/aesscan.c new file mode 100644 index 0000000..b9c9e95 --- /dev/null +++ b/aesscan.c @@ -0,0 +1,496 @@ +/* + + aesscan.c -- Utility to scan for AES keys in binaries + + Copyright © 2014 Samuel Lidén Borell <samuel@kodafritt.se> + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + +*/ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <nettle/aes.h> +#include <nettle/cbc.h> + +#define READSIZE 1024 + +struct fileentry { + char *name; + long datalen; + unsigned char *data; +}; + +enum ciphermode { + AES128_CBC = 0, + AES128_ECB, + AES256_CBC, + AES256_ECB +}; + +static const char ciphermodes[][11] = { + "AES128_CBC", + "AES128_ECB", + "AES256_CBC", + "AES256_ECB" +}; + + +static const int cipher_iv_size[] = { + AES_BLOCK_SIZE, + 0, + AES_BLOCK_SIZE, + 0 +}; + +static const size_t cipher_key_size[] = { + 128/8, /* TODO upgrade libnettle and use AES128_KEY_SIZE */ + 128/8, + 256/8, + 256/8 +}; + +enum padding { + PKCS7, + THROW_AWAY, /* throw away last block */ + NO_PADDING +}; + +static const char paddings[][11] = { + "PKCS#7", + "THROW_AWAY", + "NONE" +}; + + +static long offset; +static long maxlength; +static long numfiles; +static struct fileentry *files; +static char *scanfilename; +static FILE *scanfile; +static unsigned char *decryptbuff; +static enum ciphermode ciphermode; +static enum padding padding; + +static union { + struct aes_ctx aesctx; + /*struct CBC_CTX(struct aes_ctx, 128/8) aes128cbc;*/ +} keyctx; + + +enum typeofdata { + JUNK = 0, + TEXT = 1, + BINARY = 2 +}; + +/* + No strange control characters = OK, probably text + High number of zeros = OK, probably binary data +*/ +enum typeofdata checkdata(const unsigned char *data, long len) +{ + long numzeros = 0; + long numcontrol = 0; + float rzero, rctrl; + const unsigned char *p = data; + const unsigned char *end = data+len; + + int i; + char padsize = end[-1]; + if (padding == PKCS7) { + if (padsize > AES_BLOCK_SIZE || padsize < 0) { + return JUNK; + } + for (i = 0; i < padsize; i++) { + if (end <= p || *--end != padsize) return JUNK; + } + } else if (padding == THROW_AWAY) { + end -= AES_BLOCK_SIZE; + } + + while (p < end) { + unsigned char c = *(p++); + if (c == '\0') { + numzeros++; + } else if (c >= '\x01' && c <= '\x1F' && + c != '\x09' && c != '\x08' && c != '\x0A' && c != '\x0D') { + numcontrol++; + } + } + + rzero = (float)numzeros / (float)len; + rctrl = (float)numcontrol / (float)len; + + if (rzero >= 0.02) { /* >> 1/256 */ + return BINARY; + } else if (rctrl <= 0.03) { /* << (32-4) / 256 */ + return TEXT; + } else if (padding == PKCS7 && padsize >= 4) { + return BINARY; + } else { + return JUNK; + } +} + +void decrypt(unsigned char *encrypted, long len, const unsigned char *key, + unsigned char *decrypted) +{ + unsigned char iv[AES_BLOCK_SIZE]; + /* TODO use nettle-meta.h */ + switch (ciphermode) { + case AES128_CBC: + if (len < AES_BLOCK_SIZE) { abort(); } + memcpy(iv, encrypted, AES_BLOCK_SIZE); + aes_set_decrypt_key(&keyctx.aesctx, 128/8, key); + cbc_decrypt(&keyctx.aesctx, (nettle_crypt_func *)aes_decrypt, AES_BLOCK_SIZE, iv, + len-AES_BLOCK_SIZE, decrypted, encrypted+AES_BLOCK_SIZE); + break; + case AES128_ECB: + aes_set_decrypt_key(&keyctx.aesctx, 128/8, key); + aes_decrypt(&keyctx.aesctx, len, decrypted, encrypted); + break; + case AES256_CBC: + if (len < AES_BLOCK_SIZE) { abort(); } + memcpy(iv, encrypted, AES_BLOCK_SIZE); + aes_set_decrypt_key(&keyctx.aesctx, 256/8, key); + cbc_decrypt(&keyctx.aesctx, (nettle_crypt_func *)aes_decrypt, AES_BLOCK_SIZE, iv, + len-AES_BLOCK_SIZE, decrypted, encrypted+AES_BLOCK_SIZE); + break; + case AES256_ECB: + aes_set_decrypt_key(&keyctx.aesctx, 256/8, key); + aes_decrypt(&keyctx.aesctx, len, decrypted, encrypted); + break; + default: + abort(); + } + /*putchar(key[128/8-1]);*/ +} + +int try_decrypt(long scan_offset, unsigned char *key) +{ + long files_text = 0, files_bin = 0; + long i; + + for (i = 0; i < numfiles; i++) { + const struct fileentry *entry = &files[i]; + enum typeofdata result; + const long decryptlen = entry->datalen - cipher_iv_size[ciphermode]; + +/*if (*key == 0x4B && key[1] == 0x45 && key[2] == 0x59) { +fprintf(stderr, "found key at %ld\n", scan_offset); +fprintf(stderr, "file = %hhx %hhx %hhx %hhx\n", entry->data[0], entry->data[1], entry->data[2], entry->data[3]); +}*/ + decrypt(entry->data, entry->datalen, key, decryptbuff); +/*if (*key == 0x4B && key[1] == 0x45 && key[2] == 0x59) { +fprintf(stderr, "data = %.*s\n", decryptlen, decryptbuff); +}*/ + +/*result = 0; +if (*key == 0x4B && key[1] == 0x45 && key[2] == 0x59) {*/ + result = checkdata(decryptbuff, decryptlen); +/*fprintf(stderr, "result = %d\n", result); +}*/ + + if (result == JUNK) return 0; + + if (result == BINARY) files_bin++; + else files_text++; + } + + printf("%ld: found key (decrypted data = %ld text, %ld binary)\n", + scan_offset, files_text, files_bin); + return 0; +} + +void do_scan_file() +{ + unsigned char keybuff[2*READSIZE]; + unsigned long scan_offset = 0; + const unsigned long keysize = cipher_key_size[ciphermode]; + + size_t numbytes = fread(keybuff, 1, READSIZE, scanfile); + if (numbytes != READSIZE && ferror(scanfile)) { + perror("scan file too short"); + exit(1); + } + + /* Scan the first half of the buffer and move in more data. + This is done so we can handle keys that overlap a READSIZE + boundary. */ + while (!feof(scanfile)) { + unsigned long keyoffs = 0; + int nonzeros = keysize; + + if ((scan_offset & 0xFFFFFL) == 0) { + fprintf(stderr, "Scanned %ld bytes\n", scan_offset); + } + + numbytes += fread(keybuff+READSIZE, 1, READSIZE, scanfile); + while (numbytes > READSIZE) { + if (keybuff[keyoffs+keysize]) { + nonzeros = keysize; + } + + if (nonzeros) { + try_decrypt(scan_offset, &keybuff[keyoffs]); + } + numbytes--; + scan_offset++; + keyoffs++; + if (nonzeros) nonzeros--; + } + memcpy(keybuff, keybuff+READSIZE, READSIZE); + } + + /* Scan the remaining data */ + { + unsigned long keyoffs = 0; + while (numbytes > keysize) { + try_decrypt(scan_offset, &keybuff[keyoffs]); + numbytes--; + scan_offset++; + keyoffs++; + } + fprintf(stderr, "Scanned %ld bytes\n", scan_offset); + } +} + +void show_usage(const char *arg0) +{ + printf("usage: %s -s FILE_TO_SCAN [-o OFFSET] [-l LEN] FILES...\n" + "\n" + "Tries to decrypt the given FILES with AES-128/256 using all 128/256-bit\n" + "substrings in FILE_TO_SCAN.\n" + "\n" + "Options:\n" + " -c CIPHER Set the cipher to use:\n" + " 0 AES128 with CBC (default)\n" + " 1 AES128 with ECB\n" + " 2 AES256 with CBC\n" + " 3 AES256 with ECB\n" + " -p PADDING Set the padding to use:\n" + " 0 PKCS#5 (default)\n" + " 1 Simply throw away the last block\n" + " 2 No padding\n" + " -l LEN Try to decrypt only up to LEN bytes in the FILES\n" + " -o OFFSET Start from the given byte offset in the FILES\n" + "\n" + "Note: The -o and -l options are parsed from left to right. An -o\n" + "or -l option must occur before the file(s) it should to apply to.\n" + "\n", + arg0); +} + +long get_num(int argc, char **argv, int *idxp) +{ + int i = *idxp; + long value; + char *s, *endp; + + if (i+1 >= argc) { + fprintf(stderr, "%s: option requires an argument\n", argv[i]); + exit(2); + } + + s = argv[i+1]; + value = strtol(s, &endp, 10); + if (*s == '\0' || *endp != '\0' || value < 0) { + fprintf(stderr, "%s: invalid number\n", s); + exit(2); + } + + *idxp = i+1; + return value; +} + +char *get_str(int argc, char **argv, int *idxp) +{ + int i = *idxp; + + if (i+1 >= argc) { + fprintf(stderr, "%s: option requires an argument\n", argv[i]); + exit(2); + } + + *idxp = i+1; + return argv[i+1]; +} + +int main(int argc, char **argv) +{ + int parsing_opts = 1, error = 0; + int i; + long maxdatalen; + for (i = 1; i < argc; i++) { + char *const arg = argv[i]; + if (arg[0] == '-' && parsing_opts) { + /* Option */ + if (arg[1] == '-' && arg[2] == '\0') { + /* "--" = stop parsing arguments */ + parsing_opts = 0; + continue; + } + if (arg[1] != '\0' && arg[2] == '\0') { + switch (arg[1]) { + case 'c': /*{ + const char *s = get_str(argc, argv, &i); + if (strcmp + break; }*/ + ciphermode = get_num(argc, argv, &i); + if (ciphermode > AES256_ECB) { + fprintf(stderr, "%s: invalid cipher type %d\n", + argv[0], ciphermode); + error = 1; + } + break; + case 'h': + show_usage(argv[0]); + return 0; + case 'l': + maxlength = get_num(argc, argv, &i); + break; + case 'o': + offset = get_num(argc, argv, &i); + break; + case 'p': + padding = get_num(argc, argv, &i); + if (padding > NO_PADDING) { + fprintf(stderr, "%s: invalid paddig type %d\n", + argv[0], padding); + error = 1; + } + break; + case 's': + scanfilename = get_str(argc, argv, &i); + break; + } + } + } else { + /* Filename */ + unsigned char *data; + long datalen; + struct fileentry *entry; + FILE *file = fopen(arg, "rb"); + if (!file) { + perror(arg); + error = 1; + continue; + } + + if (offset) { + if (fseek(file, offset, SEEK_SET) == -1) { + perror(arg); + exit(1); + } + } + + if (!maxlength) { + /* Read the whole file */ + if (fseek(file, 0, SEEK_END) == -1) { + perror(arg); + exit(1); + } + + datalen = ftell(file) - offset; + if (datalen < 0) { + perror("ftell"); + exit(1); + } + + data = malloc(datalen); + if (!data) { + perror("allocating memory for file"); + exit(1); + } + + if (fseek(file, offset, SEEK_SET) == -1) { + perror(arg); + exit(1); + } + } + + if (fread(data, 1, datalen, file) != (size_t)datalen) { + perror(arg); + exit(1); + } + + fclose(file); + + if (datalen == 0) { + fprintf(stderr, "%s: File is empty\n", arg); + exit(1); + } + + numfiles++; + files = realloc(files, sizeof(struct fileentry)*numfiles); + if (!files) { + perror("realloc"); + exit(1); + } + entry = &files[numfiles-1]; + entry->name = arg; + entry->data = data; + entry->datalen = datalen; + } + } + + if (error) { + return 1; + } + + if (!numfiles) { + fprintf(stderr, "%s: no files specified\n", argv[0]); + show_usage(argv[0]); + return 2; + } + + if (!scanfilename) { + fprintf(stderr, "%s: no scan file specified\n", argv[0]); + return 2; + } + + /* Allocate buffer for decryption */ + maxdatalen = 0; + for (i = 0; i < numfiles; i++) { + if (files[i].datalen > maxdatalen) { + maxdatalen = files[i].datalen; + } + } + decryptbuff = malloc(maxdatalen); + + fprintf(stderr, "Using %s cipher and mode, with padding %s\n", + ciphermodes[ciphermode], paddings[padding]); + + /* Start scanning the file for keys */ + scanfile = fopen(scanfilename, "rb"); + if (!scanfile) { + perror(scanfilename); + exit(1); + } + + do_scan_file(); + + fclose(scanfile); + + return 0; +} + |