/..

#CONTENT

#TOP

patchelf.py
PYTHON
#!/usr/bin/env python3

from pwnc.minelf import *
from argparse import ArgumentParser, BooleanOptionalAction
from pathlib import Path
from ctypes import sizeof

def err(msg: str):
print(f"[-] {msg}")
exit(1)

def warn(msg: str):
print(f"[*] {msg}")

parser = ArgumentParser()
parser.add_argument("--bits", choices=[32, 64])
parser.add_argument("--endian", choices=["big", "little"])
parser.add_argument("--rpath", type=str)
parser.add_argument("--interp", type=str)
parser.add_argument("path")
parser.add_argument("outfile", nargs="?")

args = parser.parse_args()
path = Path(args.path)
if args.outfile is None:
outfile = path
else:
outfile = Path(args.outfile)

try:
raw_elf_bytes = open(path, "rb").read()
except Exception as e:
err(f"failed to read file: {e}")

little_endian = None
match args.endian:
case "big":
little_endian = False
case _:
little_endian = True
elf = ELF(raw_elf_bytes, args.bits, little_endian)

dynamic = elf.section_from_name(b".dynamic")
if dynamic is None:
err(f".dyanmic section not present")

offset = 0
contents = elf.section_content(dynamic)
dyntags = []
while offset < dynamic.size:
dyntag = elf.Dyntag.from_buffer_copy(contents, offset)
dyntags.append(dyntag)
if dyntag.tag == 0:
break
offset += sizeof(elf.Dyntag)

used = offset + 0x10
size = dynamic.size & ~0x0f
extra = (size - used) // sizeof(elf.Dyntag)

warn(f"used {offset:#x} out of {dynamic.size:#x} ({offset/dynamic.size*100:.0f}%) of DYNAMIC")
warn(f"space for {extra} extra dynamic tags")

if args.rpath:
rpath_bytes = bytes(args.rpath, "utf8")
if extra == 0:
err(f"not enough space for rpath in dynamic table")
if len(rpath_bytes) >= elf.Dyntag.val.size:
err(f"not enough space for rpath str (max {elf.Dyntag.val.size} bytes)")
strtab = next(filter(lambda dt: dt.tag == 5, dyntags))
address = next(filter(lambda segment: segment.type == 2, elf.segments)).virtual_address
rpath_offset = offset + sizeof(elf.Dyntag) + elf.Dyntag.val.offset
rpath = elf.Dyntag(tag=15, val=address + rpath_offset - strtab.val)
contents[offset:offset+sizeof(elf.Dyntag)] = bytes(rpath)
contents[rpath_offset:rpath_offset+len(rpath_bytes)] = rpath_bytes
offset += sizeof(elf.Dyntag)
warn(f"rpath set to {args.rpath}")

if args.interp:
interp = next(filter(lambda segment: segment.type == 3, elf.segments))
new_interp_path = bytes(args.interp, encoding="utf8")
if len(new_interp_path) < interp.file_size:
elf.raw_elf_bytes[interp.offset:interp.offset+interp.file_size] = new_interp_path.ljust(interp.file_size, b"\x00")
warn(f"interp set to {args.interp}")
else:
err(f"new interp path is too long")

with open(outfile, "wb+") as fp:
fp.write(elf.raw_elf_bytes)