leakhunt/dll/leakfix/tools/list_imports.py
acbot 57b5e43d0e Initial commit — leak-hunt project complete
Five bugs identified and patched in retail Asheron's Call client:
- v3b: palette refcount over-increment (3-byte NOP at two sites)
- v5: RenderSurface PurgeResource no-op stub (vtable slot 2 thunk)
- v11: two dangling-pointer crash guards (NULL-check + reorder)
- v14: CEnvCell::Destroy ClipPlaneList leak (18-byte JMP to cleanup thunk)
- v22: unpacker stale-pointer SEH guard (whole-function __try/__except)

All five ship in leakfix.dll (117 KB, SHA d282f23c…) which is loaded
by acclient.exe at process start via PE import table patching by
tools/install_leakfix.py.

Controlled 15-client fleet soak: unpatched control died at 26h with
palette exhaustion; all 14 patched clients survived past that point
and reached ≥5-day uptime.

Residual ~15 MB/h growth traced to d3d9.dll's internal slab allocator
(260KB surface backing buffers retained after Release). See REPORT.md
§10 for the full investigation; conclusion is that it's unfixable from
outside d3d9.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-23 21:07:58 +02:00

47 lines
1.6 KiB
Python

"""list_imports.py <pe.exe> — list all DLLs imported by a PE file."""
import struct, sys
with open(sys.argv[1], "rb") as f:
data = f.read()
pe_off = struct.unpack_from("<I", data, 0x3c)[0]
opt_off = pe_off + 24
num_sec = struct.unpack_from("<H", data, pe_off + 6)[0]
size_opt = struct.unpack_from("<H", data, pe_off + 20)[0]
sec_off = opt_off + size_opt
imp_rva, imp_size = struct.unpack_from("<II", data, opt_off + 96 + 1*8)
# Build RVA->offset map
secs = []
for i in range(num_sec):
sh = sec_off + i*40
vaddr = struct.unpack_from("<I", data, sh+12)[0]
vsize = struct.unpack_from("<I", data, sh+8)[0]
rsize = struct.unpack_from("<I", data, sh+16)[0]
roff = struct.unpack_from("<I", data, sh+20)[0]
secs.append((vaddr, max(vsize, rsize), roff, bytes(data[sh:sh+8]).rstrip(b"\0").decode()))
def rva2off(rva):
for vaddr, vsize, roff, _ in secs:
if vaddr <= rva < vaddr + vsize:
return roff + (rva - vaddr)
return None
print(f"IMPORT dir RVA=0x{imp_rva:08x} size={imp_size}")
print(f"sections:")
for v, sz, r, n in secs:
print(f" {n:>10} vaddr=0x{v:08x} vsize={sz:>8} roff=0x{r:x}")
print("imports:")
pos = rva2off(imp_rva)
i = 0
while True:
desc = data[pos:pos+20]
if desc == b"\0"*20:
print(f" [{i}] (null terminator)")
break
ilt_rva, ts, fwc, name_rva, iat_rva = struct.unpack("<IIIII", desc)
name_off = rva2off(name_rva)
name = bytes(data[name_off:data.index(0, name_off)]).decode("ascii", "replace") if name_off else "?"
print(f" [{i}] {name} (ILT=0x{ilt_rva:08x} IAT=0x{iat_rva:08x})")
pos += 20
i += 1