#include #include #include #include #include #include #include #include #include "loader.h" void my_assert(int condition, char *msg) { if (!condition) { printf("%s", msg); exit(-1); } } static uint get_system_api_level(void) { char sdk_version[PROP_VALUE_MAX]; sdk_version[0] = '\0'; __system_property_get("ro.build.version.sdk", sdk_version); return atoi(sdk_version); } void init_jvm(JavaVM **vm, JNIEnv **env) { if (!getenv("ANDROID_DATA")) putenv("ANDROID_DATA=/data"); if (!getenv("ANDROID_ROOT")) putenv("ANDROID_ROOT=/system"); void *vm_module, *runtime_module; jint (*create_java_vm)(JavaVM ** vm, JNIEnv ** env, void * vm_args); jint (*register_natives)(JNIEnv * env); jint (*register_natives_legacy)(JNIEnv * env, jclass clazz); jint result; JavaVMOption options[7]; JavaVMInitArgs args; char so_name[PROP_VALUE_MAX]; if ((__system_property_get("persist.sys.dalvik.vm.lib.2", so_name) <= 0) && (__system_property_get("persist.sys.dalvik.vm.lib", so_name) <= 0)) { strcpy(so_name, "libdvm.so"); // fallback } vm_module = dlopen(so_name, RTLD_LAZY | RTLD_GLOBAL); my_assert(vm_module != NULL, "dvm module is null"); runtime_module = dlopen ("libandroid_runtime.so", RTLD_LAZY | RTLD_GLOBAL); my_assert(runtime_module != NULL, "runtime module is null"); create_java_vm = dlsym (vm_module, "JNI_CreateJavaVM"); my_assert(create_java_vm != NULL, "unable to resolve JNI_CreateJavaVM"); FILE *pipe = popen("pm path com.tencent.mm", "r"); if (!pipe) { puts("have you installed wechat?"); abort(); } char wechat_path[MAX_PATH]; fgets(wechat_path, MAX_PATH, pipe); if (strstr(wechat_path, "package:") != wechat_path) { puts("unable to retrive package info"); abort(); } char class_path[MAX_PATH]; snprintf(class_path, MAX_PATH, "-Djava.class.path=%s", wechat_path + strlen("package:")); int i = 0; /* for debugging */ options[i++].optionString = "-verbose:jni"; options[i++].optionString = "-verbose:gc"; options[i++].optionString = "-verbose:class"; options[i++].optionString = "-Xdebug"; /* for debugging */ options[i++].optionString = "-Xcheck:jni"; options[i++].optionString = class_path; args.version = JNI_VERSION_1_6; args.nOptions = i; args.options = options; args.ignoreUnrecognized = JNI_TRUE; result = create_java_vm(vm, env, &args); my_assert(result == JNI_OK, "unable to create jvm"); register_natives = dlsym (runtime_module, "registerFrameworkNatives"); if (register_natives != NULL) { result = register_natives(*env); my_assert(result == JNI_OK, "unable to register natives"); } else { register_natives_legacy = dlsym(runtime_module, "Java_com_android_internal_util_WithFramework_registerNatives"); my_assert(register_natives_legacy != NULL, "unable to resolve android.internal.util.WithFramework.registerNatives"); result = register_natives_legacy(*env, NULL); my_assert(result == JNI_OK, "unable to register natives using legacy function"); } } JNIEXPORT void InitializeSignalChain() { } JNIEXPORT void ClaimSignalChain() { } JNIEXPORT void UnclaimSignalChain() { } JNIEXPORT void InvokeUserSignalHandler() { } JNIEXPORT void EnsureFrontOfChain() { } void device_id(char *id, size_t len) { JavaVM *vm; JNIEnv *env; init_jvm(&vm, &env); jstring filename = (*env)->NewStringUTF(env, DATA_PATH"MicroMsg/CompatibleInfo.cfg"); jclass clsFileInputStream = (*env)->FindClass(env, "java/io/FileInputStream"); jclass clsObjectInputStream = (*env)->FindClass(env, "java/io/ObjectInputStream"); jclass clsHashMap = (*env)->FindClass(env, "java/util/HashMap"); jmethodID constructor = (*env)->GetMethodID(env, clsFileInputStream, "", "(Ljava/lang/String;)V"); jobject fileInputStream = (*env)->NewObject(env, clsFileInputStream, constructor, filename); constructor = (*env)->GetMethodID(env, clsObjectInputStream, "", "(Ljava/io/InputStream;)V"); jobject objInputStream = (*env)->NewObject(env, clsObjectInputStream, constructor, fileInputStream); jmethodID readObject = (*env)->GetMethodID(env, clsObjectInputStream, "readObject", "()Ljava/lang/Object;"); jobject hashmap = (*env)->CallObjectMethod(env, objInputStream, readObject); // cast to hash map jmethodID get = (*env)->GetMethodID(env, clsHashMap, "get", "(Ljava/lang/Object;)Ljava/lang/Object;"); jmethodID toString = (*env)->GetMethodID(env, (*env)->FindClass(env, "java/lang/Object"), "toString", "()Ljava/lang/String;"); jclass clsInteger = (*env)->FindClass(env, "java/lang/Integer"); jmethodID valueOf = (*env)->GetStaticMethodID(env, clsInteger, "valueOf", "(I)Ljava/lang/Integer;"); jobject key = (*env)->CallStaticObjectMethod(env, clsInteger, valueOf, 258); jstring val = (*env)->CallObjectMethod(env, hashmap, get, key); strncpy(id, (*env)->GetStringUTFChars(env, val, 0), len); (*env)->DeleteLocalRef(env, filename); (*env)->DeleteLocalRef(env, fileInputStream); (*env)->DeleteLocalRef(env, objInputStream); (*env)->DeleteLocalRef(env, hashmap); (*env)->DeleteLocalRef(env, key); (*env)->DeleteLocalRef(env, val); vm->DestroyJavaVM(); } // does not work on certain devices // int get_imei(char *imei) // { // FILE* pipe; // char line[MAX_PATH]; // char stripped[MAX_PATH]; // char *left, *right, *p; // size_t len, pos = 0; // pipe = popen("service call iphonesubinfo 1", "r"); // if (!pipe) { // return UNABLE_TO_CALL_SERVICE; // } // while(!feof(pipe)) { // if (fgets(line, MAX_PATH, pipe)) { // left = strstr(line, "'"); // if (!left) continue; // left++; // right = strstr(left, "'"); // if (!right) continue; // for (p = left; p < right && pos < IMEI_LEN - 1; p++) // if (*p != '.') // imei[pos++] = *p; // } // } // imei[IMEI_LEN - 1] = '\0'; // pclose(pipe); // return OK; // } int get_uin(char *uin, size_t len) { FILE *fp = fopen(DATA_PATH"shared_prefs/system_config_prefs.xml", "r"); char line[MAX_PATH]; char *p; size_t pos = 0; // todo: parse xml with Jni ??? while(!feof(fp)) { if (fgets(line, MAX_PATH, fp)) { if (!strstr(line, "name=\"default_uin\"")) continue; for (p = strstr(line, "value=\""); *p; p++) { if (*p < '0' || *p > '9') continue; if (*p == '"') break; // end if (pos > len - 1) return INSUFFICIENT_BUF; // insufficient length uin[pos++] = *p; } } } return OK; } int md5_hex(const char *raw, char *output) { static const char hexchars[] = "0123456789abcdef"; unsigned char digest[MD5_DIGEST_LENGTH]; unsigned int pos = 0; void *lib = dlopen("libcrypto.so", 0); MD5INIT_FN_PTR MD5_Init = (MD5INIT_FN_PTR) dlsym(lib, "MD5_Init"); MD5UPDATE_FN_PTR MD5_Update = (MD5UPDATE_FN_PTR) dlsym(lib, "MD5_Update"); MD5FINAL_FN_PTR MD5_Final = (MD5FINAL_FN_PTR) dlsym(lib, "MD5_Final"); MD5_CTX ctx; MD5_Init(&ctx); MD5_Update(&ctx, (unsigned char *)raw, strlen(raw)); MD5_Final(digest, &ctx); for (unsigned i = 0; i < MD5_DIGEST_LENGTH; i++) { unsigned char b = digest[i]; output[pos++] = hexchars[b >> 4]; output[pos++] = hexchars[b & 0xF]; } output[pos] = '\0'; return 0; } int passwd(const char *imei, const char *uin, char *buf) { char md5[HEX_DIGEST_LENGTH]; size_t len = IMEI_LEN + strlen(uin) + 1; char *joint = calloc(len, sizeof(char)); snprintf(joint, len, "%s%s", imei, uin); printf("joint: %s\n", joint); md5_hex(joint, md5); strncpy(buf, md5, WECHAT_KEY_LEN); buf[8] = '\0'; free(joint); return 0; } static int callback(void *data, int argc, char **argv, char **azColName){ int i; char timestamp[DATE_LEN]; char datetime[DATE_LEN]; char *remark = NULL; for (i = 0; i < argc; i++) { char *col = azColName[i]; char *val = argv[i]; if (STREQ(col, "remark")) { remark = strlen(val) ? val : NULL; } else if (STREQ(col, "nickname")) { printf("%s ", remark ? remark : val); } else if (STREQ(col, "createTime")) { int slice = strlen(val) - 3; // divided by 1000 in decimal :) if (slice > DATE_LEN) { fprintf(stderr, "invalid time stamp"); exit(-1); } strncpy(timestamp, val, slice); time_t ts = (time_t)atoi(timestamp); struct tm * timeinfo; timeinfo = localtime(&ts); strftime(datetime, DATE_LEN, "%Y-%m-%d %H:%M:%S", timeinfo); printf("%s\n", datetime); } else if (STREQ(col, "imgPath") && val) { printf("[图片]\n"); } else if (STREQ(col, "content") && val) { printf("%s\n", val); } } // for(i = 0; i < argc; i++){ // printf("%s = %s\n", azColName[i], argv[i] ? argv[i] : "NULL"); // } printf("\n"); return 0; } int query(const char *uin, const char *key) { void *lib = dlopen(DATA_PATH"lib/libmmdb.so", 0); char *zErrMsg = NULL; // todo: move to .init SQLITE3_KEY_FN_PTR sqlite3_key = dlsym(lib, "sqlite3_key"); SQLITE3_OPEN_V2_FN_PTR sqlite3_open_v2 = dlsym(lib, "sqlite3_open_v2"); SQLITE3_CLOSE_V2_FN_PTR sqlite3_close_v2 = dlsym(lib, "sqlite3_close_v2"); SQLITE3_EXEC_FN_PTR sqlite3_exec = dlsym(lib, "sqlite3_exec"); SQLITE3_FREE_FN_PTR sqlite3_free = dlsym(lib, "sqlite3_free"); const char *(*sqlite3_errmsg)(sqlite3*) = dlsym(lib, "sqlite3_errmsg"); char digest[HEX_DIGEST_LENGTH]; size_t len = strlen(uin) + 2 + 1; char *mmuin = calloc(len, sizeof(char)); snprintf(mmuin, len, "mm%s", uin); printf("%s\n", mmuin); md5_hex(mmuin, digest); char path[MAX_PATH]; snprintf(path, MAX_PATH, DATA_PATH"MicroMsg/%s/EnMicroMsg.db", digest); printf(DATA_PATH"MicroMsg/%s\n", digest); printf("password: %s\n", key); printf("latest "RECORD_COUNT" message history\n"); sqlite3* db; int rc = sqlite3_open_v2(path, &db, SQLITE_OPEN_READONLY, NULL); if (rc == SQLITE_OK) { printf("database is now open, status: %d\n", rc); sqlite3_key(db, key, WECHAT_KEY_LEN); sqlite3_exec(db, "PRAGMA cipher_use_hmac = OFF;" "PRAGMA cipher_page_size = 1024;" "PRAGMA kdf_iter = 4000;", NULL, NULL, &zErrMsg); char *sql = "SELECT rcontact.conRemark AS remark, rcontact.nickname AS nickname, " "message.createTime, message.content, message.imgPath FROM message " "LEFT JOIN rcontact ON rcontact.username = message.talker " "ORDER BY createTime DESC limit "RECORD_COUNT";"; // latest 200 rc = sqlite3_exec(db, sql, callback, 0, &zErrMsg); if ( rc != SQLITE_OK ) { fprintf(stderr, "SQL error: %s\n", zErrMsg); sqlite3_free(zErrMsg); } else { fprintf(stdout, "Message history dump has finished.\n"); } } else { printf("error: %s\n", sqlite3_errmsg(db)); } sqlite3_close_v2(db); free(mmuin); return OK; } int main (int argc, char *argv[]) { char devid[MAX_PATH]; char uin[MAX_PATH]; device_id(devid, MAX_PATH); printf("devid: %s\n", devid); get_uin(uin, MAX_PATH); printf("uin: %s\n", uin); char pw[MAX_PATH]; passwd(devid, uin, pw); printf("passwd: %s\n", pw); query(uin, pw); return 0; }