mirror of
https://github.com/hicccc77/WeFlow.git
synced 2026-03-24 23:06:51 +00:00
fix: 图片密钥内存扫描通过子进程调用解决 task_for_pid 权限问题
Electron 进程缺少 com.apple.security.cs.debugger entitlement, 导致 ScanMemoryForImageKey 中的 task_for_pid 调用失败(kr=5)。 新增 image_scan_helper 子进程包装程序(与 xkey_helper 方案一致): - 新建 resources/image_scan_helper.c:dlopen libwx_key.dylib 并调用 ScanMemoryForImageKey,通过 JSON stdout 返回结果 - 新建 resources/image_scan_entitlements.plist:包含 debugger 和 allow-unsigned-executable-memory entitlements - 编译为 universal binary(x86_64 + arm64)并 ad-hoc 签名 - 修改 keyServiceMac.ts _scanMemoryForAesKey():优先 spawn image_scan_helper 子进程,失败时 fallback 到直接调 dylib Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -43,6 +43,26 @@ export class KeyServiceMac {
|
|||||||
throw new Error('xkey_helper not found')
|
throw new Error('xkey_helper not found')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private getImageScanHelperPath(): string {
|
||||||
|
const isPackaged = app.isPackaged
|
||||||
|
const candidates: string[] = []
|
||||||
|
|
||||||
|
if (isPackaged) {
|
||||||
|
candidates.push(join(process.resourcesPath, 'resources', 'image_scan_helper'))
|
||||||
|
candidates.push(join(process.resourcesPath, 'image_scan_helper'))
|
||||||
|
} else {
|
||||||
|
const cwd = process.cwd()
|
||||||
|
candidates.push(join(cwd, 'resources', 'image_scan_helper'))
|
||||||
|
candidates.push(join(app.getAppPath(), 'resources', 'image_scan_helper'))
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const path of candidates) {
|
||||||
|
if (existsSync(path)) return path
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error('image_scan_helper not found')
|
||||||
|
}
|
||||||
|
|
||||||
private getDylibPath(): string {
|
private getDylibPath(): string {
|
||||||
const isPackaged = app.isPackaged
|
const isPackaged = app.isPackaged
|
||||||
const candidates: string[] = []
|
const candidates: string[] = []
|
||||||
@@ -463,6 +483,36 @@ export class KeyServiceMac {
|
|||||||
|
|
||||||
private async _scanMemoryForAesKey(pid: number, ciphertext: Buffer): Promise<string | null> {
|
private async _scanMemoryForAesKey(pid: number, ciphertext: Buffer): Promise<string | null> {
|
||||||
const ciphertextHex = ciphertext.toString('hex')
|
const ciphertextHex = ciphertext.toString('hex')
|
||||||
|
|
||||||
|
// 优先通过 image_scan_helper 子进程调用(有 debugger entitlement)
|
||||||
|
try {
|
||||||
|
const helperPath = this.getImageScanHelperPath()
|
||||||
|
const result = await new Promise<string | null>((resolve, reject) => {
|
||||||
|
const child = spawn(helperPath, [String(pid), ciphertextHex], { stdio: ['ignore', 'pipe', 'pipe'] })
|
||||||
|
let stdout = ''
|
||||||
|
child.stdout.on('data', (chunk: Buffer) => { stdout += chunk.toString() })
|
||||||
|
child.stderr.on('data', (chunk: Buffer) => { console.log('[image_scan_helper]', chunk.toString().trim()) })
|
||||||
|
child.on('error', reject)
|
||||||
|
child.on('close', () => {
|
||||||
|
try {
|
||||||
|
const lines = stdout.split(/\r?\n/).map(x => x.trim()).filter(Boolean)
|
||||||
|
const last = lines[lines.length - 1]
|
||||||
|
if (!last) { resolve(null); return }
|
||||||
|
const payload = JSON.parse(last)
|
||||||
|
resolve(payload?.success && payload?.aesKey ? payload.aesKey : null)
|
||||||
|
} catch {
|
||||||
|
resolve(null)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
setTimeout(() => { try { child.kill('SIGTERM') } catch {} }, 30_000)
|
||||||
|
})
|
||||||
|
return result
|
||||||
|
} catch (e: any) {
|
||||||
|
console.warn('[KeyServiceMac] image_scan_helper unavailable, fallback to dylib:', e?.message)
|
||||||
|
}
|
||||||
|
|
||||||
|
// fallback: 直接调 dylib(Electron 进程可能没有 task_for_pid 权限)
|
||||||
|
if (!this.initialized) await this.initialize()
|
||||||
const aesKey = this.ScanMemoryForImageKey(pid, ciphertextHex)
|
const aesKey = this.ScanMemoryForImageKey(pid, ciphertextHex)
|
||||||
return aesKey || null
|
return aesKey || null
|
||||||
}
|
}
|
||||||
|
|||||||
10
resources/image_scan_entitlements.plist
Normal file
10
resources/image_scan_entitlements.plist
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>com.apple.security.cs.debugger</key>
|
||||||
|
<true/>
|
||||||
|
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
|
||||||
|
<true/>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
BIN
resources/image_scan_helper
Executable file
BIN
resources/image_scan_helper
Executable file
Binary file not shown.
77
resources/image_scan_helper.c
Normal file
77
resources/image_scan_helper.c
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
/*
|
||||||
|
* image_scan_helper - 轻量包装程序
|
||||||
|
* 加载 libwx_key.dylib 并调用 ScanMemoryForImageKey
|
||||||
|
* 用法: image_scan_helper <pid> <ciphertext_hex>
|
||||||
|
* 输出: JSON {"success":true,"aesKey":"..."} 或 {"success":false,"error":"..."}
|
||||||
|
*/
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <dlfcn.h>
|
||||||
|
#include <libgen.h>
|
||||||
|
#include <mach-o/dyld.h>
|
||||||
|
|
||||||
|
typedef const char* (*ScanMemoryForImageKeyFn)(int pid, const char* ciphertext);
|
||||||
|
typedef void (*FreeStringFn)(const char* str);
|
||||||
|
|
||||||
|
int main(int argc, char* argv[]) {
|
||||||
|
if (argc != 3) {
|
||||||
|
fprintf(stderr, "Usage: %s <pid> <ciphertext_hex>\n", argv[0]);
|
||||||
|
printf("{\"success\":false,\"error\":\"invalid arguments\"}\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int pid = atoi(argv[1]);
|
||||||
|
const char* ciphertext_hex = argv[2];
|
||||||
|
|
||||||
|
if (pid <= 0) {
|
||||||
|
printf("{\"success\":false,\"error\":\"invalid pid\"}\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 定位 dylib: 与自身同目录下的 libwx_key.dylib */
|
||||||
|
char exe_path[4096];
|
||||||
|
uint32_t size = sizeof(exe_path);
|
||||||
|
if (_NSGetExecutablePath(exe_path, &size) != 0) {
|
||||||
|
printf("{\"success\":false,\"error\":\"cannot get executable path\"}\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
char* dir = dirname(exe_path);
|
||||||
|
char dylib_path[4096];
|
||||||
|
snprintf(dylib_path, sizeof(dylib_path), "%s/libwx_key.dylib", dir);
|
||||||
|
|
||||||
|
void* handle = dlopen(dylib_path, RTLD_LAZY);
|
||||||
|
if (!handle) {
|
||||||
|
printf("{\"success\":false,\"error\":\"dlopen failed: %s\"}\n", dlerror());
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
ScanMemoryForImageKeyFn scan_fn = (ScanMemoryForImageKeyFn)dlsym(handle, "ScanMemoryForImageKey");
|
||||||
|
if (!scan_fn) {
|
||||||
|
printf("{\"success\":false,\"error\":\"symbol not found: ScanMemoryForImageKey\"}\n");
|
||||||
|
dlclose(handle);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
FreeStringFn free_fn = (FreeStringFn)dlsym(handle, "FreeString");
|
||||||
|
|
||||||
|
fprintf(stderr, "[image_scan_helper] calling ScanMemoryForImageKey(pid=%d, ciphertext=%s)\n", pid, ciphertext_hex);
|
||||||
|
|
||||||
|
const char* result = scan_fn(pid, ciphertext_hex);
|
||||||
|
|
||||||
|
if (result && strlen(result) > 0) {
|
||||||
|
/* 检查是否是错误 */
|
||||||
|
if (strncmp(result, "ERROR", 5) == 0) {
|
||||||
|
printf("{\"success\":false,\"error\":\"%s\"}\n", result);
|
||||||
|
} else {
|
||||||
|
printf("{\"success\":true,\"aesKey\":\"%s\"}\n", result);
|
||||||
|
}
|
||||||
|
if (free_fn) free_fn(result);
|
||||||
|
} else {
|
||||||
|
printf("{\"success\":false,\"error\":\"no key found\"}\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
dlclose(handle);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user