Files
kefu/wechat_rpa/test_wxwork_key_auto.py
T
2026-09-21 10:34:06 +08:00

83 lines
4.1 KiB
Python

"""自动密钥提取:合成数据库/内存,不读取真实账号的内存。"""
import ctypes
import os
import tempfile
import unittest
from pathlib import Path
from unittest import mock
import wxwork_key as scanner
from test_crypto_roundtrip import build_encrypted_db
class AutomaticKeyScannerTest(unittest.TestCase):
def setUp(self):
temporary = tempfile.TemporaryDirectory()
self.addCleanup(temporary.cleanup)
self.path = str(Path(temporary.name) / "message.db")
self.key = bytes(range(1, 17))
Path(self.path).write_bytes(build_encrypted_db(self.key))
def test_binary_patterns_and_chunk_boundaries_return_hex_and_stop_early(self):
patterns = [self.key + b"\x01\x00\x00\x00sAlT",
b"\x10\x00\x00\x00" + self.key,
b"raw:" + self.key, self.key.hex().encode()]
for pattern in patterns:
with self.subTest(pattern_length=len(pattern)):
memory = b"-" * 59 + pattern + b"-" * 512
pm = mock.Mock()
pm.regions.return_value = [(0, len(memory))]
pm.read.side_effect = lambda base, size: memory[base:base+size]
with mock.patch.object(scanner, "_ProcessMemory", return_value=pm), mock.patch.object(scanner, "_CHUNK", 64):
result = scanner._scan_pid(123, [self.path])
self.assertEqual(result, {self.path: [self.key.hex()]})
self.assertLessEqual(pm.read.call_count, 2 + 2 * ((len(memory) + 63) // 64))
pm.close.assert_called_once_with()
def test_completed_accounts_skip_remaining_processes(self):
with mock.patch.object(scanner, "find_wxwork_pids", return_value=[1, 2, 3]), \
mock.patch.object(scanner, "_scan_pid", return_value={self.path: [self.key.hex()]}) as scan:
result = scanner.extract_keys_from_running([self.path], timeout=50)
self.assertEqual(result, {self.path: [self.key.hex()]})
self.assertEqual(scan.call_count, 1)
def test_deadline_preserves_partial_results_and_closes_memory(self):
pm = mock.Mock()
pm.regions.return_value = [(0, 64), (64, 64)]
pm.read.return_value = self.key + b"\x01\x00\x00\x00sAlT"
with mock.patch.object(scanner, "_ProcessMemory", return_value=pm), \
mock.patch.object(scanner.time, "monotonic", side_effect=[0, 0, 20]):
result = scanner._scan_pid(123, [self.path, "absent.db"], deadline=10)
self.assertEqual(result, {self.path: [self.key.hex()]})
pm.close.assert_called_once_with()
def test_expired_budget_does_not_start_scanning(self):
with mock.patch.object(scanner, "find_wxwork_pids", return_value=[1]), \
mock.patch.object(scanner, "_scan_pid") as scan:
self.assertEqual(scanner.extract_keys_from_running([self.path], timeout=0), {})
scan.assert_not_called()
def test_native_memory_api_reads_own_test_buffer_at_full_pointer_width(self):
payload = b"synthetic-native-memory-probe"
buffer = ctypes.create_string_buffer(payload)
pm = scanner._ProcessMemory(os.getpid())
try:
address = ctypes.addressof(buffer)
self.assertEqual(pm.read(address, len(payload)), payload)
self.assertTrue(any(base <= address < base + size for base, size in pm.regions(rw_only=True)))
finally:
pm.close()
def test_fast_salt_pass_reaches_later_regions_before_expensive_fallback(self):
pm = mock.Mock()
memory = b"-" * 192 + self.key + b"\x01\x00\x00\x00sAlT"
pm.regions.return_value = [(0, len(memory))]
pm.read.side_effect = lambda base, size: memory[base:base + size]
with mock.patch.object(scanner, "_ProcessMemory", return_value=pm), \
mock.patch.object(scanner, "_CHUNK", 64), \
mock.patch.object(scanner, "_candidates_codec", side_effect=AssertionError("fallback ran too early")):
result = scanner._scan_pid(123, [self.path])
self.assertEqual(result, {self.path: [self.key.hex()]})
self.assertEqual(pm.read.call_count, 4)