Last active
March 28, 2024 10:56
-
-
Save ChiChou/36556fd412a9e3216abecf06e084e4d9 to your computer and use it in GitHub Desktop.
WeChat dump
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
LOCAL_PATH := $(call my-dir) | |
include $(CLEAR_VARS) | |
LOCAL_MODULE := loader | |
LOCAL_MODULE_TAGS := optional | |
LOCAL_SRC_FILES := loader.c | |
LOCAL_CPPFLAGS := -std=gnu++0x -Wall | |
LOCAL_LDLIBS := -L$(SYSROOT)/usr/lib -llog -pie -fPIE | |
LOCAL_LDFLAGS += -Wl,--export-dynamic |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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[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, "<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); | |
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; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <android/log.h> | |
#include <stddef.h> | |
#define PACKAGE "com.tencent.mm" | |
#define DATA_PATH "/data/data/"PACKAGE"/" | |
#define WECHAT_KEY_LEN 7 | |
#define MAX_PATH 256 | |
#define DATE_LEN 64 | |
#define RECORD_COUNT "200" | |
#define STREQ(a, b) (strcmp((a), (b)) == 0) | |
#define LOG_TAG "CHAITIN" | |
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__) | |
#define LOGW(...) __android_log_print(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__) | |
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__) | |
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__) | |
#define OK 0 | |
#define UNABLE_TO_CALL_SERVICE -1 | |
#define INSUFFICIENT_BUF -2 | |
/* openssl */ | |
#if defined(__LP32__) | |
#define MD5_LONG unsigned long | |
#elif defined(OPENSSL_SYS_CRAY) || defined(__ILP64__) | |
#define MD5_LONG unsigned long | |
#define MD5_LONG_LOG2 3 | |
#else | |
#define MD5_LONG unsigned int | |
#endif | |
#define MD5_CBLOCK 64 | |
#define MD5_LBLOCK (MD5_CBLOCK/4) | |
#define MD5_DIGEST_LENGTH 16 | |
#define HEX_DIGEST_LENGTH MD5_DIGEST_LENGTH * 2 + 1 | |
typedef struct MD5state_st | |
{ | |
MD5_LONG A,B,C,D; | |
MD5_LONG Nl,Nh; | |
MD5_LONG data[MD5_LBLOCK]; | |
unsigned int num; | |
} MD5_CTX; | |
typedef void (*MD5INIT_FN_PTR)(MD5_CTX *context); | |
typedef void (*MD5UPDATE_FN_PTR)(MD5_CTX *context, unsigned char *input, unsigned int inlen); | |
typedef void (*MD5FINAL_FN_PTR)(unsigned char *output, MD5_CTX *context); | |
/* end of openssl */ | |
/* sqlcipher */ | |
#define SQLITE_OPEN_READONLY 0x00000001 | |
#define SQLITE_OK 0 | |
typedef void sqlite3; /* we don't care about what exatcly this struct is defined */ | |
typedef int(*SQLITE3_KEY_FN_PTR) ( | |
sqlite3 *db, /* Database to be rekeyed */ | |
const void *pKey, int nKey /* The key, and the length of the key in bytes */ | |
); | |
typedef int (*SQLITE3_OPEN_V2_FN_PTR) ( | |
const char *filename, /* Database filename (UTF-8) */ | |
sqlite3 **ppDb, /* OUT: SQLite db handle */ | |
int flags, /* Flags */ | |
const char *zVfs /* Name of VFS module to use */ | |
); | |
typedef int (*SQLITE3_CLOSE_V2_FN_PTR) (sqlite3*); | |
typedef int (*SQLITE3_EXEC_FN_PTR) ( | |
sqlite3*, /* An open database */ | |
const char *sql, /* SQL to be evaluated */ | |
int (*callback)(void*,int,char**,char**), /* Callback function */ | |
void *, /* 1st argument to callback */ | |
char **errmsg /* Error msg written here */ | |
); | |
typedef void (*SQLITE3_FREE_FN_PTR) (void*); | |
/* end of sqlcipher */ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment