import ctypes import hashlib import json import os import re import sys
import psutil from win32com.client import Dispatch from pymem import Pymem import pymem import hmac
ReadProcessMemory = ctypes.windll.kernel32.ReadProcessMemory void_p = ctypes.c_void_p KEY_SIZE = 32 DEFAULT_PAGESIZE = 4096 DEFAULT_ITER = 64000
def validate_key(key, salt, first, mac_salt): byteKey = hashlib.pbkdf2_hmac("sha1", key, salt, DEFAULT_ITER, KEY_SIZE) mac_key = hashlib.pbkdf2_hmac("sha1", byteKey, mac_salt, 2, KEY_SIZE) hash_mac = hmac.new(mac_key, first[:-32], hashlib.sha1) hash_mac.update(b'\x01\x00\x00\x00')
if hash_mac.digest() == first[-32:-12]: return True else: return False
def get_exe_bit(file_path): """ 获取 PE 文件的位数: 32 位或 64 位 :param file_path: PE 文件路径(可执行文件) :return: 如果遇到错误则返回 64 """ try: with open(file_path, 'rb') as f: dos_header = f.read(2) if dos_header != b'MZ': print('get exe bit error: Invalid PE file') return 64 f.seek(60) pe_offset_bytes = f.read(4) pe_offset = int.from_bytes(pe_offset_bytes, byteorder='little')
f.seek(pe_offset + 4) machine_bytes = f.read(2) machine = int.from_bytes(machine_bytes, byteorder='little')
if machine == 0x14c: return 32 elif machine == 0x8664: return 64 else: print('get exe bit error: Unknown architecture: %s' % hex(machine)) return 64 except IOError: print('get exe bit error: File not found or cannot be opened') return 64
def get_exe_version(file_path): """ 获取 PE 文件的版本号 :param file_path: PE 文件路径(可执行文件) :return: 如果遇到错误则返回 """ file_version = Dispatch("Scripting.FileSystemObject").GetFileVersion(file_path) return file_version
def find_all(c: bytes, string: bytes, base_addr=0): """ 查找字符串中所有子串的位置 :param c: 子串 b'123' :param string: 字符串 b'123456789123' :return: """ return [base_addr + m.start() for m in re.finditer(re.escape(c), string)]
class BiasAddr: def __init__(self, account, mobile, name, key, db_path): print(f"[+] 初始化参数:") print(f" 账号: {account}") print(f" 手机: {mobile}") print(f" 名称: {name}") print(f" 密钥: {key[:10]}..." if key else " 密钥: 无") print(f" 数据库路径: {db_path}\n") self.account = account.encode("utf-8") self.mobile = mobile.encode("utf-8") self.name = name.encode("utf-8") self.key = bytes.fromhex(key) if key else b"" self.db_path = db_path if db_path and os.path.exists(db_path) else ""
self.process_name = "WeChat.exe" self.module_name = "WeChatWin.dll"
self.pm = None self.is_WoW64 = None self.process_handle = None self.pid = None self.version = None self.process = None self.exe_path = None self.address_len = None self.bits = 64 if sys.maxsize > 2 ** 32 else 32
def get_process_handle(self): try: print("[+] 正在获取微信进程...") self.pm = Pymem(self.process_name) self.pm.check_wow64() self.is_WoW64 = self.pm.is_WoW64 self.process_handle = self.pm.process_handle self.pid = self.pm.process_id self.process = psutil.Process(self.pid) self.exe_path = self.process.exe() self.version = get_exe_version(self.exe_path)
print(f"[+] 进程信息:") print(f" PID: {self.pid}") print(f" 路径: {self.exe_path}") print(f" 版本: {self.version}") print(f" WoW64: {self.is_WoW64}\n")
version_nums = list(map(int, self.version.split("."))) if version_nums[0] <= 3 and version_nums[1] <= 9 and version_nums[2] <= 2: self.address_len = 4 else: self.address_len = 8 return True, "" except pymem.exception.ProcessNotFound: return False, "[-] WeChat No Run"
def search_memory_value(self, value: bytes, module_name="WeChatWin.dll"): module = pymem.process.module_from_name(self.pm.process_handle, module_name) ret = self.pm.pattern_scan_module(value, module, return_multiple=True) ret = ret[-1] - module.lpBaseOfDll if len(ret) > 0 else 0 return ret
def get_key_bias1(self): try: byteLen = self.address_len
keyLenOffset = 0x8c if self.bits == 32 else 0xd0 keyWindllOffset = 0x90 if self.bits == 32 else 0xd8
module = pymem.process.module_from_name(self.process_handle, self.module_name) keyBytes = b'-----BEGIN PUBLIC KEY-----\n...' publicKeyList = pymem.pattern.pattern_scan_all(self.process_handle, keyBytes, return_multiple=True)
keyaddrs = [] for addr in publicKeyList: keyBytes = addr.to_bytes(byteLen, byteorder="little", signed=True) may_addrs = pymem.pattern.pattern_scan_module(self.process_handle, module, keyBytes, return_multiple=True) if may_addrs != 0 and len(may_addrs) > 0: for addr in may_addrs: keyLen = self.pm.read_uchar(addr - keyLenOffset) if keyLen != 32: continue keyaddrs.append(addr - keyWindllOffset)
return keyaddrs[-1] - module.lpBaseOfDll if len(keyaddrs) > 0 else 0 except: return 0
def search_key(self, key: bytes): key = re.escape(key) key_addr = self.pm.pattern_scan_all(key, return_multiple=False) key = key_addr.to_bytes(self.address_len, byteorder='little', signed=True) result = self.search_memory_value(key, self.module_name) return result
def get_key_bias2(self, wx_db_path):
addr_len = get_exe_bit(self.exe_path) // 8 db_path = wx_db_path
def read_key_bytes(h_process, address, address_len=8): array = ctypes.create_string_buffer(address_len) if ReadProcessMemory(h_process, void_p(address), array, address_len, 0) == 0: return "None" address = int.from_bytes(array, byteorder='little') key = ctypes.create_string_buffer(32) if ReadProcessMemory(h_process, void_p(address), key, 32, 0) == 0: return "None" key_bytes = bytes(key) return key_bytes
def verify_key(key, wx_db_path): KEY_SIZE = 32 DEFAULT_PAGESIZE = 4096 DEFAULT_ITER = 64000 with open(wx_db_path, "rb") as file: blist = file.read(5000) salt = blist[:16] byteKey = hashlib.pbkdf2_hmac("sha1", key, salt, DEFAULT_ITER, KEY_SIZE) first = blist[16:DEFAULT_PAGESIZE]
mac_salt = bytes([(salt[i] ^ 58) for i in range(16)]) mac_key = hashlib.pbkdf2_hmac("sha1", byteKey, mac_salt, 2, KEY_SIZE) hash_mac = hmac.new(mac_key, first[:-32], hashlib.sha1) hash_mac.update(b'\x01\x00\x00\x00')
if hash_mac.digest() != first[-32:-12]: return False return True
phone_type1 = "iphone\x00" phone_type2 = "android\x00" phone_type3 = "ipad\x00"
pm = pymem.Pymem("WeChat.exe") module_name = "WeChatWin.dll"
MicroMsg_path = os.path.join(db_path, "MSG", "MicroMsg.db")
module = pymem.process.module_from_name(pm.process_handle, module_name)
type1_addrs = pm.pattern_scan_module(phone_type1.encode(), module, return_multiple=True) type2_addrs = pm.pattern_scan_module(phone_type2.encode(), module, return_multiple=True) type3_addrs = pm.pattern_scan_module(phone_type3.encode(), module, return_multiple=True) type_addrs = type1_addrs if len(type1_addrs) >= 2 else type2_addrs if len( type2_addrs) >= 2 else type3_addrs if len(type3_addrs) >= 2 else "None" if type_addrs == "None": return 0 for i in type_addrs[::-1]: for j in range(i, i - 2000, -addr_len): key_bytes = read_key_bytes(pm.process_handle, j, addr_len) if key_bytes == "None": continue if verify_key(key_bytes, MicroMsg_path): return j - module.lpBaseOfDll return 0
def run(self, logging_path=False, version_list_path=None): if not self.get_process_handle()[0]: return {} print("[+] 开始搜索内存偏移...") mobile_bias = self.search_memory_value(self.mobile, self.module_name) print(f" 手机号偏移: 0x{mobile_bias:X}") name_bias = self.search_memory_value(self.name, self.module_name) print(f" 用户名偏移: 0x{name_bias:X}") account_bias = self.search_memory_value(self.account, self.module_name) print(f" 账号偏移: 0x{account_bias:X}") print("[+] 开始搜索密钥偏移...") key_bias = 0 key_bias = self.get_key_bias1() if key_bias <= 0 and self.key: print(" 方法1失败,尝试方法2...") key_bias = self.search_key(self.key) if key_bias <= 0 and self.db_path: print(" 方法2失败,尝试方法3...") key_bias = self.get_key_bias2(self.db_path) print(f" 密钥偏移: 0x{key_bias:X}\n")
rdata = {self.version: [name_bias, account_bias, mobile_bias, 0, key_bias]} print("[+] 搜索完成!") print(f" 结果: {json.dumps(rdata, indent=4)}\n") return rdata
def get_info_without_key(h_process, address, n_size=64): array = ctypes.create_string_buffer(n_size) if ReadProcessMemory(h_process, void_p(address), array, n_size, 0) == 0: return "None" array = bytes(array).split(b"\x00")[0] if b"\x00" in array else bytes(array) text = array.decode('utf-8', errors='ignore') return text.strip() if text.strip() != "" else "None"
def get_user_input(): print("\n[*] 请输入以下信息:") account = input("微信账号: ").strip() mobile = input("手机号码: ").strip() name = input("用户名称: ").strip() print("\n[*] 以下信息为可选,直接回车可跳过:") key = input("密钥(可选): ").strip() db_path = input("数据库路径(可选): ").strip() return account, mobile, name, key, db_path
def main(): try: account, mobile, name, key, db_path = get_user_input() if not all([account, mobile, name]): print("\n[-] 错误: 账号、手机号和用户名为必填项") sys.exit(1) bias = BiasAddr( account=account, mobile=mobile, name=name, key=key, db_path=db_path ) result = bias.run() if not result: print("\n[-] 未找到微信进程或搜索失败") sys.exit(1) except KeyboardInterrupt: print("\n\n[-] 用户取消操作") sys.exit(0) except Exception as e: print(f"\n[-] 发生错误: {str(e)}") sys.exit(1)
if __name__ == '__main__': main()
|