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:
Felix
2026-03-13 15:18:52 +11:00
parent 19ab4409a3
commit bff9e87096
4 changed files with 137 additions and 0 deletions

View File

@@ -43,6 +43,26 @@ export class KeyServiceMac {
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 {
const isPackaged = app.isPackaged
const candidates: string[] = []
@@ -463,6 +483,36 @@ export class KeyServiceMac {
private async _scanMemoryForAesKey(pid: number, ciphertext: Buffer): Promise<string | null> {
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: 直接调 dylibElectron 进程可能没有 task_for_pid 权限)
if (!this.initialized) await this.initialize()
const aesKey = this.ScanMemoryForImageKey(pid, ciphertextHex)
return aesKey || null
}