Skip to content

Patcherex

Patcherex

The main class of the library. This is how you are intended to interact with patches.

Source code in src/patcherex2/patcherex.py
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
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 '<filename>.patched'
        """
        self.binfmt_tool.save_binary(filename)

__init__(binary_path, target_cls=None, target_opts=None, components_opts=None)

Constructor.

Parameters:

Name Type Description Default
binary_path str

The path of the binary to patch.

required
target_cls type[Target] | None

Specified architecture class to use, otherwise it is automatically detected, defaults to None

None
target_opts dict[str, str] | None

Options to specify components for the target, defaults to None

None
components_opts dict[str, dict[str, str]] | None

Options for configuring each component for the target, defaults to None

None
Source code in src/patcherex2/patcherex.py
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
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),
            ),
        )

apply_patches()

Applies all added patches to the binary. Call this when you have added all the patches you want.

Source code in src/patcherex2/patcherex.py
88
89
90
91
92
93
94
95
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()

save_binary(filename=None)

Save the patched binary to a file.

Parameters:

Name Type Description Default
filename str | None

Name of file to save to, defaults to '.patched'

None
Source code in src/patcherex2/patcherex.py
 97
 98
 99
100
101
102
103
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 '<filename>.patched'
    """
    self.binfmt_tool.save_binary(filename)

shutdown()

Required when using Ghidra (which spawns a JVM via pyghidra). Calls .shutdown() on every component that defines it.

Source code in src/patcherex2/patcherex.py
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
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()