mirror of https://github.com/Nonannet/copapy.git
117 lines
3.8 KiB
Python
117 lines
3.8 KiB
Python
from dataclasses import dataclass
|
|
import pelfy
|
|
from typing import Generator, Literal
|
|
from enum import Enum
|
|
|
|
ByteOrder = Literal['little', 'big']
|
|
|
|
START_MARKER = 0xF17ECAFE
|
|
END_MARKER = 0xF27ECAFE
|
|
MARKER_LENGTH = 4
|
|
|
|
LENGTH_CALL_INSTRUCTION = 5 # x86_64
|
|
|
|
RelocationType = Enum('RelocationType', [('RELOC_RELATIVE_32', 0)])
|
|
|
|
|
|
@dataclass
|
|
class patch_entry:
|
|
"""
|
|
A class for representing a relocation entry
|
|
|
|
Attributes:
|
|
addr (int): address of first byte to patch relative to the start of the symbol
|
|
type (RelocationType): relocation type"""
|
|
|
|
type: RelocationType
|
|
addr: int
|
|
addend: int
|
|
|
|
|
|
def translate_relocation(relocation_addr: int, reloc_type: str, bits: int, r_addend: int) -> patch_entry:
|
|
if reloc_type in ('R_AMD64_PLT32', 'R_AMD64_PC32'):
|
|
# S + A - P
|
|
patch_offset = relocation_addr
|
|
return patch_entry(RelocationType.RELOC_RELATIVE_32, patch_offset, r_addend)
|
|
else:
|
|
raise Exception(f"Unknown: {reloc_type}")
|
|
|
|
|
|
def get_ret_function_def(symbol: pelfy.elf_symbol) -> str:
|
|
#print('*', symbol.name, len(symbol.relocations))
|
|
if symbol.relocations:
|
|
result_func = symbol.relocations[-1].symbol
|
|
|
|
assert result_func.name.startswith('result_')
|
|
return result_func.name[7:]
|
|
else:
|
|
return 'void'
|
|
|
|
|
|
def strip_symbol(data: bytes, byteorder: ByteOrder) -> bytes:
|
|
"""Return data between start and end marker and removing last instruction (call)"""
|
|
start_index, end_index = get_stencil_position(data, byteorder)
|
|
return data[start_index:end_index]
|
|
|
|
|
|
def get_stencil_position(data: bytes, byteorder: ByteOrder) -> tuple[int, int]:
|
|
# Find first start marker
|
|
marker_index = data.find(START_MARKER.to_bytes(MARKER_LENGTH, byteorder))
|
|
start_index = 0 if marker_index < 0 else marker_index + MARKER_LENGTH
|
|
|
|
# Find last end marker
|
|
end_index = data.rfind(END_MARKER.to_bytes(MARKER_LENGTH, byteorder))
|
|
end_index = len(data) if end_index < 0 else end_index - LENGTH_CALL_INSTRUCTION
|
|
|
|
return start_index, end_index
|
|
|
|
|
|
class stencil_database():
|
|
def __init__(self, obj_file: str | bytes):
|
|
"""Load the stencil database from an ELF object file
|
|
"""
|
|
if isinstance(obj_file, str):
|
|
self.elf = pelfy.open_elf_file(obj_file)
|
|
else:
|
|
self.elf = pelfy.elf_file(obj_file)
|
|
|
|
#print(self.elf.symbols)
|
|
|
|
self.function_definitions = {s.name: get_ret_function_def(s) for s in self.elf.symbols
|
|
if s.info == 'STT_FUNC'}
|
|
|
|
self.data = {s.name: strip_symbol(s.data, self.elf.byteorder) for s in self.elf.symbols
|
|
if s.info == 'STT_FUNC'}
|
|
|
|
self.var_size = {s.name: s.fields['st_size'] for s in self.elf.symbols
|
|
if s.info == 'STT_OBJECT'}
|
|
|
|
self.byteorder: Literal['little', 'big'] = self.elf.byteorder
|
|
|
|
for name in self.function_definitions.keys():
|
|
sym = self.elf.symbols[name]
|
|
sym.relocations
|
|
self.elf.symbols[name].data
|
|
|
|
def get_patch_positions(self, symbol_name: str) -> Generator[patch_entry, None, None]:
|
|
"""Return patch positions
|
|
"""
|
|
symbol = self.elf.symbols[symbol_name]
|
|
start_index, end_index = get_stencil_position(symbol.data, symbol.file.byteorder)
|
|
|
|
for reloc in symbol.relocations:
|
|
|
|
# address to fist byte to patch relative to the start of the symbol
|
|
patch = translate_relocation(
|
|
reloc.fields['r_offset'] - symbol.fields['st_value'] - start_index,
|
|
reloc.type,
|
|
reloc.bits,
|
|
reloc.fields['r_addend'])
|
|
|
|
# Exclude the call to the result_x function
|
|
if patch.addr < end_index - start_index:
|
|
yield patch
|
|
|
|
def get_func_data(self, name: str) -> bytes:
|
|
return strip_symbol(self.elf.symbols[name].data, self.elf.byteorder)
|