summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore18
-rw-r--r--CMakeLists.txt12
-rw-r--r--main.c217
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
diff --git a/main.c b/main.c
new file mode 100644
index 0000000..95b8587
--- /dev/null
+++ b/main.c
@@ -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