Prepare documented WhatsApp injector with portable Python bundle

This commit is contained in:
Ops Agent committed 2026-10-11 07:57:48 +02:00
commit d2b621bce7
49 files changed
+5737

No files matched your search

+109
View File
@@ -0,0 +1,109 @@
"""Shared verified Desktop process and window discovery."""
import ctypes
import hashlib
from pathlib import Path
SUPPORTED_BUILD = "2.2639.100.0"
CORE_SHA256 = "c6da3c67eda66a8d48ff2ae11f68085f1f8250111138a6ae384d38eefb4da536"
def check_core_build(path):
if 'WhatsAppDesktop_' + SUPPORTED_BUILD + '_' not in str(path):
raise ValueError('Unsupported WhatsApp build: ' + str(path) +
'; supported build is ' + SUPPORTED_BUILD + '. No hook installed.')
if hashlib.sha256(Path(path).read_bytes()).hexdigest() != CORE_SHA256:
raise ValueError('Unknown WebView2 Core DLL hash. No hook installed.')
def visible_ui_threads(pid):
"""Inspect window ownership; no clicks, navigation or window messages."""
import ctypes
from ctypes import wintypes
user32 = ctypes.WinDLL('user32', use_last_error=True)
user32.GetWindowThreadProcessId.argtypes = [wintypes.HWND, ctypes.POINTER(wintypes.DWORD)]
user32.GetWindowThreadProcessId.restype = wintypes.DWORD
user32.IsWindowVisible.argtypes = [wintypes.HWND]
user32.IsWindowVisible.restype = wintypes.BOOL
callback_type = ctypes.WINFUNCTYPE(wintypes.BOOL, wintypes.HWND, wintypes.LPARAM)
threads = set()
def visit(hwnd, parameter):
process = wintypes.DWORD()
thread = user32.GetWindowThreadProcessId(hwnd, ctypes.byref(process))
if process.value == pid and user32.IsWindowVisible(hwnd):
threads.add(thread)
return True
callback = callback_type(visit)
user32.EnumWindows.argtypes = [callback_type, wintypes.LPARAM]
user32.EnumWindows.restype = wintypes.BOOL
if not user32.EnumWindows(callback, 0):
raise ctypes.WinError(ctypes.get_last_error())
return threads
def detect_desktop_pid(requested_pid=None):
"""Find the running supported Desktop host using Python and Windows APIs."""
import ctypes
from ctypes import wintypes as w
import hashlib
class ProcessEntry(ctypes.Structure):
_fields_ = [('dwSize',w.DWORD),('cntUsage',w.DWORD),('th32ProcessID',w.DWORD),
('th32DefaultHeapID',ctypes.c_size_t),('th32ModuleID',w.DWORD),
('cntThreads',w.DWORD),('th32ParentProcessID',w.DWORD),
('pcPriClassBase',w.LONG),('dwFlags',w.DWORD),('szExeFile',w.WCHAR*260)]
class ModuleEntry(ctypes.Structure):
_fields_ = [('dwSize',w.DWORD),('th32ModuleID',w.DWORD),('th32ProcessID',w.DWORD),
('GlblcntUsage',w.DWORD),('ProccntUsage',w.DWORD),
('modBaseAddr',ctypes.POINTER(w.BYTE)),('modBaseSize',w.DWORD),
('hModule',w.HMODULE),('szModule',w.WCHAR*256),('szExePath',w.WCHAR*260)]
kernel = ctypes.WinDLL('kernel32',use_last_error=True)
kernel.CreateToolhelp32Snapshot.argtypes = [w.DWORD,w.DWORD]
kernel.CreateToolhelp32Snapshot.restype = w.HANDLE
kernel.CloseHandle.argtypes = [w.HANDLE]
kernel.CloseHandle.restype = w.BOOL
for name, entry in [('Process32FirstW',ProcessEntry),('Process32NextW',ProcessEntry),
('Module32FirstW',ModuleEntry),('Module32NextW',ModuleEntry)]:
fn = getattr(kernel,name)
fn.argtypes = [w.HANDLE,ctypes.POINTER(entry)]
fn.restype = w.BOOL
invalid = ctypes.c_void_p(-1).value
snapshot = kernel.CreateToolhelp32Snapshot(2,0)
if snapshot == invalid:
raise ctypes.WinError(ctypes.get_last_error())
roots = []
try:
entry = ProcessEntry(dwSize=ctypes.sizeof(ProcessEntry))
exists = kernel.Process32FirstW(snapshot,ctypes.byref(entry))
while exists:
if entry.szExeFile.casefold() == 'whatsapp.root.exe':
roots.append(entry.th32ProcessID)
exists = kernel.Process32NextW(snapshot,ctypes.byref(entry))
finally:
kernel.CloseHandle(snapshot)
candidates = []
rejected = []
for pid in roots:
if requested_pid is not None and pid != requested_pid:
continue
snapshot = kernel.CreateToolhelp32Snapshot(8|16,pid)
if snapshot == invalid:
continue
try:
entry = ModuleEntry(dwSize=ctypes.sizeof(ModuleEntry))
exists = kernel.Module32FirstW(snapshot,ctypes.byref(entry))
while exists:
if entry.szModule.casefold() == 'microsoft.web.webview2.core.dll':
path = Path(entry.szExePath)
if len(visible_ui_threads(pid)) == 1:
try:
check_core_build(path)
candidates.append(pid)
except (ValueError, OSError) as error:
rejected.append(str(error))
break
exists = kernel.Module32NextW(snapshot,ctypes.byref(entry))
except OSError:
continue
finally:
kernel.CloseHandle(snapshot)
if not candidates:
raise ValueError('; '.join(rejected) if rejected else 'No supported running WhatsApp Desktop host with one identifiable UI thread found; start WhatsApp normally')
if len(candidates) != 1:
raise ValueError('Multiple active Desktop hosts found; specify --pid: '+', '.join(map(str,candidates)))
return candidates[0]