mirror of
https://github.com/archlinux/aur.git
synced 2026-02-09 14:34:42 +01:00
175 lines
5.6 KiB
Python
175 lines
5.6 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],
|
|
)]
|
|
|
|
targets = [
|
|
b'Could not verify RL version',
|
|
b'Dunno how the user got here',
|
|
b'User wishes to inject anyway',
|
|
]
|
|
|
|
# gives storage locations of target phrases accounting for offsets
|
|
def rel32(f):
|
|
text, rdata = offsets(f)
|
|
return [f.index(i) + rdata - text for i in targets]
|
|
|
|
# gives positions of references to the target phrases
|
|
def refs(f, rel=None, it=None):
|
|
default = rel is None and it is None
|
|
rel, it = rel or rel32(f), it or addr(f)
|
|
res = tuple(zip(*[(rel.index(x), i) for i, x in it if x in rel]))
|
|
if default:
|
|
assert res[0] == (0, 1, 2, 0, 1, 2)
|
|
return (res[1][:3], res[1][3:])
|
|
return res
|
|
|
|
jcc32 = [b'\x0F' + int.to_bytes(i, 1, 'little') for i in range(0x80, 0x90)]
|
|
jcc8 = list(bytes(range(0x70, 0x80)))
|
|
|
|
# gives "inject anyway" jmp RVAs
|
|
def branches(f, ref=None):
|
|
ref = ref or refs(f)
|
|
def inner(a, b):
|
|
seq = f[a : b]
|
|
op32 = [seq.index(i) for i in jcc32 if i in seq]
|
|
op8 = [seq.index(i) for i in jcc8 if i in seq]
|
|
loc32 = [a + i + 6 + little(seq[i + 2 : i + 6]) for i in op32]
|
|
loc8 = [a + i + 2 + little(seq[i + 1 : i + 2]) for i in op8]
|
|
return loc8 + loc32
|
|
return [max(m for m in inner(i, j) if m <= k) for i, j, k in ref]
|
|
|
|
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("NumberOfRvaAndSizes", magic + 0xc)
|
|
isize = header("NumberOfRvaAndSizes", 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 shunts(f, dbg=True):
|
|
loads, msg = refs(f), fns(f)
|
|
calls = [refs(f, (msg,), addr(f, *ref[:2], call64))[1][0] for ref in loads]
|
|
skips = branches(f, loads)
|
|
paired = tuple(zip(calls, skips))
|
|
if dbg:
|
|
print(tuple(map(ox, paired)))
|
|
return paired
|
|
|
|
jmp = lambda x: b'\xE9' + int.to_bytes(x, 4, "little")
|
|
|
|
def mask(f, space=6):
|
|
prev, out = 0, b''
|
|
for pos, end in shunts(f):
|
|
update = jmp(end - pos - 5)
|
|
assert len(update) == 5
|
|
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, dest=None):
|
|
global Path
|
|
from pathlib import Path
|
|
path = 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:])
|
|
|