Skip to content

Instantly share code, notes, and snippets.

@ChiChou
Last active March 28, 2024 10:56
Show Gist options
  • Save ChiChou/36556fd412a9e3216abecf06e084e4d9 to your computer and use it in GitHub Desktop.
Save ChiChou/36556fd412a9e3216abecf06e084e4d9 to your computer and use it in GitHub Desktop.
WeChat dump
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
#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;
}
#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