|
|
@@ -0,0 +1,375 @@ |
|
|
#include <dlfcn.h> |
|
|
#include <fcntl.h> |
|
|
#include <assert.h> |
|
|
#include <stdio.h> |
|
|
#include <stdlib.h> |
|
|
#include <time.h> |
|
|
|
|
|
#include <jni.h> |
|
|
|
|
|
#include <sys/system_properties.h> |
|
|
|
|
|
#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[6]; |
|
|
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, "<init>", "(Ljava/lang/String;)V"); |
|
|
jobject fileInputStream = (*env)->NewObject(env, clsFileInputStream, constructor, filename); |
|
|
|
|
|
constructor = (*env)->GetMethodID(env, clsObjectInputStream, "<init>", "(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); |
|
|
} |
|
|
|
|
|
// 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; |
|
|
} |