runtime capture pt2: vst3 loads AT ImageBase 0x180000000; 0x540658 = field [ctx+0x540658] not RVA; beacon->ctx script (racy)
This commit is contained in:
+95
-62
@@ -1,76 +1,109 @@
|
||||
#!/usr/bin/env python3
|
||||
import subprocess, os, glob, sys, time, struct
|
||||
"""rtdeep2.py — find DSP ctx object via phase-table pointer beacon, then dump window 0x540658 & neighbors."""
|
||||
import subprocess, time, glob, os, struct, sys
|
||||
import numpy as np
|
||||
|
||||
RPP = "/home/m/soothe-bt/render_long.rpp"
|
||||
proc = subprocess.Popen(['reaper', '-nosplash', '-renderproject', RPP],
|
||||
stdout=open('/tmp/rtdeep2.log', 'w'), stderr=subprocess.STDOUT)
|
||||
print('reaper', proc.pid, flush=True)
|
||||
RPP = sys.argv[1] if len(sys.argv) > 1 else '/home/m/soothe-bt/dual300.rpp'
|
||||
BEACON = 0x182615608 # VA of phase_table_1024 (static .data)
|
||||
|
||||
|
||||
def find_hosts():
|
||||
def vst_base(pid):
|
||||
lo = None
|
||||
for line in open(f'/proc/{pid}/maps').read().splitlines():
|
||||
if 'soothe2' not in line:
|
||||
continue
|
||||
a = int(line.split()[0].split('-')[0], 16)
|
||||
lo = a if lo is None else min(lo, a)
|
||||
return lo
|
||||
|
||||
|
||||
def readable_regions(pid):
|
||||
out = []
|
||||
for p in glob.glob('/proc/[0-9]*'):
|
||||
try:
|
||||
pid = int(os.path.basename(p))
|
||||
m = open(f'/proc/{pid}/maps').read()
|
||||
c = open(f'/proc/{pid}/cmdline', 'rb').read().decode('utf8', 'replace')
|
||||
if 'soothe2' in m and 'yabridge-host.exe' in c:
|
||||
out.append(pid)
|
||||
except Exception:
|
||||
pass
|
||||
for line in open(f'/proc/{pid}/maps').read().splitlines():
|
||||
p = line.split()[0]
|
||||
lo, hi = int(p.split('-')[0], 16), int(p.split('-')[1], 16)
|
||||
if 'vst3' in line:
|
||||
continue # skip module itself
|
||||
out.append((lo, hi))
|
||||
return out
|
||||
|
||||
|
||||
def scan(pid):
|
||||
# strategy: enumerate every readable chunk; build set of img addresses whose content is 0x18052xxxx
|
||||
# then find heap words equal to any such address
|
||||
mem = os.open(f'/proc/{pid}/mem', os.O_RDONLY)
|
||||
img_sp = set() # vtbl-slot candidates: addrs in module image holding 0x18052xxxx
|
||||
heap_addrs = [] # (va, chunk) heap chunks
|
||||
maps = open(f'/proc/{pid}/maps').read()
|
||||
for line in maps.splitlines():
|
||||
p = line.split()
|
||||
lo, hi = (int(x, 16) for x in p[0].split('-'))
|
||||
if 'r' not in p[1]:
|
||||
def find_beacon(pid):
|
||||
try:
|
||||
mem = os.open(f'/proc/{pid}/mem', os.O_RDONLY)
|
||||
except Exception:
|
||||
return []
|
||||
res = []
|
||||
for lo, hi in readable_regions(pid):
|
||||
sz = (hi - lo) & ~7
|
||||
if sz <= 0 or sz > 0x200000000:
|
||||
continue
|
||||
try:
|
||||
d = os.pread(mem, hi - lo, lo)
|
||||
buf = os.pread(mem, sz, lo)
|
||||
except Exception:
|
||||
continue
|
||||
if lo < 0x180000000 < hi or (0x180000000 <= lo < 0x183000000):
|
||||
for off in range(0, len(d) - 7, 8):
|
||||
q = struct.unpack_from('<Q', d, off)[0]
|
||||
if 0x180520000 <= q < 0x180555000:
|
||||
img_sp.add(lo + off)
|
||||
else:
|
||||
heap_addrs.append((lo, d))
|
||||
arr = np.frombuffer(buf, dtype='<u8')
|
||||
idx = np.where(arr == BEACON)[0]
|
||||
for i in idx:
|
||||
res.append(lo + int(i) * 8)
|
||||
os.close(mem)
|
||||
print('img SP-slot addrs found:', len(img_sp), flush=True)
|
||||
# find heap objects pointing into img_sp
|
||||
found = []
|
||||
for lo, d in heap_addrs:
|
||||
for s in img_sp:
|
||||
t = struct.pack('<Q', s)
|
||||
i = 0
|
||||
while True:
|
||||
i = d.find(t, i)
|
||||
if i < 0:
|
||||
break
|
||||
found.append((s, lo + i))
|
||||
i += 1
|
||||
return img_sp, found
|
||||
return res
|
||||
|
||||
res = {}
|
||||
t0 = time.time()
|
||||
while time.time() - t0 < 80:
|
||||
for pid in find_hosts():
|
||||
if pid in res:
|
||||
continue
|
||||
img_sp, found = scan(pid)
|
||||
res[pid] = (img_sp, found)
|
||||
print('PID', pid, 'objects pointing to SP-method vtbl:', len(found), flush=True)
|
||||
for s, a in found[:30]:
|
||||
print(f' slotslot {s:#x} object@{a:#x}', flush=True)
|
||||
time.sleep(0.4)
|
||||
proc.kill()
|
||||
print('done', flush=True)
|
||||
|
||||
def main():
|
||||
proc = subprocess.Popen(['reaper', '-nosplash', '-renderproject', RPP],
|
||||
stdout=open('/tmp/rtdeep2.log', 'w'), stderr=subprocess.STDOUT)
|
||||
t0 = time.time()
|
||||
host = None
|
||||
while time.time() - t0 < 60:
|
||||
for p in glob.glob('/proc/[0-9]*'):
|
||||
try:
|
||||
maps = open(f'/proc/{p}/maps').read()
|
||||
except Exception:
|
||||
continue
|
||||
if 'soothe2' not in maps:
|
||||
continue
|
||||
host = int(os.path.basename(p))
|
||||
break
|
||||
if host:
|
||||
break
|
||||
time.sleep(0.05)
|
||||
print(f'host={host}')
|
||||
if not host:
|
||||
proc.kill()
|
||||
return
|
||||
base = vst_base(host)
|
||||
# allow module load: plugin init first
|
||||
mem = os.open(f'/proc/{host}/mem', os.O_RDONLY)
|
||||
t1 = time.time()
|
||||
while time.time() - t1 < 50:
|
||||
hits = find_beacon(host)
|
||||
ctxs = set()
|
||||
for p in hits:
|
||||
for d in (0x540548, 0x540550, 0x540598):
|
||||
ctxc = p - d
|
||||
if 0x10000000 < ctxc < 0x7fff00000000:
|
||||
ctxs.add(ctxc)
|
||||
if ctxs:
|
||||
print(f't={time.time()-t1:.1f}s hits={len(hits)} ctx_cands={sorted(hex(c) for c in ctxs)}', flush=True)
|
||||
for ctx in sorted(ctxs):
|
||||
d = os.pread(mem, 4096, ctx + 0x540658)
|
||||
if len(d) < 4096:
|
||||
continue
|
||||
fl = np.frombuffer(d, dtype='<f4')
|
||||
if np.all(np.isfinite(fl)) and fl.min() >= -1 and fl.max() <= 2:
|
||||
print(f' ctx={hex(ctx)} WIN@+0x540658 head={[round(float(x),4) for x in fl[:8]]} mid={[round(float(x),4) for x in fl[1024:1032]]}', flush=True)
|
||||
blk = os.pread(mem, 0x8000, ctx + 0x540000)
|
||||
open(f'/tmp/ctx_{ctx:x}_win.bin', 'wb').write(blk)
|
||||
break
|
||||
time.sleep(0.2)
|
||||
os.close(mem)
|
||||
try:
|
||||
proc.wait(timeout=5)
|
||||
except Exception:
|
||||
proc.kill()
|
||||
print('done', flush=True)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Reference in New Issue
Block a user