Files
helper/tests/test_contacts.py
T
2026-10-15 18:27:46 +08:00

99 lines
4.6 KiB
Python

"""默认微信名读取和缓存变化验证,不连接微信。"""
import struct
import unittest
from test_account import AccountMemory, BASE, CONTEXT, SELF
from wxbot_framework.account import CONTEXT_VTABLE_RVA, IDENTITY_GETTER_RVA
from wxbot_framework.contacts import (CACHE_PROVIDER_GETTER_RVA, CACHE_PROVIDER_VTABLE_RVA,
CONTACT_CACHE_VTABLE_RVA, read_contact_name)
PEER = 'wxid_contact_name'
PROVIDER, HEAD, NODE, CACHE, SENTINEL, BUCKETS, ENTRY, CONTACT, STRINGS = [
0x600000000 + i * 0x1000 for i in range(9)]
class ContactMemory(AccountMemory):
def __init__(self, name='DemoUser'):
super().__init__()
self.add(BASE + CONTEXT_VTABLE_RVA, 0x80)
self.write(BASE + CONTEXT_VTABLE_RVA + 0x18, struct.pack('<Q', BASE + IDENTITY_GETTER_RVA))
self.add(BASE + CACHE_PROVIDER_GETTER_RVA + 28, 8)
self.write(BASE + CACHE_PROVIDER_GETTER_RVA + 28, bytes.fromhex('48 83 b9 60 04 00 00 00'))
self.add(CONTEXT + 0x460, 8)
self.write(CONTEXT + 0x460, struct.pack('<Q', PROVIDER))
self.write(BASE + CONTEXT_VTABLE_RVA + 0x38, struct.pack('<Q', BASE + CACHE_PROVIDER_GETTER_RVA))
for address, length in ((PROVIDER, 0xC0), (HEAD, 0x40), (NODE, 0x40), (CACHE, 0x200),
(SENTINEL, 0x40), (BUCKETS, 32), (ENTRY, 0x40), (CONTACT, 0x100), (STRINGS, 1024)):
self.add(address, length)
self.q(PROVIDER, BASE + CACHE_PROVIDER_VTABLE_RVA)
self.q(PROVIDER + 0x90, HEAD)
self.q(PROVIDER + 0x98, 1)
self.q(HEAD, NODE)
self.q(NODE, HEAD)
self.q(NODE + 0x30, CACHE)
self.q(CACHE, BASE + CONTACT_CACHE_VTABLE_RVA)
self.q(CACHE + 0xD0, SENTINEL)
self.q(CACHE + 0xD8, 1)
self.q(CACHE + 0xE0, BUCKETS)
self.q(CACHE + 0xF8, 1)
hashed = 0xCBF29CE484222325
for byte in PEER.encode():
hashed = ((hashed ^ byte) * 0x100000001B3) & 0xFFFFFFFFFFFFFFFF
for slot in (0, 16):
self.write(BUCKETS + slot, struct.pack('<QQ', SENTINEL, SENTINEL))
self.write(BUCKETS + (hashed & 1) * 16, struct.pack('<QQ', ENTRY, ENTRY))
self.q(ENTRY + 0x30, CONTACT)
self.s(ENTRY + 0x10, PEER, STRINGS)
self.s(CONTACT + 8, PEER, STRINGS + 128)
self.s(CONTACT + 0xD8, name, STRINGS + 256)
def q(self, address, value):
self.write(address, struct.pack('<Q', value))
def s(self, address, value, buffer):
body = value.encode('utf-8')
if len(body) <= 15:
self.write(address, body.ljust(16, b'\0') + struct.pack('<QQ', len(body), 15))
else:
self.write(buffer, body)
self.write(address, struct.pack('<QQQQ', buffer, 0, len(body), 511))
class ContactTests(unittest.TestCase):
def test_verified_id_returns_wechat_name_for_inline_and_unicode_heap_strings(self):
for name in ('DemoUser', '微信原始名字🌸很长'):
ram = ContactMemory(name)
self.assertEqual(read_contact_name(ram.read, BASE, PEER, expected_account=SELF), name)
def test_other_account_and_unknown_member_never_get_someone_elses_name(self):
ram = ContactMemory()
self.assertIsNone(read_contact_name(ram.read, BASE, PEER, expected_account='wxid_other'))
self.assertIsNone(read_contact_name(ram.read, BASE, 'wxid_missing'))
ram.s(CONTACT + 8, 'wxid_other', STRINGS + 128)
self.assertIsNone(read_contact_name(ram.read, BASE, PEER))
def test_wrong_layout_and_unavailable_cache_are_skipped(self):
for address, value in ((PROVIDER, BASE + 1234), (CACHE, BASE + 5678),
(CACHE + 0xF8, 123), (CONTEXT + 0x460, 0)):
ram = ContactMemory()
ram.q(address, value)
self.assertIsNone(read_contact_name(ram.read, BASE, PEER))
def test_cache_changes_during_read_do_not_return_a_torn_name(self):
ram = ContactMemory()
changed = False
def changing(address, size):
nonlocal changed
result = ram.read(address, size)
if address == CONTACT + 0xD8 and not changed:
changed = True
ram.q(CACHE + 0xD8, 2)
return result
self.assertIsNone(read_contact_name(changing, BASE, PEER))
self.assertEqual(read_contact_name(ram.read, BASE, PEER), 'DemoUser')
def test_empty_and_control_character_names_are_rejected(self):
for name in ('', '错误\n昵称', 'x' * 65):
ram = ContactMemory(name)
self.assertIsNone(read_contact_name(ram.read, BASE, PEER))