### Install Patcherex2 from latest commit with pip Source: https://purseclab.github.io/Patcherex2 Install Patcherex2 directly from the latest commit on the GitHub repository. ```bash pip install git+https://github.com/purseclab/Patcherex2.git ``` -------------------------------- ### Configuring component options with component_opts Source: https://purseclab.github.io/Patcherex2/advanced_usages/add_new_target_support Shows how to pass keyword arguments to a component implementation via component_opts. The get_assembler method accepts **kwargs and forwards them to SomeAssembler; the example configures arch and mode. ```python def get_assembler(self, assembler, **kwargs): assembler = assembler or "some_assembler" if assembler == "some_assembler": return SomeAssembler(**kwargs) raise NotImplementedError() ``` ```python p = Patcherex( "/path/to/binary", component_opts={"assembler": {"arch": "x86", "mode": "64"}} ) ``` ```python SomeAssembler(arch="x86", mode="64") ``` -------------------------------- ### FunctionPatch __init__ and apply methods Source: https://purseclab.github.io/Patcherex2/api_references/patches The constructor stores the replacement C code, the target function name or address, the detour position, and the symbol map. It also captures compile options from keyword arguments. The apply method retrieves the target function from the binary analyzer, compiles the replacement code, and either writes it directly into the binary if it fits within the original function size or installs a detour when the compiled code is larger. ```python def __init__( self, addr_or_name: int | str, code: str, detour_pos=-1, symbols: dict[str, int] | None = None, **kwargs, ) -> None: """ Constructor. :param addr_or_name: The name or file address of the function. :param code: C code to replace the function. :param detour_pos: If needed, memory address where the replacement is placed, defaults to -1 :param symbols: Symbols to include when compiling, in format {symbol name: memory address}, defaults to None """ self.code = code self.detour_pos = detour_pos self.addr_or_name = addr_or_name self.symbols = symbols if symbols else {} self.compile_opts = kwargs.get("compile_opts", {}) ``` ```python def apply(self, p: Patcherex) -> None: """ Applies the patch to the binary, intended to be called by a Patcherex instance. :param p: Patcherex instance. """ func = p.binary_analyzer.get_function(self.addr_or_name) func_addr = func["addr"] is_thumb = p.binary_analyzer.is_thumb(func_addr) compile_at = partial( p.compiler.compile, self.code, symbols=self.symbols, is_thumb=is_thumb, **self.compile_opts, ) compiled = compile_at(func_addr) if len(compiled) <= func["size"]: file_addr = p.binary_analyzer.mem_addr_to_file_offset(func_addr) p.binfmt_tool.update_binary_content(file_addr, compiled) return mem_addr, file_addr, compiled = self._create_detour_target( p, func_addr, compiled, compile_at, ) self._install_detour( p, source_addr=func_addr, target_addr=mem_addr, is_thumb=is_thumb, ) p.binfmt_tool.update_binary_content(file_addr, compiled) ``` -------------------------------- ### Install Patcherex2 with pip Source: https://purseclab.github.io/Patcherex2 Install the latest released version of Patcherex2 from PyPI. ```bash pip install patcherex2 ``` -------------------------------- ### Patcherex.__init__ Source: https://purseclab.github.io/Patcherex2/api_references/patcherex Constructs a new Patcherex object, initializing the target and its components. The binary path is required, while target class and options can be specified optionally. ```APIDOC ## Patcherex.__init__ ### Description Initializes the Patcherex object. It automatically detects the target architecture unless a specific target class is provided. Components like assembler, disassembler, compiler, etc., are initialized with optional custom options. ### Method Constructor ### Signature Patcherex(binary_path, target_cls=None, target_opts=None, components_opts=None) ### Parameters - **binary_path** (str) - Required - The path of the binary to patch. - **target_cls** (type[Target]) - Optional - Specified architecture class to use, otherwise it is automatically detected. Defaults to None. - **target_opts** (dict[str, str]) - Optional - Options to specify components for the target. Defaults to None. - **components_opts** (dict[str, dict[str, str]]) - Optional - Options for configuring each component for the target. Defaults to None. ### Request Example { "binary_path": "example_binary", "target_cls": null, "target_opts": {}, "components_opts": {} } ### Response #### Success Response Returns a new Patcherex instance with the target and components initialized. #### Response Example { "binary_path": "example_binary", "target": "", "symbols": {}, "sypy_info": {"patcherex_added_functions": []}, "patches": [] } ``` -------------------------------- ### Patcherex __init__ constructor Source: https://purseclab.github.io/Patcherex2/api_references/patcherex Initializes a Patcherex instance with the binary path and optional target/component options. If target_cls is omitted, the target is auto-detected. Defaults target_opts and components_opts to empty dicts and initializes all core components via the target's get_component method. ```python def __init__( self, binary_path: str, target_cls: type[Target] | None = None, target_opts: dict[str, str] | None = None, components_opts: dict[str, dict[str, str]] | None = None, ) -> None: """ Constructor. :param binary_path: The path of the binary to patch. :param target_cls: Specified architecture class to use, otherwise it is automatically detected, defaults to None :param target_opts: Options to specify components for the target, defaults to None :param components_opts: Options for configuring each component for the target, defaults to None """ if target_opts is None: target_opts = {} if components_opts is None: components_opts = {} self.binary_path = binary_path if target_cls is None: self.target = Target.detect_target(self, binary_path) else: self.target = target_cls(self, binary_path) self.symbols = {} self.sypy_info = {"patcherex_added_functions": []} self.patches = [] # Initialize components components = [ "assembler", "disassembler", "compiler", "binary_analyzer", "allocation_manager", "binfmt_tool", "utils", "archinfo", ] for component in components: setattr( self, component, self.target.get_component( component, target_opts.get(component), components_opts.get(component), ), ) ``` -------------------------------- ### Build and run Patcherex2 Docker image from latest commit Source: https://purseclab.github.io/Patcherex2 Build a Docker image from the latest commit and run it, mounting the current directory as /workdir. ```bash docker build -t patcherex2 --platform linux/amd64 https://github.com/purseclab/Patcherex2.git docker run --rm -it -v ${PWD}:/workdir -w /workdir patcherex2 ``` -------------------------------- ### Disassembly of compiled binary add Source: https://purseclab.github.io/Patcherex2/examples/insert_instruction_patch Disassembly of the compiled binary showing the add and main functions. The add function starts at 0x1149, with the instruction at 0x114d being the push %rbp that will be replaced by the inserted instructions. ```assembly 0000000000001149 : 1149: f3 0f 1e fa endbr64 114d: 55 push %rbp 114e: 48 89 e5 mov %rsp,%rbp 1151: 89 7d fc mov %edi,-0x4(%rbp) 1154: 89 75 f8 mov %esi,-0x8(%rbp) 1157: 8b 55 fc mov -0x4(%rbp),%edx 115a: 8b 45 f8 mov -0x8(%rbp),%eax 115d: 01 d0 add %edx,%eax 115f: 5d pop %rbp 1160: c3 ret 0000000000001161
: 1161: f3 0f 1e fa endbr64 1165: 55 push %rbp 1166: 48 89 e5 mov %rsp,%rbp 1169: be 03 00 00 00 mov $0x3,%esi 116e: bf 02 00 00 00 mov $0x2,%edi 1173: e8 d1 ff ff ff call 1149 1178: 89 c6 mov %eax,%esi 117a: 48 8d 05 83 0e 00 00 lea 0xe83(%rip),%rax # 2004 <_IO_stdin_used+0x4> 1181: 48 89 c7 mov %rax,%rdi 1184: b8 00 00 00 00 mov $0x0,%eax 1189: e8 c2 fe ff ff call 1050 118e: b8 00 00 00 00 mov $0x0,%eax 1193: 5d pop %rbp 1194: c3 ret ``` -------------------------------- ### Patcherex.__init__ Source: https://purseclab.github.io/Patcherex2/api_references/patcherex Constructor for the Patcherex class. Initializes the target, components, and patch list based on the provided binary path and optional configuration. ```APIDOC ## Patcherex.__init__ ### Description Constructor for the Patcherex class. Initializes the target, components, and patch list based on the provided binary path and optional configuration. ### Method Constructor ### Signature `__init__(binary_path: str, target_cls: type[Target] | None = None, target_opts: dict[str, str] | None = None, components_opts: dict[str, dict[str, str]] | None = None) -> None` ### Parameters - **binary_path** (str) - Required - The path of the binary to patch. - **target_cls** (type[Target] | None) - Optional - Specified architecture class to use, otherwise it is automatically detected, defaults to None. - **target_opts** (dict[str, str] | None) - Optional - Options to specify components for the target, defaults to None. - **components_opts** (dict[str, dict[str, str]] | None) - Optional - Options for configuring each component for the target, defaults to None. ### Source Code ```python def __init__( self, binary_path: str, target_cls: type[Target] | None = None, target_opts: dict[str, str] | None = None, components_opts: dict[str, dict[str, str]] | None = None, ) -> None: """ Constructor. :param binary_path: The path of the binary to patch. :param target_cls: Specified architecture class to use, otherwise it is automatically detected, defaults to None :param target_opts: Options to specify components for the target, defaults to None :param components_opts: Options for configuring each component for the target, defaults to None """ if target_opts is None: target_opts = {} if components_opts is None: components_opts = {} self.binary_path = binary_path if target_cls is None: self.target = Target.detect_target(self, binary_path) else: self.target = target_cls(self, binary_path) self.symbols = {} self.sypy_info = {"patcherex_added_functions": []} self.patches = [] # Initialize components components = [ "assembler", "disassembler", "compiler", "binary_analyzer", "allocation_manager", "binfmt_tool", "utils", "archinfo", ] for component in components: setattr( self, component, self.target.get_component( component, target_opts.get(component), components_opts.get(component), ), ) ``` ``` -------------------------------- ### Patcherex2 ModifyFunctionPatch example Source: https://purseclab.github.io/Patcherex2/examples/modify_function_patch Uses Patcherex2 to modify the 'add' function in the binary to multiply instead of add. Creates a Patcherex instance, appends a ModifyFunctionPatch with the new function body, applies patches, and saves the patched binary as 'add_patched'. ```python from patcherex2 import * p = Patcherex("add") new_add_func = """ int add(int a, int b) { return a * b; } """ p.patches.append(ModifyFunctionPatch("add", new_add_func)) p.apply_patches() p.save_binary("add_patched") ``` -------------------------------- ### Compile and run original program Source: https://purseclab.github.io/Patcherex2/examples/modify_function_patch Compiles and runs the original C program, producing the output '2 + 3 = 5'. This shows the expected behavior before patching. ```bash $ gcc -o add add.c && ./add 2 + 3 = 5 ``` -------------------------------- ### Pre- and Post- Function Hooks with InsertFunctionPatch Source: https://purseclab.github.io/Patcherex2/advanced_usages/advanced_usage Adds pre and post function hooks to a function call when using InsertFunctionPatch with an address as first argument. The prefunc executes before the function, postfunc after, useful for passing arguments or getting return values. ```python InsertFunctionPatch( 0xDEADBEEF, "int foo(int a) { return bar(); }", prefunc="mov rdi, 0x10", postfunc="mov rdi, rax", ) ``` -------------------------------- ### InsertInstructionPatch.__init__ Source: https://purseclab.github.io/Patcherex2/api_references/patches Initializes an InsertInstructionPatch object. The constructor parameters are not detailed in the source. ```APIDOC ## InsertInstructionPatch.__init__ ### Description Initializes an InsertInstructionPatch object. ### Method Constructor ### Signature `InsertInstructionPatch.__init__(...)` ### Parameters No parameters are documented in the source. ``` -------------------------------- ### RemoveDataPatch class definition Source: https://purseclab.github.io/Patcherex2/api_references/patches Defines the RemoveDataPatch class, which extends ModifyRawBytesPatch to remove data by filling bytes with nulls starting at a given memory address. The constructor takes an address and a size, and calls the parent constructor with the null-byte-filled data and addr_type="mem". ```python class RemoveDataPatch(ModifyRawBytesPatch): """ Extends ModifyRawBytesPatch for removing data in the binary (fills it with null bytes starting at address given). Expects a memory address. """ def __init__(self, addr: int, size: int) -> None: """ Same as ModifyRawBytes Patch constructor, but adds size parameter and assumes memory address. :param size: The number of bytes to remove. """ super().__init__(addr, b"\x00" * size, addr_type="mem") ``` -------------------------------- ### ModifyRawBytesPatch class with __init__ and apply Source: https://purseclab.github.io/Patcherex2/api_references/patches Defines a patch that modifies raw bytes of the binary. The constructor accepts a starting address, new bytes, and an address type ("mem" or "raw", defaulting to "mem"). The apply method converts the address to a file offset and updates the binary content, raising NotImplementedError for unsupported address types. ```python class ModifyRawBytesPatch(Patch): """ Patch that modifies bytes of the binary. """ def __init__(self, addr: int, new_bytes: bytes, addr_type="mem") -> None: """ Constructor. :param addr: Starting address of bytes you want to change. :param new_bytes: New bytes to replace original ones. :param addr_type: The type of address given, "mem" (memory address) or "raw" (file address), defaults to "mem" """ self.addr = addr self.new_bytes = new_bytes self.addr_type = addr_type def apply(self, p) -> None: """ Applies the patch to the binary, intended to be called by a Patcherex instance. :param p: Patcherex instance. :raises NotImplementedError: Raised if an address type other than "raw" or "mem" is specified. """ if self.addr_type == "raw": offset = self.addr elif self.addr_type == "mem": offset = p.binary_analyzer.mem_addr_to_file_offset(self.addr) else: raise NotImplementedError() p.binfmt_tool.update_binary_content(offset, self.new_bytes) ``` -------------------------------- ### InsertInstructionPatch.CConfig.__init__ Source: https://purseclab.github.io/Patcherex2/api_references/patches Initializes the CConfig class for InsertInstructionPatch. The constructor parameters are not detailed in the source. ```APIDOC ## InsertInstructionPatch.CConfig.__init__ ### Description Initializes the CConfig class for InsertInstructionPatch. ### Method Constructor ### Signature `InsertInstructionPatch.CConfig.__init__(...)` ### Parameters No parameters are documented in the source. ``` -------------------------------- ### Running with short input Source: https://purseclab.github.io/Patcherex2/examples/multiple_patches Command and output showing the program works fine with a short string. ```bash $ ./getline aaaaa aaaaa ``` -------------------------------- ### Selecting an assembler implementation with target_opts Source: https://purseclab.github.io/Patcherex2/advanced_usages/add_new_target_support Shows how to define a get_assembler method that supports multiple assembler implementations and how to select one via target_opts. The method defaults to Keystone unless another assembler is specified; passing "gas" selects the Gas assembler. ```python def get_assembler(self, assembler, **kwargs): assembler = assembler or "keystone" if assembler == "keystone": return Keystone() elif assembler == "gas": return Gas() raise NotImplementedError() ``` ```python p = Patcherex("/path/to/binary", target_opts={"assembler": "gas"}) ``` -------------------------------- ### Run patched binary Source: https://purseclab.github.io/Patcherex2/examples/modify_function_patch Executes the patched binary, which now multiplies the arguments, producing the output '2 + 3 = 6'. This demonstrates the successful modification. ```bash $ ./add_patched 2 + 3 = 6 ``` -------------------------------- ### InsertInstructionPatch.apply Source: https://purseclab.github.io/Patcherex2/api_references/patches Applies the insert instruction patch to the binary. The method signature and parameters are not detailed in the source. ```APIDOC ## InsertInstructionPatch.apply ### Description Applies the insert instruction patch to the binary. ### Method Method ### Signature `InsertInstructionPatch.apply(...)` ### Parameters No parameters are documented in the source. ``` -------------------------------- ### InsertInstructionPatch constructor and apply methods Source: https://purseclab.github.io/Patcherex2/api_references/patches The InsertInstructionPatch class constructor and its apply and _apply_c methods. The constructor initializes patch parameters such as address or symbol name, instruction text, language (ASM or C), and C configuration. The apply method dispatches to _apply_asm or _apply_c based on the language. _apply_c raises ValueError if no address is provided and computes extra saved registers for C code generation. ```python def __init__( self, addr_or_name: int | str, instr: str, force_insert=False, detour_pos=-1, symbols: dict[str, int] | None = None, is_thumb=False, language: str = "ASM", c_config: CConfig | None = None, **kwargs, ) -> None: """ Constructor. :param addr_or_name: If an integer, the new instructions are placed in a free spot in the binary and the jump to them is inserted at that memory address. If a string, the new instructions are placed in a free spot in the binary and added as a symbol (with this as its name). :param instr: Instructions to insert. You can use "SAVE_CONTEXT" and "RESTORE_CONTEXT" wherever you want to save and restore program context. If you want to use any symbols from the program or from previous patches, you must surround them with curly braces. :param force_insert: If Patcherex should ignore whether instructions can be moved when inserting, defaults to False :param detour_pos: Memory address where the new instructions are placed, defaults to -1 :param symbols: Symbols to include when assembling, in format {symbol name: memory address}, defaults to None :param is_thumb: Whether the instructions given are thumb, defaults to False :param language: The language of the patch, can be either "ASM" or "C" :param c_config: Configuration options for when language == "C" :param **kwargs: Extra options. Can have a boolean "save_context" for whether context should be saved. """ self.addr = None self.name = None if isinstance(addr_or_name, int): self.addr = addr_or_name elif isinstance(addr_or_name, str): self.name = addr_or_name self.instr = instr self.force_insert = force_insert self.detour_pos = detour_pos self.symbols = symbols if symbols else {} self.is_thumb = is_thumb self.language = language.upper() self.c_config = self.CConfig() if c_config is None else c_config self.save_context = kwargs.get("save_context", False) self.compile_opts = kwargs.get("compile_opts", {}) def apply(self, p): """ Applies the patch to the binary, intended to be called by a Patcherex instance. :param p: Patcherex instance. :type p: Patcherex """ if self.language == "ASM": self._apply_asm(p) elif self.language == "C": self._apply_c(p) def _apply_c(self, p) -> None: if self.addr is None: raise ValueError("An address must be provided for a C instruction patch") c_forward_header = self.c_config.c_forward_header c_scratch_regs = ( frozenset() if self.c_config.scratch_regs is None else frozenset(self.c_config.scratch_regs) ) c_regs_sort = ( [] if self.c_config.regs_sort is None else list(self.c_config.regs_sort) ) # TODO: make it os agnostic? calling_convention = ( p.archinfo.cc["LinuxPreserveNone"] if p.compiler.preserve_none else p.archinfo.cc["Linux"] ) subregister_table = p.archinfo.subregisters subregister_float_table = p.archinfo.subregisters_float # Figure out if there are any extra registers that we need to expose to the user # that aren't part of the calling convention. For x64 preserve_none, this will be # registers r10 and rbx. extra_saved = set(p.archinfo.regs) # Note that we cannot control callee saved registers. If we attempt to define # some registers via 'register uint64_t rbx asm("rbx");', the compiler will insert # push and pop instructions to save these registers. extra_saved = ( extra_saved - set(calling_convention) - set(p.archinfo.callee_saved["Linux"]) ) extra_saved_in = list(extra_saved) # We don't want to necessarily output registers that have been marked as scratch # However we always want to make them available as input extra_saved_out = list(extra_saved - c_scratch_regs) def uint_converter(size: int): return f"uint{size}_t" extra_saved_in_converted = convert_to_subregisters( extra_saved_in, subregister_table, c_regs_sort, uint_converter ) extra_saved_out_converted = convert_to_subregisters( extra_saved_out, subregister_table, c_regs_sort, uint_converter ) ``` -------------------------------- ### CConfig constructor: configure C micropatch registers and asm header/footer Source: https://purseclab.github.io/Patcherex2/api_references/patches Configuration class for InsertInstructionPatch when language == "C". Provides C forward headers, scratch register specification, register sorting, and assembly header/footer insertion. The docstring details how scratch registers and subregisters are handled, including the valid list format for x64. ```python class CConfig: def __init__( self, c_forward_header: str = "", scratch_regs: Iterable[str] | None = None, regs_sort: Iterable[str | tuple[str, int] | tuple[str, str]] | None = None, asm_header: str = "", asm_footer: str = "", ): """ Used to configure an InsertInstructionPatch when language == "C" :param c_forward_header: C code that will be inserted before the micropatch code. This is useful when you want\ to use C types, C headers, and C function forward declarations. :param scratch_regs: It is generally a good idea to mark some registers as scratch to give the compiler\ breathing room for allocating registers to use for intermediate variables in your micropatch.\ All of the registers that we mark as scratch can be freely clobbered by the compiler\ Note that you can still read from scratch registers stored in the variables. What the scratch\ register denotation will indicate however is that the register can be re-used after the variable\ is no longer live. :param regs_sort: Some architectures have subregisters\ which allow access to a portion of a register, such as the lower 32 bits of a 64 bit register. If you want\ to use a subregister instead of the full register you can request this by passing in a list of subregisters\ here. Note that if you specify a subregister here, the full parent register is not available in the C\ patch. Some registers allow values of smaller types to be stored in larger registers. For example, you can\ store float values in xmm0 on x64, but there is no 32 bit subregister. To indicate that a register\ should be treated as a specific size, you may specify register size in bits in the second element of the\ tuple. Alternatively, you may specify the type as a string. An example of a valid list for x64 is\ ['eax', ('xmm0', 32), ('xmm1', 'double')]. :param asm_header: The asm_header is inserted in the main body of the patch before the C code. This header is\ primarily useful for gaining access to the stack pointer, which is a register that is typically unavailable in\ our C patch code. :param asm_footer: The asm_footer is the same as the asm_header, except that it runs after the C code executes.\ This is typically less useful than asm_header, but is still available here if you need to do any cleanup in\ assembly. """ self.c_forward_header = c_forward_header self.scratch_regs = scratch_regs self.regs_sort = regs_sort self.asm_header = asm_header self.asm_footer = asm_footer ``` -------------------------------- ### InsertFunctionPatch class with apply, _apply_at_address, _expand_context_macros, and _apply_named Source: https://purseclab.github.io/Patcherex2/api_references/patches Defines the InsertFunctionPatch class for inserting a function into a binary via Patcherex. The constructor accepts an address or name, C code, and options like force_insert, detour_pos, symbols, is_thumb, and extra kwargs (prefunc, postfunc, save_context, compile_opts). The apply method dispatches to _apply_at_address or _apply_named, and _expand_context_macros replaces SAVE_CONTEXT and RESTORE_CONTEXT macros with architecture-specific assembly. ```python class InsertFunctionPatch(Patch): """ Inserts a function into the binary. """ def __init__( self, addr_or_name: int | str, code: str, force_insert=False, detour_pos=-1, symbols: dict[str, int] | None = None, is_thumb=False, **kwargs, ) -> None: """ Constructor. :param addr_or_name: If an integer, an intermediate function is created in a free spot in the binary, and at that address, a jump to the function is made with necessary context saves. If a string, the function is created in a free spot in the binary with that name. :param code: C code for the new function. "SAVE_CONTEXT" and "RESTORE_CONTEXT" can be used to save and restore context. :param force_insert: If Patcherex should ignore whether instructions can be moved when inserting, defaults to False :param detour_pos: Memory address for the trampoline or named function, defaults to -1 :param symbols: Symbols to include when compiling/assembling, in format {symbol name: memory address}, defaults to None :param is_thumb: Whether the instructions given are thumb, defaults to False :param kwargs: Extra options. Can include "prefunc" and "postfunc", instructions to go before or after your function if you give an address. Can also have "save_context" for whether context should be saved and "compile_opts" for extra compile options. """ self.addr = None self.name = None if isinstance(addr_or_name, int): self.addr = addr_or_name elif isinstance(addr_or_name, str): self.name = addr_or_name self.code = code self.detour_pos = detour_pos self.symbols = symbols if symbols else {} self.is_thumb = is_thumb self.force_insert = force_insert self.prefunc = kwargs.get("prefunc", None) self.postfunc = kwargs.get("postfunc", None) self.compile_opts = kwargs.get("compile_opts", {}) self.save_context = kwargs.get("save_context", False) def apply(self, p: Patcherex) -> None: """ Applies the patch to the binary, intended to be called by a Patcherex instance. :param p: Patcherex instance. """ if self.addr is not None: self._apply_at_address(p, self.addr) return if self.name: self._apply_named(p, self.name) def _apply_at_address(self, p: Patcherex, addr: int) -> None: prefunc = self._expand_context_macros(p, self.prefunc) postfunc = self._expand_context_macros(p, self.postfunc) function_name = f"__patcherex_{hex(addr)}" InsertFunctionPatch( function_name, self.code, is_thumb=p.binary_analyzer.is_thumb(addr), symbols=self.symbols, ).apply(p) instrs = "" instrs += p.archinfo.save_context_asm if self.save_context else "" instrs += prefunc instrs += "\n" # NOTE: This is hardcoded to bl, not blx, but it is valid for this use case. instrs += p.archinfo.call_asm.format(dst=f"{{{function_name}}}") instrs += "\n" instrs += postfunc instrs += p.archinfo.restore_context_asm if self.save_context else "" p.utils.insert_trampoline_code( addr, instrs, force_insert=self.force_insert, detour_pos=self.detour_pos, symbols=self.symbols, ) @staticmethod def _expand_context_macros( p: Patcherex, instrs: str | None, ) -> str: if not instrs: return "" return instrs.replace( "SAVE_CONTEXT", f"\n{p.archinfo.save_context_asm}\n", ).replace( "RESTORE_CONTEXT", f"\n{p.archinfo.restore_context_asm}\n", ) def _apply_named(self, p: Patcherex, name: str) -> None: compile_at = partial( p.compiler.compile, self.code, symbols=self.symbols, is_thumb=self.is_thumb, **self.compile_opts, ) compiled = compile_at(0) if self.detour_pos == -1: block, compiled = p.utils.allocate_generated_code( len(compiled), compile_at, align=p.archinfo.alignment, flag=MemoryFlag.RX, ) mem_addr = block.mem_addr file_addr = block.file_addr else: mem_addr = self.detour_pos compiled = compile_at(mem_addr) file_addr = p.binary_analyzer.mem_addr_to_file_offset(mem_addr) p.sypy_info["patcherex_added_functions"].append(hex(mem_addr)) p.symbols[name] = mem_addr p.binfmt_tool.update_binary_content(file_addr, compiled) ``` -------------------------------- ### Insert patch syntax: Insert*Patch(addr_or_name, content) Source: https://purseclab.github.io/Patcherex2/core_ideas/patches Shows the generic constructor signature for all Insert patch types. The first argument is an address or a name; when it is an address, content is inserted right before that address, and when it is a name, patcherex finds free space and stores the name for later reference. The content argument varies by patch type: byte string for InsertDataPatch, newline-separated assembly for InsertInstructionPatch, and a C function for InsertFunctionPatch. ```text Insert*Patch(addr_or_name, content) ``` -------------------------------- ### InsertDataPatch.__init__ Source: https://purseclab.github.io/Patcherex2/api_references/patches Initializes an InsertDataPatch object. The constructor parameters are not detailed in the source. ```APIDOC ## InsertDataPatch.__init__ ### Description Initializes an InsertDataPatch object. ### Method Constructor ### Signature `InsertDataPatch.__init__(...)` ### Parameters No parameters are documented in the source. ``` -------------------------------- ### Run Patcherex2 Docker image Source: https://purseclab.github.io/Patcherex2 Run the official Patcherex2 Docker image, mounting the current directory as /workdir. ```bash docker run --rm -it -v ${PWD}:/workdir -w /workdir ghcr.io/purseclab/patcherex2 ``` -------------------------------- ### InsertInstructionPatch __init__ for C language Source: https://purseclab.github.io/Patcherex2/api_references/patches Constructor for InsertInstructionPatch when language is "C". Accepts optional C forward header, scratch registers, register sorting, and assembly header/footer. The docstring indicates it configures the patch for C language. ```python def __init__( self, c_forward_header: str = "", scratch_regs: Iterable[str] | None = None, regs_sort: Iterable[str | tuple[str, int] | tuple[str, str]] | None = None, asm_header: str = "", asm_footer: str = "", ): """ Used to configure an InsertInstructionPatch when language == "C" ``` -------------------------------- ### Patcherex class: __init__, apply_patches, save_binary, shutdown Source: https://purseclab.github.io/Patcherex2/api_references/patcherex The main Patcherex class. Use it to load a binary, add patches, and apply them. The constructor accepts a binary path and optional target/component options; if no target class is given, it auto-detects the architecture. Call apply_patches() after adding all patches, then save_binary() to write the result. shutdown() is required when using Ghidra to release the JVM. ```python class Patcherex: """ The main class of the library. This is how you are intended to interact with patches. """ def __init__( self, binary_path: str, target_cls: type[Target] | None = None, target_opts: dict[str, str] | None = None, components_opts: dict[str, dict[str, str]] | None = None, ) -> None: """ Constructor. :param binary_path: The path of the binary to patch. :param target_cls: Specified architecture class to use, otherwise it is automatically detected, defaults to None :param target_opts: Options to specify components for the target, defaults to None :param components_opts: Options for configuring each component for the target, defaults to None """ if target_opts is None: target_opts = {} if components_opts is None: components_opts = {} self.binary_path = binary_path if target_cls is None: self.target = Target.detect_target(self, binary_path) else: self.target = target_cls(self, binary_path) self.symbols = {} self.sypy_info = {"patcherex_added_functions": []} self.patches = [] # Initialize components components = [ "assembler", "disassembler", "compiler", "binary_analyzer", "allocation_manager", "binfmt_tool", "utils", "archinfo", ] for component in components: setattr( self, component, self.target.get_component( component, target_opts.get(component), components_opts.get(component), ), ) def shutdown(self): """ Required when using Ghidra (which spawns a JVM via pyghidra). Calls .shutdown() on every component that defines it. """ for component in ( "assembler", "disassembler", "compiler", "binary_analyzer", "allocation_manager", "binfmt_tool", "utils", "archinfo", ): obj = getattr(self, component, None) if obj is not None and hasattr(obj, "shutdown"): obj.shutdown() def apply_patches(self) -> None: """ Applies all added patches to the binary. Call this when you have added all the patches you want. """ logger.debug(f"Applying patches: {self.patches}") for patch in self.patches: patch.apply(self) self.binfmt_tool.finalize() def save_binary(self, filename: str | None = None) -> None: """ Save the patched binary to a file. :param filename: Name of file to save to, defaults to '.patched' """ self.binfmt_tool.save_binary(filename) ``` -------------------------------- ### InsertFunctionPatch.__init__ Source: https://purseclab.github.io/Patcherex2/api_references/patches Initializes an InsertFunctionPatch object. The constructor parameters are not detailed in the source. ```APIDOC ## InsertFunctionPatch.__init__ ### Description Initializes an InsertFunctionPatch object. ### Method Constructor ### Signature `InsertFunctionPatch.__init__(...)` ### Parameters No parameters are documented in the source. ``` -------------------------------- ### Disassembly of my_getline and main Source: https://purseclab.github.io/Patcherex2/examples/multiple_patches Disassembly of the relevant functions from the compiled binary. Shows the exact instructions and addresses that will be patched. ```assembly 0000000000001189 : 1189: f3 0f 1e fa endbr64 118d: 55 push %rbp 118e: 48 89 e5 mov %rsp,%rbp 1191: 48 83 ec 20 sub $0x20,%rsp 1195: 48 89 7d e8 mov %rdi,-0x18(%rbp) 1199: c7 45 fc 00 00 00 00 movl $0x0,-0x4(%rbp) 11a0: 48 8b 05 69 2e 00 00 mov 0x2e69(%rip),%rax # 4010 11a7: 48 89 c7 mov %rax,%rdi 11aa: e8 e1 fe ff ff call 1090 11af: 88 45 fb mov %al,-0x5(%rbp) 11b2: 80 7d fb 0a cmpb $0xa,-0x5(%rbp) 11b6: 74 1b je 11d3 11b8: 8b 45 fc mov -0x4(%rbp),%eax 11bb: 8d 50 01 lea 0x1(%rax),%edx 11be: 89 55 fc mov %edx,-0x4(%rbp) 11c1: 48 63 d0 movslq %eax,%rdx 11c4: 48 8b 45 e8 mov -0x18(%rbp),%rax 11c8: 48 01 c2 add %rax,%rdx 11cb: 0f b6 45 fb movzbl -0x5(%rbp),%eax 11cf: 88 02 mov %al,(%rdx) 11d1: eb cd jmp 11a0 11d3: 90 nop 11d4: 8b 45 fc mov -0x4(%rbp),%eax 11d7: 48 63 d0 movslq %eax,%rdx 11da: 48 8b 45 e8 mov -0x18(%rbp),%rax 11de: 48 01 d0 add %rdx,%rax 11e1: c6 00 00 movb $0x0,(%rax) 11e4: 8b 45 fc mov -0x4(%rbp),%eax 11e7: c9 leave 11e8: c3 ret 00000000000011e9
: 11e9: f3 0f 1e fa endbr64 11ed: 55 push %rbp 11ee: 48 89 e5 mov %rsp,%rbp 11f1: 48 83 ec 20 sub $0x20,%rsp 11f5: 64 48 8b 04 25 28 00 mov %fs:0x28,%rax 11fc: 00 00 11fe: 48 89 45 f8 mov %rax,-0x8(%rbp) 1202: 31 c0 xor %eax,%eax 1204: 48 8d 45 ee lea -0x12(%rbp),%rax 1208: 48 89 c7 mov %rax,%rdi 120b: e8 79 ff ff ff call 1189 1210: 48 8d 45 ee lea -0x12(%rbp),%rax 1214: 48 89 c7 mov %rax,%rdi 1217: e8 54 fe ff ff call 1070 121c: b8 00 00 00 00 mov $0x0,%eax 1221: 48 8b 55 f8 mov -0x8(%rbp),%rdx 1225: 64 48 2b 14 25 28 00 sub %fs:0x28,%rdx 122c: 00 00 122e: 74 05 je 1235 1230: e8 4b fe ff ff call 1080 <__stack_chk_fail@plt> 1235: c9 leave 1236: c3 ret ``` -------------------------------- ### InstructionPatch.__init__ Source: https://purseclab.github.io/Patcherex2/api_references/patches Constructor for the InstructionPatch class. Initializes a patch that inserts new instructions into a binary at a specified memory address or as a named symbol. ```APIDOC ## InstructionPatch.__init__ ### Description Constructor for the InstructionPatch class. Initializes a patch that inserts new instructions into a binary at a specified memory address or as a named symbol. ### Method Constructor ### Signature `__init__(addr_or_name, instr, force_insert=False, detour_pos=-1, symbols=None, is_thumb=False, language='ASM', c_config=None, **kwargs)` ### Parameters - **addr_or_name** (`int | str`) - Required - If an integer, the new instructions are placed in a free spot in the binary and the jump to them is inserted at that memory address. If a string, the new instructions are placed in a free spot in the binary and added as a symbol (with this as its name). - **instr** (`str`) - Required - Instructions to insert. You can use "SAVE_CONTEXT" and "RESTORE_CONTEXT" wherever you want to save and restore program context. If you want to use any symbols from the program or from previous patches, you must surround them with curly braces. - **force_insert** - Optional - If Patcherex should ignore whether instructions can be moved when inserting, defaults to False. - **detour_pos** - Optional - Memory address where the new instructions are placed, defaults to -1. - **symbols** (`dict[str, int] | None`) - Optional - Symbols to include when assembling, in format {symbol name: memory address}, defaults to None. - **is_thumb** - Optional - Whether the instructions given are thumb, defaults to False. - **language** (`str`) - Optional - The language of the patch, can be either "ASM" or "C", defaults to 'ASM'. - **c_config** (`CConfig | None`) - Optional - Configuration options for when language == "C", defaults to None. - **kwargs** - Optional - Extra options. Can have a boolean "save_context" for whether context should be saved. ### Returns None ```