79 lines
3.9 KiB
Python
79 lines
3.9 KiB
Python
"""Rename a DLL's exports after linking: python -I tools/pe_rename_exports.py IN.dll OUT.dll MAP.txt
|
|
|
|
MAP.txt: lines "placeholder<TAB>new name". MSVC's linker cuts an aliased export name at its first '@' (in a .def file
|
|
and in /EXPORT alike), so decorated C++ names cannot be exported under another symbol's code directly. Instead the DLL
|
|
is linked with placeholder export names, and this script writes a new export directory - the same functions (export
|
|
address table and ordinals unchanged), names replaced and sorted (the loader and GetProcAddress search the name table
|
|
by binary search) - into a new section, and points the export data directory at it.
|
|
"""
|
|
import struct, sys
|
|
|
|
|
|
def main():
|
|
src, dst, mapfile = sys.argv[1:4]
|
|
rename = dict(line.rstrip('\n').split('\t') for line in open(mapfile, encoding='latin1') if '\t' in line)
|
|
d = bytearray(open(src, 'rb').read())
|
|
pe = struct.unpack_from('<I', d, 0x3c)[0]
|
|
nsec = struct.unpack_from('<H', d, pe + 6)[0]
|
|
opt_size = struct.unpack_from('<H', d, pe + 20)[0]
|
|
opt = pe + 24
|
|
sec_align, file_align = struct.unpack_from('<II', d, opt + 32)
|
|
dd = opt + 96 # data directories (PE32)
|
|
exp_rva, exp_size = struct.unpack_from('<II', d, dd)
|
|
shdr = opt + opt_size
|
|
secs = [struct.unpack_from('<8sIIIIIIHHI', d, shdr + 40 * i) for i in range(nsec)]
|
|
|
|
def off(rva):
|
|
for s in secs:
|
|
if s[2] <= rva < s[2] + max(s[1], s[3]):
|
|
return s[4] + rva - s[2]
|
|
raise ValueError(hex(rva))
|
|
|
|
e = off(exp_rva)
|
|
(_, _, _, _, name_rva, base, n_funcs, n_names, eat_rva, names_rva, ords_rva) = struct.unpack_from('<IIHHIIIIIII', d, e)
|
|
dll_name = d[off(name_rva):d.index(b'\0', off(name_rva))]
|
|
entries, old_names = [], set()
|
|
for i in range(n_names):
|
|
nr = struct.unpack_from('<I', d, off(names_rva) + 4 * i)[0]
|
|
old = d[off(nr):d.index(b'\0', off(nr))].decode('latin1')
|
|
old_names.add(old)
|
|
ordinal = struct.unpack_from('<H', d, off(ords_rva) + 2 * i)[0]
|
|
entries.append((rename.get(old, old), ordinal))
|
|
missing = [p for p in rename if p not in old_names]
|
|
if missing:
|
|
sys.exit('placeholders not exported: %s' % missing[:5])
|
|
entries.sort(key=lambda e: e[0].encode('latin1')) # byte order, as the loader compares
|
|
|
|
# new section layout: directory (40) | name pointers | ordinals | dll name | names
|
|
new_rva = (secs[-1][2] + max(secs[-1][1], secs[-1][3]) + sec_align - 1) // sec_align * sec_align
|
|
blob = bytearray(40)
|
|
ptrs_at = len(blob)
|
|
blob += b'\0' * (4 * len(entries))
|
|
ords_at = len(blob)
|
|
blob += b'\0' * (2 * len(entries))
|
|
name_at = len(blob)
|
|
blob += dll_name + b'\0'
|
|
for i, (name, ordinal) in enumerate(entries):
|
|
struct.pack_into('<I', blob, ptrs_at + 4 * i, new_rva + len(blob))
|
|
struct.pack_into('<H', blob, ords_at + 2 * i, ordinal)
|
|
blob += name.encode('latin1') + b'\0'
|
|
struct.pack_into('<IIHHIIIIIII', blob, 0, 0, 0, 0, 0, new_rva + name_at, base, n_funcs, len(entries), eat_rva,
|
|
new_rva + ptrs_at, new_rva + ords_at)
|
|
vsize = len(blob)
|
|
raw_size = (vsize + file_align - 1) // file_align * file_align
|
|
raw_ptr = (len(d) + file_align - 1) // file_align * file_align
|
|
if shdr + 40 * (nsec + 1) > secs[0][4]:
|
|
sys.exit('no room for another section header')
|
|
struct.pack_into('<8sIIIIIIHHI', d, shdr + 40 * nsec, b'.edata2', vsize, new_rva, raw_size, raw_ptr, 0, 0, 0, 0,
|
|
0x40000040) # initialized data, readable
|
|
struct.pack_into('<H', d, pe + 6, nsec + 1)
|
|
struct.pack_into('<I', d, opt + 56, (new_rva + vsize + sec_align - 1) // sec_align * sec_align) # SizeOfImage
|
|
struct.pack_into('<II', d, dd, new_rva, vsize)
|
|
d += b'\0' * (raw_ptr - len(d))
|
|
d += blob + b'\0' * (raw_size - vsize)
|
|
open(dst, 'wb').write(bytes(d))
|
|
print('%s: %d exports renamed into section .edata2 (%d bytes)' % (dst, len(entries), vsize))
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|