From 8bee66d404a8bdf0f675c13fe29a0fa464528911 Mon Sep 17 00:00:00 2001 From: hicccc77 <98377878+hicccc77@users.noreply.github.com> Date: Tue, 10 Mar 2026 22:43:47 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20macOS=20=E5=AF=86=E9=92=A5=E8=8E=B7?= =?UTF-8?q?=E5=8F=96=E5=AE=8C=E6=95=B4=E5=AE=9E=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - KeyServiceMac 实现内存扫描 - 复刻 Windows 的图片密钥扫描逻辑 - 使用 Xkey dylib 的 ScanMemoryForImageKey - 更新 libwx_key.dylib (102KB) --- electron/services/keyServiceMac.ts | 234 +++++++++++++++++++++++++++++ resources/libwx_key.dylib | Bin 0 -> 103936 bytes 2 files changed, 234 insertions(+) create mode 100644 electron/services/keyServiceMac.ts create mode 100755 resources/libwx_key.dylib diff --git a/electron/services/keyServiceMac.ts b/electron/services/keyServiceMac.ts new file mode 100644 index 0000000..f322658 --- /dev/null +++ b/electron/services/keyServiceMac.ts @@ -0,0 +1,234 @@ +import { app } from 'electron' +import { join } from 'path' +import { existsSync, readdirSync, readFileSync, statSync } from 'fs' + +type DbKeyResult = { success: boolean; key?: string; error?: string; logs?: string[] } +type ImageKeyResult = { success: boolean; xorKey?: number; aesKey?: string; error?: string } + +export class KeyServiceMac { + private koffi: any = null + private lib: any = null + private initialized = false + + private GetDbKey: any = null + private ScanMemoryForImageKey: any = null + private FreeString: any = null + + private getDylibPath(): string { + const isPackaged = app.isPackaged + const candidates: string[] = [] + + if (process.env.WX_KEY_DYLIB_PATH) { + candidates.push(process.env.WX_KEY_DYLIB_PATH) + } + + if (isPackaged) { + candidates.push(join(process.resourcesPath, 'resources', 'libwx_key.dylib')) + candidates.push(join(process.resourcesPath, 'libwx_key.dylib')) + } else { + const cwd = process.cwd() + candidates.push(join(cwd, 'resources', 'libwx_key.dylib')) + candidates.push(join(app.getAppPath(), 'resources', 'libwx_key.dylib')) + } + + for (const path of candidates) { + if (existsSync(path)) return path + } + + throw new Error('libwx_key.dylib not found') + } + + async initialize(): Promise { + if (this.initialized) return + + try { + this.koffi = require('koffi') + const dylibPath = this.getDylibPath() + + if (!existsSync(dylibPath)) { + throw new Error('libwx_key.dylib not found: ' + dylibPath) + } + + this.lib = this.koffi.load(dylibPath) + + this.GetDbKey = this.lib.func('const char* GetDbKey()') + this.ScanMemoryForImageKey = this.lib.func('const char* ScanMemoryForImageKey(int pid, const char* ciphertext)') + this.FreeString = this.lib.func('void FreeString(const char* str)') + + this.initialized = true + } catch (e: any) { + throw new Error('Failed to initialize KeyServiceMac: ' + e.message) + } + } + + async autoGetDbKey( + timeoutMs = 60_000, + onStatus?: (message: string, level: number) => void + ): Promise { + if (!this.initialized) { + await this.initialize() + } + + try { + onStatus?.('正在获取数据库密钥...', 0) + + const keyPtr = this.GetDbKey() + if (!keyPtr) { + onStatus?.('获取失败:WeChat 未运行或无法附加', 2) + return { success: false, error: 'WeChat 未运行或无法附加' } + } + + const key = this.koffi.decode(keyPtr, 'char', -1) + this.FreeString(keyPtr) + + onStatus?.('密钥获取成功', 1) + return { success: true, key } + } catch (e: any) { + onStatus?.('获取失败: ' + e.message, 2) + return { success: false, error: e.message } + } + } + + async autoGetImageKey( + accountPath?: string, + onStatus?: (message: string) => void, + wxid?: string + ): Promise { + onStatus?.('macOS 请使用内存扫描方式') + return { success: false, error: 'macOS 请使用内存扫描方式' } + } + + async autoGetImageKeyByMemoryScan( + userDir: string, + onProgress?: (message: string) => void + ): Promise { + if (!this.initialized) { + await this.initialize() + } + + try { + // 1. 查找模板文件获取密文和 XOR 密钥 + onProgress?.('正在查找模板文件...') + let result = await this._findTemplateData(userDir, 32) + let { ciphertext, xorKey } = result + + if (ciphertext && xorKey === null) { + onProgress?.('未找到有效密钥,尝试扫描更多文件...') + result = await this._findTemplateData(userDir, 100) + xorKey = result.xorKey + } + + if (!ciphertext) return { success: false, error: '未找到 V2 模板文件,请先在微信中查看几张图片' } + if (xorKey === null) return { success: false, error: '未能从模板文件中计算出有效的 XOR 密钥' } + + onProgress?.(`XOR 密钥: 0x${xorKey.toString(16).padStart(2, '0')},正在查找微信进程...`) + + // 2. 找微信 PID + const pid = await this.findWeChatPid() + if (!pid) return { success: false, error: '微信进程未运行,请先启动微信' } + + onProgress?.(`已找到微信进程 PID=${pid},正在扫描内存...`) + + // 3. 持续轮询内存扫描 + const deadline = Date.now() + 60_000 + let scanCount = 0 + while (Date.now() < deadline) { + scanCount++ + onProgress?.(`第 ${scanCount} 次扫描内存,请在微信中打开图片大图...`) + const aesKey = await this._scanMemoryForAesKey(pid, ciphertext) + if (aesKey) { + onProgress?.('密钥获取成功') + return { success: true, xorKey, aesKey } + } + await new Promise(r => setTimeout(r, 5000)) + } + + return { success: false, error: '60 秒内未找到 AES 密钥' } + } catch (e: any) { + return { success: false, error: `内存扫描失败: ${e.message}` } + } + } + + private async _findTemplateData(userDir: string, limit: number = 32): Promise<{ ciphertext: Buffer | null; xorKey: number | null }> { + const V2_MAGIC = Buffer.from([0x07, 0x08, 0x56, 0x32, 0x08, 0x07]) + + const collect = (dir: string, results: string[], maxFiles: number) => { + if (results.length >= maxFiles) return + try { + for (const entry of readdirSync(dir, { withFileTypes: true })) { + if (results.length >= maxFiles) break + const full = join(dir, entry.name) + if (entry.isDirectory()) collect(full, results, maxFiles) + else if (entry.isFile() && entry.name.endsWith('_t.dat')) results.push(full) + } + } catch { } + } + + const files: string[] = [] + collect(userDir, files, limit) + + files.sort((a, b) => { + try { return statSync(b).mtimeMs - statSync(a).mtimeMs } catch { return 0 } + }) + + let ciphertext: Buffer | null = null + const tailCounts: Record = {} + + for (const f of files.slice(0, 32)) { + try { + const data = readFileSync(f) + if (data.length < 8) continue + + if (data.subarray(0, 6).equals(V2_MAGIC) && data.length >= 2) { + const key = `${data[data.length - 2]}_${data[data.length - 1]}` + tailCounts[key] = (tailCounts[key] ?? 0) + 1 + } + + if (!ciphertext && data.subarray(0, 6).equals(V2_MAGIC) && data.length >= 0x1F) { + ciphertext = data.subarray(0xF, 0x1F) + } + } catch { } + } + + let xorKey: number | null = null + let maxCount = 0 + for (const [key, count] of Object.entries(tailCounts)) { + if (count > maxCount) { + maxCount = count + const [x, y] = key.split('_').map(Number) + const k = x ^ 0xFF + if (k === (y ^ 0xD9)) xorKey = k + } + } + + return { ciphertext, xorKey } + } + + private async _scanMemoryForAesKey(pid: number, ciphertext: Buffer): Promise { + const ciphertextHex = ciphertext.toString('hex') + const aesKeyPtr = this.ScanMemoryForImageKey(pid, ciphertextHex) + + if (!aesKeyPtr) return null + + const aesKey = this.koffi.decode(aesKeyPtr, 'char', -1) + this.FreeString(aesKeyPtr) + + return aesKey + } + + private async findWeChatPid(): Promise { + const { execSync } = await import('child_process') + try { + const output = execSync('pgrep -x WeChat', { encoding: 'utf8' }) + const pid = parseInt(output.trim()) + return isNaN(pid) ? null : pid + } catch { + return null + } + } + + cleanup(): void { + this.lib = null + this.initialized = false + } +} diff --git a/resources/libwx_key.dylib b/resources/libwx_key.dylib new file mode 100755 index 0000000000000000000000000000000000000000..661c866bc183243b06a5f22a5c1effb98b40dc1d GIT binary patch literal 103936 zcmeIbeO#1P-ame>nE?hw2L+YXWPEBdTM-16)EoqK(9sO=A zcBR~Md)P&*U0}CeYPHoiZA?=ucav;gd$a6?6*kE&(Y@1ap^Wg%y+yK#)<{n2 ztziy-(rhlb+4IV~p+Nqw&XBzH*u?_)OSmzI?G6gV)wwwY4YDRK%#_bP8d%1E!=W-TgNA>)DRMW;$0&&d8h z+LufquV%BoWKB^?fw`z;c_{(~rng;AuTU;8(Sd3B(k7d#ZwrjsoR*TEVotqtewL&y zKK-C=C3*sA(mOJg4FxfqSCsZh?fcVfllh|bD8BEZ`kde+U=v?P7_-@wF@J7)T86|G zlpG*E2LA4*-H{Sa$@{V(q%RDm8x7-+8?0ryw!*mLqP#T~=9QMU;|tcp%~&F14k{1j zvk47sqD*f>1!@74EGezypr^xLvWl@nFy9Am6p26nJdk^ujNiy=T*+AR4LpVdqkO`C z@{Zj$B>s259Cbc9E%D<&CIHjJNM+HXvFf;+%QEN}np@$=`G-vpZ3Ozt@2>|v5 z@ymT!+#-v$ya??mE~VQSY0+~XKii8TWu$K}w^D78$sadve7syC6n+Yfj~~WnqkHNd zUSe(hOkW*RKEqIN2G|roZecn2iL=^EN-WkmVmfXKs$6>I*^7z`;#ONq3QDbUX(S88 zV2rvmlH(TCvjO&4nWlPI9z_C*1QZD<5>O+$MATzff-eAQ7#$Y!}fp0e>|_oYwzocOmWpDA(~VDXTjw3icgFo!2}JN zL>4nnO+RAq?>ug--XD8U%DpMeQtnGJFWVJmg?42mwwjh{77`j3GFo|FFT9$^oPKt_8ug=e7r7ZBL(I8bKc|}DL+c@f-f;+ zo&D@C+SM1wPzhY~kaQm&xI3}s0qLbU4?!ocqIAAu^vS|W42%G8D+Qo1%;N$nicTsHOmcj5TiC9mc(FYE#`b16AE0N0D~!�R% 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 literal 0 HcmV?d00001