aur/dll_patch.py
2024-06-29 16:28:14 -07:00

142 lines
4.5 KiB
Python

lea64 = b'\x48\x8d'
call64 = b'\xFF'
little = lambda x: int.from_bytes(x, byteorder='little')
# iterate over matches for lea64
def leas(f, start=None, end=None, op=lea64):
match = f.find(op, start, end)
while match != -1:
yield match
match = f.find(op, match + 1, end)
# give the position and loaded virtual address for each lea match
def addr(f, start=None, end=None, op=lea64, reg=1):
arg = len(op) + reg
for i in leas(f, start, end, op):
yield [i, i + arg + 4 + little(f[i + arg: i + arg + 4])]
# VA offsets for .text and .rdata
def offsets(f):
# .text567_size_want
off = (f.index(b".text"), f.index(b".rdata"))
return [a - b for a, b in zip(
[little(f[i + 0xc : i + 0x10]) for i in off],
[little(f[i + 0x14 : i + 0x18]) for i in off],
)]
# gives storage locations of target phrases accounting for offsets
def rel32(f, target=b'Could not verify RL version'):
text, rdata = offsets(f)
return f.index(target) + rdata - text
# gives positions of references to the target phrases
def refs(f, rel, it):
return tuple(i for i, x in it if x == rel)
optionals = (
(2, "Magic"),
(1, "MajorLinkerVersion"),
(1, "MinorLinkerVersion"),
(4, "SizeOfCode"),
(4, "SizeOfInitializedData"),
(4, "SizeOfUninitializedData"),
(4, "AddressOfEntryPoint"),
(4, "BaseOfCode"),
(8, "ImageBase"),
(4, "SectionAlignment"),
(4, "FileAlignment"),
(2, "MajorOperatingSystemVersion"),
(2, "MinorOperatingSystemVersion"),
(2, "MajorImageVersion"),
(2, "MinorImageVersion"),
(2, "MajorSubsystemVersion"),
(2, "MinorSubsystemVersion"),
(4, "Win32VersionValue"),
(4, "SizeOfImage"),
(4, "SizeOfHeaders"),
(4, "CheckSum"),
(2, "Subsystem"),
(2, "DllCharacteristics"),
(8, "SizeOfStackReserve"),
(8, "SizeOfStackCommit"),
(8, "SizeOfHeapReserve"),
(8, "SizeOfHeapCommit"),
(4, "LoaderFlags"),
(4, "NumberOfRvaAndSizes"),
)
def header(name, offset=0):
for size, field in optionals:
if field == name:
return (offset, offset + size)
offset += size
raise Exception()
# gives offset and size of DLL directory
def idata(f, off=None):
off = off or offsets(f)
magic = f.index(b'\x0b\x02')
iaddr = header(optionals[-1][1], magic + 0xc)
isize = header(optionals[-1][1], magic + 0x10)
return little(f[slice(*iaddr)]) - off[1], little(f[slice(*isize)])
ox = lambda x: tuple(map(hex, x))
# gives IAT start and end
def user32(f, dlls=None, size=None, off=None):
off = off or offsets(f)
dlls, size = idata(f, off) if dlls is None else (dlls, size)
rows = [dlls + i for i in range(0, size, 20)]
rvas = [little(f[i + 0xc : i + 0x10]) for i in rows]
tabs = [little(f[i + 0x10 : i + 0x14]) for i in rows]
matching = b'USER32.dll'
substr = lambda i: f[i - off[1] : i - off[1] + len(matching)]
ptrs = [n for n, i in enumerate(rvas) if i != 0 and substr(i) == matching]
assert len(ptrs) == 1
ptr = ptrs[0]
end = min(i for i in tabs if i > tabs[ptr])
return tabs[ptr] - off[1], end - off[1]
# gives start of 8 byte IAT line as a .text RVA
def fns(f, iat=None, end=None, off=None):
off = off or offsets(f)
iat, end = user32(f, off=off) if iat is None else (dlls, size)
rvas = [little(f[i : i + 4]) for i in range(iat, end, 8)]
vas = [i - off[1] for i in rvas if i != 0]
hints = [f[i : i + 2] for i in vas]
names = [f[i + 2 : f.index(b'\x00', i + 2)] for i in vas]
return iat + names.index(b'MessageBoxA') * 8 + off[1] - off[0]
def calls(f, dbg=True):
loads, msg = refs(f, rel32(f), addr(f)), fns(f)
res = [refs(f, msg, (next(addr(f, ref, op=call64)),))[0] for ref in loads]
if dbg:
print(ox(res))
return res
def mask(f, update=b'\xB8\x06\x00\x00\x00', space=6):
prev, out = 0, b''
for pos in calls(f):
out += f[prev:pos] + update + b'\x90' * (space - len(update))
prev = pos + space
out += f[prev:]
assert len(f) == len(out)
return out
def dll(path=None, dest=None):
global Path
from pathlib import Path
path = Path(__file__).parents[0] if path is None else Path(path)
path = path if path.is_file() else path / "bakkesmod.dll"
with open(path, "rb") as fp:
f = fp.read()
f = mask(f)
dest = path.parents[0] if dest is None else dest
dest = dest if dest.is_file() else dest / "bakkesmod_promptless.dll"
with open(dest, "wb") as fp:
fp.write(f)
if __name__ == "__main__":
import sys
dll(*sys.argv[1:])