diff options
-rw-r--r-- | .gitignore | 18 | ||||
-rw-r--r-- | CMakeLists.txt | 12 | ||||
-rw-r--r-- | main.c | 217 |
3 files changed, 247 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..92cc9fe --- /dev/null +++ b/.gitignore @@ -0,0 +1,18 @@ +.idea +.code + +# copied from https://github.com/github/gitignore/blob/main/CMake.gitignore +CMakeLists.txt.user +CMakeCache.txt +CMakeFiles +CMakeScripts +Testing +Makefile +cmake_install.cmake +install_manifest.txt +compile_commands.json +CTestTestfile.cmake +_deps + +# cmake builds +cmake-build-*
\ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..5bdfa5a --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,12 @@ +cmake_minimum_required(VERSION 3.0) +project(mobileqq_fp_cracker C) + +set(CMAKE_C_STANDARD 99) + +set(common_compiler_args "-Wall -Werror -Wno-unused") +set(CMAKE_CXX_FLAGS_DEBUG "${common_compiler_args} -g -ggdb -fsanitize=address -DDEBUG") +set(CMAKE_C_FLAGS_RELEASE "${common_compiler_args} -O3 -DNDEBUG") + +add_executable(fp_cracker main.c) + +target_link_libraries(fp_cracker libtomcrypt.a)
\ No newline at end of file @@ -0,0 +1,217 @@ +#include <tomcrypt.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <stdbool.h> +#include <assert.h> + +const char *hexchars = "0123456789ABCDEF"; + +typedef struct s_key_search_ctx { + uint64_t ciphertext; // first 8 bytes of the ciphertext in encrypted QQ flash image + uint64_t yield; // if routine `yield_possible_key` returns true, the possible 64-bit DES key will be stored here + uint32_t next_possible_key; // 4 byte effective key space + bool finished; +} key_search_ctx; + +/* constructor of type `key_search_ctx` */ +void new_key_search_ctx(key_search_ctx *ctx, uint64_t ciphertext) { + ctx->finished = false; + ctx->next_possible_key = 0u; + ctx->ciphertext = ciphertext; +} + +/* returns false if no result yield from this call and searching is finished */ +bool yield_possible_key(key_search_ctx *ctx) { + if (ctx->finished) return false; + +// const char[] hexchars = {0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, +// 0x38, 0x39, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46}; +#define FILL_KEY(buf, key, i) \ + ((buf)[7-(i)] = hexchars[((key) >> (4*(i))) & 0xFu]) \ + // buf: char[8], key: uint64_t, i:uint = 0, 1, 2, ..., 7 + uint32_t k = ctx->next_possible_key; + uint64_t plaintext; + char key[8]; + symmetric_key skey; + do { + // convert key uint32 to char[] + FILL_KEY(key, k, 0); + FILL_KEY(key, k, 1); + FILL_KEY(key, k, 2); + FILL_KEY(key, k, 3); + FILL_KEY(key, k, 4); + FILL_KEY(key, k, 5); + FILL_KEY(key, k, 6); + FILL_KEY(key, k, 7); + // decrypt file header with this key + int err; + if ((err = des_setup((const unsigned char *) key, 8, 0, &skey)) != CRYPT_OK) { + fprintf(stderr, "Err: setup: %s", error_to_string(err)); + return false; + } + if (des_ecb_decrypt( + (const unsigned char *) &(ctx->ciphertext), + (unsigned char *) &plaintext, + (const symmetric_key *) &skey + ) != CRYPT_OK) + continue; // failed to decrypt + // validate first 3 bytes of the cleartext + if ((plaintext & 0xFFFFFFu) == 0xFFD8FFu) { + ctx->yield = *(uint64_t *) key; + // if next_possible_key rewinds to 0, finished will be set to true + ctx->finished = !++ctx->next_possible_key; + return true; + } + } while ((k = ++ctx->next_possible_key)); + ctx->finished = true; + return false; +} + +/* given buf, returns length of the pad, or -1 if the data is not padded with valid pkcs7 */ +int pkcs7_check_pad(const char *buf, size_t n) { + if (!n) return -1; + --n; + unsigned char pad = buf[n--]; + if (!pad) return -1; + if (n < pad) return -1; // buf is shorter than a valid pad + for (int i = pad; i > 1; --i) { + if (buf[n--] != pad) return -1; + } + return pad; +} + +int main(int argc, char *argv[]) { +// uint64_t ciphertext = 8022485120222247589; +// unsigned char plaintext[8]; +// const char *key = "A0979B6D"; +// symmetric_key skey; + FILE *fp; + + if (argc != 2 && argc != 3) { + printf("Usage: %s <fp_file> [<where_to_save_the_decrypted_file>]\n" + "The decrypted image won't be saved if save path is not specified.\n", + argv[0]); + return 0; + } + + const char* plaintext_save_path = (argc == 3) ? (argv[2]) : NULL; + const char* ciphertext_file_path = argv[1]; + + // open file + if (!(fp = fopen(ciphertext_file_path, "rb"))) { + perror("fopen"); + return 1; + } + + // test file header + char header[8]; + if (fread(header, 1, 8, fp) != 8) { + fprintf(stderr, "Cannot read first 8 bytes from file.\n"); + return 1; + } + if (*(uint64_t *) header != *(uint64_t *) "ENCRYPT:") { + fprintf(stderr, "Bad file header.\n"); + return 1; + } + + // read ciphertext into memory + fseek(fp, 0, SEEK_END); + long file_length = ftell(fp); + if (file_length <= 8) { + fprintf(stderr, "Invalid file length (%ld)\n", file_length); + return 1; + } + + const unsigned long ciphertext_length = file_length - 8; + if (ciphertext_length % 8 != 0) { + fprintf(stderr, "Invalid file length: %ld can not be divided by 8.\n", file_length); + return 1; + } + char *ciphertext = malloc(ciphertext_length); + char *plaintext = malloc(ciphertext_length); // for the future decryption use, padded plaintext (pkcs7) + if (ciphertext == NULL || plaintext == NULL) { + perror("malloc"); + return 1; + } + + fseek(fp, 8, SEEK_SET); + if (fread(ciphertext, 1, ciphertext_length, fp) != ciphertext_length) { + fprintf(stderr, "Cannot read the whole file.\n"); + return 1; + } + + // start searching + printf("Searching key...\n"); + fflush(stdout); + + key_search_ctx ctx; + new_key_search_ctx(&ctx, *(uint64_t *) ciphertext); + // FOR DEBUGGING ONLY +// assert(*(uint64_t *) ciphertext == 8022485120222247589ull); +// ctx.next_possible_key = 0xA0979B6Du; + while (yield_possible_key(&ctx)) { + // found a possible correct key + // validate it by calculating md5 hashsum of the plaintext + printf("Possible key: %zu\n", ctx.yield); + fflush(stdout); + + // decrypt the whole ciphertext + int err; + symmetric_key skey; + if ((err = des_setup((const unsigned char *) (&ctx.yield), 8, 0, &skey)) != CRYPT_OK) { + fprintf(stderr, "Err: setup: %s", error_to_string(err)); + continue; // skip this key + } + + uint_fast32_t blk_cnt = ciphertext_length >> 3; + for (uint_fast32_t blk = 0; blk < blk_cnt; ++blk) { + des_ecb_decrypt( + (const unsigned char *) ((uint64_t *) ciphertext + blk), + (unsigned char *) ((uint64_t *) plaintext + blk), + (const symmetric_key *) &skey + ); // error checking is unnecessary + } + + int pad_length = pkcs7_check_pad(plaintext, ciphertext_length); + const unsigned int unpadded_length = ciphertext_length - pad_length; + assert(pad_length < ciphertext_length); + if (pad_length < 0) { + // invalid pad, this key is incorrect, skip it + fprintf(stderr, "Invalid pad.\n"); + continue; + } + + // calculate md5 checksum of the decrypted plaintext + char md5_out[16]; + hash_state md; + md5_init(&md); + md5_process(&md, (const unsigned char *) plaintext, unpadded_length); + md5_done(&md, (unsigned char *) md5_out); + + // compare md5_out[0~3] with 8-byte ASCII hex string ctx.yield + char md5_hex[8+1]; // hex of first 4-byte of md5_out, 1 more byte to hold the '\0' terminator + snprintf(md5_hex, 8+1, "%02X%02X%02X%02X", + md5_out[0] & 0xFFu, md5_out[1] & 0xFFu, + md5_out[2] & 0xFFu, md5_out[3] & 0xFFu); + if (!memcmp(md5_hex, (const char *) (&ctx.yield), 8)) { + printf("[+] FOUND KEY: %zu\n", ctx.yield); + + if (plaintext_save_path) { + FILE *fout = fopen(plaintext_save_path, "wb"); + if (!fout) { + perror("Cannot fopen for saving"); + return 1; + } + fwrite(plaintext, 1, unpadded_length, fout); + fclose(fout); + printf("Flash photo has been saved in: %s\n", plaintext_save_path); + } + + return 0; + } + // otherwise the key is incorrect, continue searching + } + + return 0; +}
\ No newline at end of file |