fix: replace glob() with nftw() and add chunk overlap

- glob() does not support ** recursive matching on macOS (POSIX).
  Replace with nftw() + opendir to recursively walk db_storage/.
- Add overlap between memory chunks to catch x'...' patterns
  spanning chunk boundaries.
feat/daemon-cli
bbingz 2026-03-05 21:52:35 +08:00
parent 1f9ca3792a
commit d38d7ebf9c
2 changed files with 100 additions and 36 deletions

View File

@ -194,6 +194,37 @@ V2 文件结构: `[6B signature] [4B aes_size LE] [4B xor_size LE] [1B padding]`
- `media_*/media_*.db` - 媒体文件索引 - `media_*/media_*.db` - 媒体文件索引
- 其他: head_image, favorite, sns, emoticon 等 - 其他: head_image, favorite, sns, emoticon 等
## macOS 数据库密钥扫描 (WeChat 4.x)
macOS 版微信 4.x 使用 SQLCipher 4 加密本地数据库,密钥格式为 `x'<64hex_key><32hex_salt>'`。C 版扫描器通过 Mach VM API 扫描微信进程内存提取密钥。
### 前置条件
- macOS (Apple Silicon / Intel)
- WeChat 4.x (macOS 版)
- Xcode Command Line Tools: `xcode-select --install`
- 微信需要 ad-hoc 签名(或安装了防撤回补丁):
`sudo codesign --force --deep --sign - /Applications/WeChat.app`
### 编译和使用
```bash
# 编译
cc -O2 -o find_all_keys_macos find_all_keys_macos.c -framework Foundation
# 运行(自动查找微信进程、扫描内存、匹配 DB salt
sudo ./find_all_keys_macos
# 或指定 PID
sudo ./find_all_keys_macos <pid>
```
输出 `all_keys.json`,格式兼容 `decrypt_db.py`,可直接用于解密:
```bash
python3 decrypt_db.py
```
## 免责声明 ## 免责声明
本工具仅用于学习和研究目的,用于解密**自己的**微信数据。请遵守相关法律法规,不要用于未经授权的数据访问。 本工具仅用于学习和研究目的,用于解密**自己的**微信数据。请遵守相关法律法规,不要用于未经授权的数据访问。

View File

@ -22,8 +22,10 @@
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <unistd.h> #include <unistd.h>
#include <glob.h> #include <dirent.h>
#include <ftw.h>
#include <pwd.h> #include <pwd.h>
#include <sys/stat.h>
#include <mach/mach.h> #include <mach/mach.h>
#include <mach/mach_vm.h> #include <mach/mach_vm.h>
@ -39,6 +41,40 @@ typedef struct {
char full_pragma[100]; char full_pragma[100];
} key_entry_t; } key_entry_t;
/* Forward declaration */
static int read_db_salt(const char *path, char *salt_hex_out);
/* nftw callback state for collecting DB files */
#define MAX_DBS 256
static char g_db_salts[MAX_DBS][33];
static char g_db_names[MAX_DBS][256];
static int g_db_count = 0;
static int nftw_collect_db(const char *fpath, const struct stat *sb,
int typeflag, struct FTW *ftwbuf) {
(void)sb; (void)ftwbuf;
if (typeflag != FTW_F) return 0;
size_t len = strlen(fpath);
if (len < 3 || strcmp(fpath + len - 3, ".db") != 0) return 0;
if (g_db_count >= MAX_DBS) return 0;
char salt[33];
if (read_db_salt(fpath, salt) != 0) return 0;
strcpy(g_db_salts[g_db_count], salt);
/* Extract relative path from db_storage/ */
const char *rel = strstr(fpath, "db_storage/");
if (rel) rel += strlen("db_storage/");
else {
rel = strrchr(fpath, '/');
rel = rel ? rel + 1 : fpath;
}
strncpy(g_db_names[g_db_count], rel, 255);
g_db_names[g_db_count][255] = '\0';
printf(" %s: salt=%s\n", g_db_names[g_db_count], salt);
g_db_count++;
return 0;
}
static int is_hex_char(unsigned char c) { static int is_hex_char(unsigned char c) {
return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'); return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F');
} }
@ -107,40 +143,32 @@ int main(int argc, char *argv[]) {
if (!home) home = "/root"; if (!home) home = "/root";
printf("User home: %s\n", home); printf("User home: %s\n", home);
/* Collect DB salts */ /* Collect DB salts by recursively walking db_storage directories.
* Note: POSIX glob() does not support ** recursive matching on macOS,
* so we use nftw() to walk the directory tree instead. */
printf("\nScanning for DB files...\n"); printf("\nScanning for DB files...\n");
glob_t g; char db_base_dir[512];
char pattern[512]; snprintf(db_base_dir, sizeof(db_base_dir),
snprintf(pattern, sizeof(pattern), "%s/Library/Containers/com.tencent.xinWeChat/Data/Documents/xwechat_files",
"%s/Library/Containers/com.tencent.xinWeChat/Data/Documents/"
"xwechat_files/*/db_storage/**/*.db",
home); home);
char db_salts[64][33]; /* Walk each account's db_storage directory */
char db_names[64][256]; /* relative path from db_storage, e.g. "contact/contact.db" */ DIR *xdir = opendir(db_base_dir);
int db_count = 0; if (xdir) {
struct dirent *ent;
if (glob(pattern, GLOB_NOSORT, NULL, &g) == 0) { while ((ent = readdir(xdir)) != NULL) {
for (size_t i = 0; i < g.gl_pathc && db_count < 64; i++) { if (ent->d_name[0] == '.') continue;
char salt[33]; char storage_path[768];
if (read_db_salt(g.gl_pathv[i], salt) == 0) { snprintf(storage_path, sizeof(storage_path),
strcpy(db_salts[db_count], salt); "%s/%s/db_storage", db_base_dir, ent->d_name);
/* Extract relative path from db_storage/ */ struct stat st;
const char *rel = strstr(g.gl_pathv[i], "db_storage/"); if (stat(storage_path, &st) == 0 && S_ISDIR(st.st_mode)) {
if (rel) rel += strlen("db_storage/"); nftw(storage_path, nftw_collect_db, 20, FTW_PHYS);
else {
rel = strrchr(g.gl_pathv[i], '/');
rel = rel ? rel + 1 : g.gl_pathv[i];
}
strncpy(db_names[db_count], rel, 255);
db_names[db_count][255] = '\0';
printf(" %s: salt=%s\n", db_names[db_count], salt);
db_count++;
} }
} }
globfree(&g); closedir(xdir);
} }
printf("Found %d encrypted DBs\n", db_count); printf("Found %d encrypted DBs\n", g_db_count);
/* Scan memory for x' patterns */ /* Scan memory for x' patterns */
printf("\nScanning memory for keys...\n"); printf("\nScanning memory for keys...\n");
@ -222,7 +250,12 @@ int main(int argc, char *argv[]) {
} }
mach_vm_deallocate(mach_task_self(), data, dc); mach_vm_deallocate(mach_task_self(), data, dc);
} }
ca += cs; /* Advance with overlap to catch patterns spanning chunk boundaries.
* Pattern is x'<96 hex chars>' = 99 bytes total. */
if (cs > HEX_PATTERN_LEN + 3)
ca += cs - (HEX_PATTERN_LEN + 3);
else
ca += cs;
} }
} }
addr += size; addr += size;
@ -241,9 +274,9 @@ int main(int argc, char *argv[]) {
int matched = 0; int matched = 0;
for (int i = 0; i < key_count; i++) { for (int i = 0; i < key_count; i++) {
const char *db = NULL; const char *db = NULL;
for (int j = 0; j < db_count; j++) { for (int j = 0; j < g_db_count; j++) {
if (strcmp(keys[i].salt_hex, db_salts[j]) == 0) { if (strcmp(keys[i].salt_hex, g_db_salts[j]) == 0) {
db = db_names[j]; db = g_db_names[j];
matched++; matched++;
break; break;
} }
@ -267,9 +300,9 @@ int main(int argc, char *argv[]) {
int first = 1; int first = 1;
for (int i = 0; i < key_count; i++) { for (int i = 0; i < key_count; i++) {
const char *db = NULL; const char *db = NULL;
for (int j = 0; j < db_count; j++) { for (int j = 0; j < g_db_count; j++) {
if (strcmp(keys[i].salt_hex, db_salts[j]) == 0) { if (strcmp(keys[i].salt_hex, g_db_salts[j]) == 0) {
db = db_names[j]; db = g_db_names[j];
break; break;
} }
} }