Prepare documented WhatsApp injector with portable Python bundle
This commit is contained in:
commit
d2b621bce7
49 files changed
+5737
No files matched your search
@@ -0,0 +1,91 @@
|
||||
"""Find WhatsApp Desktop pairing QR payload candidates using read-only process RAM.
|
||||
|
||||
No screenshots, browser, debug connection or injected code. Finding a payload
|
||||
does not establish that its server reference is active; no session is created.
|
||||
"""
|
||||
import argparse, base64, ctypes, datetime as dt, hashlib, json, re, struct, sys
|
||||
from pathlib import Path
|
||||
from .ram import Memory, discover
|
||||
|
||||
REA_EVIDENCE = 'ev_6c3a7930e51a95d162c3634ecdc0b73652cb640663681a7cb9962a5b45a9c1da'
|
||||
PATTERN = re.compile(r'(?:https://wa\.me/settings/linked_devices#)?[A-Za-z0-9+/=_:-]{8,512},[A-Za-z0-9+/]{43}=,[A-Za-z0-9+/]{43}=,[A-Za-z0-9+/]{43}=,8(?:,https://faq\.whatsapp\.com/r/ld)?')
|
||||
|
||||
def valid_payload(value):
|
||||
fields = value.removeprefix('https://wa.me/settings/linked_devices#').split(',')
|
||||
if len(fields) not in (5, 6) or fields[4] != '8': return False
|
||||
if len(fields) == 6 and fields[5] != 'https://faq.whatsapp.com/r/ld': return False
|
||||
try: return all(len(base64.b64decode(f, validate=True)) == 32 for f in fields[1:4])
|
||||
except ValueError: return False
|
||||
|
||||
def scan(mem, budget_mb):
|
||||
scanned, seen = 0, set()
|
||||
budget = budget_mb * 1024 * 1024
|
||||
for region in mem.regions():
|
||||
for offset in range(0, region['size'], 1024 * 1024):
|
||||
if scanned >= budget: return
|
||||
address = region['base'] + offset
|
||||
size = min(1024 * 1024, region['size'] - offset, budget - scanned)
|
||||
data = mem.read(address, min(size + 2048, region['size'] - offset))
|
||||
scanned += size
|
||||
variants = [(data.decode('latin-1'), 1, 0)]
|
||||
if b',\x008\x00' in data:
|
||||
for alignment in (0, 1):
|
||||
tail = len(data) - (len(data) - alignment) % 2
|
||||
variants.append((data[alignment:tail].decode('utf-16-le', errors='surrogatepass'), 2, alignment))
|
||||
for text, width, alignment in variants:
|
||||
for match in PATTERN.finditer(text):
|
||||
payload = match.group()
|
||||
if not valid_payload(payload): continue
|
||||
start = address + alignment + match.start() * width
|
||||
header = mem.read(start - 12, 12)
|
||||
if len(header) != 12: continue
|
||||
map_tag, _, length = struct.unpack('<III', header)
|
||||
if not map_tag & 1 or length != len(payload) or (start - 12) % 4: continue
|
||||
raw = payload.encode('latin-1' if width == 1 else 'utf-16-le')
|
||||
if mem.read(start, len(raw)) != raw: continue
|
||||
digest = hashlib.sha256(payload.encode()).hexdigest()
|
||||
if digest in seen: continue
|
||||
seen.add(digest)
|
||||
yield payload, {'pid': mem.pid, 'string_address': hex(start - 12),
|
||||
'encoding': 'Latin-1' if width == 1 else 'UTF-16LE',
|
||||
'payload_sha256': digest, 'server_validity': 'unverified',
|
||||
'validation': 'field lengths, candidate V8 string header, repeat memory read'}
|
||||
|
||||
def main(argv=None):
|
||||
p = argparse.ArgumentParser(description=__doc__)
|
||||
p.add_argument('--output-dir', type=Path, default=Path.cwd() / 'whatsapp-desktop-pairing-memory')
|
||||
p.add_argument('--max-mb', type=int, default=512)
|
||||
args = p.parse_args(argv)
|
||||
import qrcode
|
||||
if sys.platform != 'win32' or ctypes.sizeof(ctypes.c_void_p) != 8: p.error('64-bit Windows Python required')
|
||||
if not 16 <= args.max_mb <= 4096: p.error('--max-mb must be 16..4096')
|
||||
targets = discover()
|
||||
candidates, diagnostics = [], []
|
||||
args.output_dir.mkdir(parents=True, exist_ok=True)
|
||||
for target in targets:
|
||||
pid = target['ProcessId']
|
||||
try: mem = Memory(pid)
|
||||
except OSError as error:
|
||||
diagnostics.append({'pid': pid, 'error': str(error)}); continue
|
||||
try:
|
||||
for payload, metadata in scan(mem, args.max_mb):
|
||||
path = args.output_dir / ('pairing-candidate-' + metadata['payload_sha256'][:16] + '.png')
|
||||
qrcode.make(payload).save(path)
|
||||
metadata['file'] = str(path.resolve())
|
||||
candidates.append(metadata)
|
||||
diagnostics.append({'pid': pid, 'bytes_read': mem.read_bytes, 'debug_flags_present': target['DebugFlagsPresent']})
|
||||
finally: mem.close()
|
||||
report = {'timestamp_utc': dt.datetime.now(dt.timezone.utc).isoformat(),
|
||||
'mode': 'ReadProcessMemory; running Desktop renderer descendants only',
|
||||
'rea_static_evidence': REA_EVIDENCE, 'candidates': candidates, 'diagnostics': diagnostics,
|
||||
'limitations': ['Payloads can be expired or unreachable heap objects; active registration is not verified.',
|
||||
'Bounded scan of private readable memory; Cons/external strings or separately stored fields are not reconstructed.',
|
||||
'No new server reference is requested; a linked app may have no pairing payload.',
|
||||
'PNG candidates contain temporary pairing credentials. JSON omits raw payload and private keys.']}
|
||||
path = args.output_dir / 'report.json'
|
||||
path.write_text(json.dumps(report, indent=2), encoding='utf-8')
|
||||
print(json.dumps({'status': 'unverified_candidates_saved' if candidates else 'no_pairing_payload_found',
|
||||
'candidates': len(candidates), 'targets': len(targets), 'report': str(path.resolve())}))
|
||||
return 0 if candidates else 2
|
||||
|
||||
if __name__ == '__main__': raise SystemExit(main())
|
||||
Reference in new issue
Block a user