Skip to content

Patches

InsertDataPatch

Bases: Patch

Patch that inserts data into the binary.

Source code in src/patcherex2/patches/data_patches.py
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
class InsertDataPatch(Patch):
    """
    Patch that inserts data into the binary.
    """

    def __init__(self, addr_or_name: int | str, data: bytes) -> None:
        """
        Constructor.

        :param addr_or_name: If an integer, data is inserted at the address.
                             If a string, it is placed in a free spot in the binary and added as a symbol (with this as its name).
        :param data: New data to place in binary.
        """
        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.data = data

    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:
            p.binfmt_tool.update_binary_content(self.addr, self.data)
        elif self.name:
            block = p.allocation_manager.allocate(
                len(self.data), flag=MemoryFlag.RWX
            )  # FIXME: why RW not work?
            p.symbols[self.name] = block.mem_addr
            p.binfmt_tool.update_binary_content(block.file_addr, self.data)

__init__(addr_or_name, data)

Constructor.

Parameters:

Name Type Description Default
addr_or_name int | str

If an integer, data is inserted at the address. If a string, it is placed in a free spot in the binary and added as a symbol (with this as its name).

required
data bytes

New data to place in binary.

required
Source code in src/patcherex2/patches/data_patches.py
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
def __init__(self, addr_or_name: int | str, data: bytes) -> None:
    """
    Constructor.

    :param addr_or_name: If an integer, data is inserted at the address.
                         If a string, it is placed in a free spot in the binary and added as a symbol (with this as its name).
    :param data: New data to place in binary.
    """
    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.data = data

apply(p)

Applies the patch to the binary, intended to be called by a Patcherex instance.

Parameters:

Name Type Description Default
p Patcherex

Patcherex instance.

required
Source code in src/patcherex2/patches/data_patches.py
50
51
52
53
54
55
56
57
58
59
60
61
62
63
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:
        p.binfmt_tool.update_binary_content(self.addr, self.data)
    elif self.name:
        block = p.allocation_manager.allocate(
            len(self.data), flag=MemoryFlag.RWX
        )  # FIXME: why RW not work?
        p.symbols[self.name] = block.mem_addr
        p.binfmt_tool.update_binary_content(block.file_addr, self.data)

InsertFunctionPatch

Bases: Patch

Inserts a function into the binary.

Source code in src/patcherex2/patches/function_patches.py
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
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: If address is used, this is the address to place trampoline code for jumping to function.
                           If name is used, this is where the new function will be placed, 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["prefunc"] if "prefunc" in kwargs else None
        self.postfunc = kwargs["postfunc"] if "postfunc" in kwargs else None
        self.compile_opts = kwargs["compile_opts"] if "compile_opts" in kwargs else {}
        self.save_context = (
            kwargs["save_context"] if "save_context" in kwargs else 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:
            if self.prefunc:
                if "SAVE_CONTEXT" in self.prefunc:
                    self.prefunc = self.prefunc.replace(
                        "SAVE_CONTEXT", f"\n{p.archinfo.save_context_asm}\n"
                    )
                if "RESTORE_CONTEXT" in self.prefunc:
                    self.prefunc = self.prefunc.replace(
                        "RESTORE_CONTEXT", f"\n{p.archinfo.restore_context_asm}\n"
                    )
            if self.postfunc:
                if "SAVE_CONTEXT" in self.postfunc:
                    self.postfunc = self.postfunc.replace(
                        "SAVE_CONTEXT", f"\n{p.archinfo.save_context_asm}\n"
                    )
                if "RESTORE_CONTEXT" in self.postfunc:
                    self.postfunc = self.postfunc.replace(
                        "RESTORE_CONTEXT", f"\n{p.archinfo.restore_context_asm}\n"
                    )
            ifp = InsertFunctionPatch(
                f"__patcherex_{hex(self.addr)}",
                self.code,
                is_thumb=p.binary_analyzer.is_thumb(self.addr),
                symbols=self.symbols,
            )
            ifp.apply(p)
            instrs = ""
            instrs += p.archinfo.save_context_asm if self.save_context else ""
            instrs += self.prefunc if self.prefunc else ""
            instrs += "\n"
            # NOTE: ↓ this is hardcoded to bl, not blx, but it should be fine for this use case
            instrs += p.archinfo.call_asm.format(
                dst=f"{{__patcherex_{hex(self.addr)}}}"
            )
            instrs += "\n"
            instrs += self.postfunc if self.postfunc else ""
            instrs += p.archinfo.restore_context_asm if self.save_context else ""
            p.utils.insert_trampoline_code(
                self.addr,
                instrs,
                force_insert=self.force_insert,
                detour_pos=self.detour_pos,
                symbols=self.symbols,
            )
        elif self.name:
            compiled_size = len(
                p.compiler.compile(
                    self.code,
                    symbols=self.symbols,
                    is_thumb=self.is_thumb,
                    **self.compile_opts,
                )
            )
            if self.detour_pos == -1:
                block = p.allocation_manager.allocate(
                    compiled_size + 0x20, align=p.archinfo.alignment, flag=MemoryFlag.RX
                )  # TODO: adjust that 0x20 part
                mem_addr = block.mem_addr
                file_addr = block.file_addr
            else:
                mem_addr = self.detour_pos
                file_addr = p.binary_analyzer.mem_addr_to_file_offset(mem_addr)
            p.sypy_info["patcherex_added_functions"].append(hex(mem_addr))
            p.symbols[self.name] = mem_addr
            p.binfmt_tool.update_binary_content(
                file_addr,
                p.compiler.compile(
                    self.code,
                    mem_addr,
                    symbols=self.symbols,
                    is_thumb=self.is_thumb,
                    **self.compile_opts,
                ),
            )

__init__(addr_or_name, code, force_insert=False, detour_pos=-1, symbols=None, is_thumb=False, **kwargs)

Constructor.

Parameters:

Name Type Description Default
addr_or_name int | str

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.

required
code str

C code for the new function. "SAVE_CONTEXT" and "RESTORE_CONTEXT" can be used to save and restore context.

required
force_insert

If Patcherex should ignore whether instructions can be moved when inserting, defaults to False

False
detour_pos

If address is used, this is the address to place trampoline code for jumping to function. If name is used, this is where the new function will be placed, defaults to -1

-1
symbols dict[str, int] | None

Symbols to include when compiling/assembling, in format {symbol name: memory address}, defaults to None

None
is_thumb

Whether the instructions given are thumb, defaults to False

False
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.

{}
Source code in src/patcherex2/patches/function_patches.py
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
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: If address is used, this is the address to place trampoline code for jumping to function.
                       If name is used, this is where the new function will be placed, 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["prefunc"] if "prefunc" in kwargs else None
    self.postfunc = kwargs["postfunc"] if "postfunc" in kwargs else None
    self.compile_opts = kwargs["compile_opts"] if "compile_opts" in kwargs else {}
    self.save_context = (
        kwargs["save_context"] if "save_context" in kwargs else False
    )

apply(p)

Applies the patch to the binary, intended to be called by a Patcherex instance.

Parameters:

Name Type Description Default
p Patcherex

Patcherex instance.

required
Source code in src/patcherex2/patches/function_patches.py
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
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:
        if self.prefunc:
            if "SAVE_CONTEXT" in self.prefunc:
                self.prefunc = self.prefunc.replace(
                    "SAVE_CONTEXT", f"\n{p.archinfo.save_context_asm}\n"
                )
            if "RESTORE_CONTEXT" in self.prefunc:
                self.prefunc = self.prefunc.replace(
                    "RESTORE_CONTEXT", f"\n{p.archinfo.restore_context_asm}\n"
                )
        if self.postfunc:
            if "SAVE_CONTEXT" in self.postfunc:
                self.postfunc = self.postfunc.replace(
                    "SAVE_CONTEXT", f"\n{p.archinfo.save_context_asm}\n"
                )
            if "RESTORE_CONTEXT" in self.postfunc:
                self.postfunc = self.postfunc.replace(
                    "RESTORE_CONTEXT", f"\n{p.archinfo.restore_context_asm}\n"
                )
        ifp = InsertFunctionPatch(
            f"__patcherex_{hex(self.addr)}",
            self.code,
            is_thumb=p.binary_analyzer.is_thumb(self.addr),
            symbols=self.symbols,
        )
        ifp.apply(p)
        instrs = ""
        instrs += p.archinfo.save_context_asm if self.save_context else ""
        instrs += self.prefunc if self.prefunc else ""
        instrs += "\n"
        # NOTE: ↓ this is hardcoded to bl, not blx, but it should be fine for this use case
        instrs += p.archinfo.call_asm.format(
            dst=f"{{__patcherex_{hex(self.addr)}}}"
        )
        instrs += "\n"
        instrs += self.postfunc if self.postfunc else ""
        instrs += p.archinfo.restore_context_asm if self.save_context else ""
        p.utils.insert_trampoline_code(
            self.addr,
            instrs,
            force_insert=self.force_insert,
            detour_pos=self.detour_pos,
            symbols=self.symbols,
        )
    elif self.name:
        compiled_size = len(
            p.compiler.compile(
                self.code,
                symbols=self.symbols,
                is_thumb=self.is_thumb,
                **self.compile_opts,
            )
        )
        if self.detour_pos == -1:
            block = p.allocation_manager.allocate(
                compiled_size + 0x20, align=p.archinfo.alignment, flag=MemoryFlag.RX
            )  # TODO: adjust that 0x20 part
            mem_addr = block.mem_addr
            file_addr = block.file_addr
        else:
            mem_addr = self.detour_pos
            file_addr = p.binary_analyzer.mem_addr_to_file_offset(mem_addr)
        p.sypy_info["patcherex_added_functions"].append(hex(mem_addr))
        p.symbols[self.name] = mem_addr
        p.binfmt_tool.update_binary_content(
            file_addr,
            p.compiler.compile(
                self.code,
                mem_addr,
                symbols=self.symbols,
                is_thumb=self.is_thumb,
                **self.compile_opts,
            ),
        )

InsertInstructionPatch

Bases: Patch

Patch that allows instructions to be inserted into binary. These instructions are inserted at a free place in the binary. Then, At the address given, an instruction is inserted that jumps to this block (also in the block are the instructions this overwrites). At the end of the block, it jumps back to right after the initial jump. The initial jump must be able to be inserted within the basic block of the given address.

Source code in src/patcherex2/patches/instruction_patches.py
 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
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
class InsertInstructionPatch(Patch):
    """
    Patch that allows instructions to be inserted into binary. These instructions are inserted at a free place in the binary.
    Then, At the address given, an instruction is inserted that jumps to this block (also in the block are the instructions this overwrites).
    At the end of the block, it jumps back to right after the initial jump. The initial jump must be able to be inserted within the basic block
    of the given address.
    """

    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,
        **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: If given a name, specifies the file address to place the new instructions, 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 **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.save_context = (
            kwargs["save_context"] if "save_context" in kwargs else False
        )

    def apply(self, p) -> None:
        """
        Applies the patch to the binary, intended to be called by a Patcherex instance.

        :param p: Patcherex instance.
        :type p: Patcherex
        """
        if self.addr:
            if "SAVE_CONTEXT" in self.instr:
                self.instr = self.instr.replace(
                    "SAVE_CONTEXT", f"\n{p.archinfo.save_context_asm}\n"
                )
            if "RESTORE_CONTEXT" in self.instr:
                self.instr = self.instr.replace(
                    "RESTORE_CONTEXT", f"\n{p.archinfo.restore_context_asm}\n"
                )
            if self.save_context:
                self.instr = f"{p.archinfo.save_context_asm}\n{self.instr}\n{p.archinfo.restore_context_asm}"
            p.utils.insert_trampoline_code(
                self.addr,
                self.instr,
                force_insert=self.force_insert,
                detour_pos=self.detour_pos,
                symbols=self.symbols,
            )
        elif self.name:
            assembled_size = len(
                p.assembler.assemble(
                    self.instr, symbols=self.symbols, is_thumb=self.is_thumb
                )
            )
            if self.detour_pos == -1:
                block = p.allocation_manager.allocate(
                    assembled_size, align=p.archinfo.alignment, flag=MemoryFlag.RX
                )
                p.symbols[self.name] = block.mem_addr
                p.binfmt_tool.update_binary_content(
                    block.file_addr,
                    p.assembler.assemble(
                        self.instr,
                        block.mem_addr,
                        symbols=self.symbols,
                        is_thumb=self.is_thumb,
                    ),
                )
            else:
                p.symbols[self.name] = self.detour_pos
                p.binfmt_tool.update_binary_content(
                    self.detour_pos,
                    p.assembler.assemble(
                        self.instr,
                        self.detour_pos,
                        symbols=self.symbols,
                        is_thumb=self.is_thumb,
                    ),
                )

__init__(addr_or_name, instr, force_insert=False, detour_pos=-1, symbols=None, is_thumb=False, **kwargs)

Constructor.

Parameters:

Name Type Description Default
addr_or_name int | str

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).

required
instr str

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.

required
force_insert

If Patcherex should ignore whether instructions can be moved when inserting, defaults to False

False
detour_pos

If given a name, specifies the file address to place the new instructions, defaults to -1

-1
symbols dict[str, int] | None

Symbols to include when assembling, in format {symbol name: memory address}, defaults to None

None
is_thumb

Whether the instructions given are thumb, defaults to False

False
**kwargs

Extra options. Can have a boolean "save_context" for whether context should be saved.

{}
Source code in src/patcherex2/patches/instruction_patches.py
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
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,
    **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: If given a name, specifies the file address to place the new instructions, 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 **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.save_context = (
        kwargs["save_context"] if "save_context" in kwargs else False
    )

apply(p)

Applies the patch to the binary, intended to be called by a Patcherex instance.

Parameters:

Name Type Description Default
p Patcherex

Patcherex instance.

required
Source code in src/patcherex2/patches/instruction_patches.py
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
def apply(self, p) -> None:
    """
    Applies the patch to the binary, intended to be called by a Patcherex instance.

    :param p: Patcherex instance.
    :type p: Patcherex
    """
    if self.addr:
        if "SAVE_CONTEXT" in self.instr:
            self.instr = self.instr.replace(
                "SAVE_CONTEXT", f"\n{p.archinfo.save_context_asm}\n"
            )
        if "RESTORE_CONTEXT" in self.instr:
            self.instr = self.instr.replace(
                "RESTORE_CONTEXT", f"\n{p.archinfo.restore_context_asm}\n"
            )
        if self.save_context:
            self.instr = f"{p.archinfo.save_context_asm}\n{self.instr}\n{p.archinfo.restore_context_asm}"
        p.utils.insert_trampoline_code(
            self.addr,
            self.instr,
            force_insert=self.force_insert,
            detour_pos=self.detour_pos,
            symbols=self.symbols,
        )
    elif self.name:
        assembled_size = len(
            p.assembler.assemble(
                self.instr, symbols=self.symbols, is_thumb=self.is_thumb
            )
        )
        if self.detour_pos == -1:
            block = p.allocation_manager.allocate(
                assembled_size, align=p.archinfo.alignment, flag=MemoryFlag.RX
            )
            p.symbols[self.name] = block.mem_addr
            p.binfmt_tool.update_binary_content(
                block.file_addr,
                p.assembler.assemble(
                    self.instr,
                    block.mem_addr,
                    symbols=self.symbols,
                    is_thumb=self.is_thumb,
                ),
            )
        else:
            p.symbols[self.name] = self.detour_pos
            p.binfmt_tool.update_binary_content(
                self.detour_pos,
                p.assembler.assemble(
                    self.instr,
                    self.detour_pos,
                    symbols=self.symbols,
                    is_thumb=self.is_thumb,
                ),
            )

ModifyDataPatch

Bases: ModifyRawBytesPatch

Extends ModifyRawBytesPatch to only be used for memory addresses, used for modifying data in binary.

Source code in src/patcherex2/patches/data_patches.py
17
18
19
20
21
22
23
24
25
26
class ModifyDataPatch(ModifyRawBytesPatch):
    """
    Extends ModifyRawBytesPatch to only be used for memory addresses, used for modifying data in binary.
    """

    def __init__(self, addr: int, new_bytes: bytes) -> None:
        """
        Same as ModifyRawBytesPatch constructor, but address type of memory is assumed.
        """
        super().__init__(addr, new_bytes, addr_type="mem")

__init__(addr, new_bytes)

Same as ModifyRawBytesPatch constructor, but address type of memory is assumed.

Source code in src/patcherex2/patches/data_patches.py
22
23
24
25
26
def __init__(self, addr: int, new_bytes: bytes) -> None:
    """
    Same as ModifyRawBytesPatch constructor, but address type of memory is assumed.
    """
    super().__init__(addr, new_bytes, addr_type="mem")

ModifyFunctionPatch

Bases: Patch

Patch that replaces an existing function in the binary with your own. If there is enough room in the existing function, your code is compiled and placed there. If not, your code is placed in a free spot in the binary, and the function will jump there instead.

Source code in src/patcherex2/patches/function_patches.py
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
class ModifyFunctionPatch(Patch):
    """
    Patch that replaces an existing function in the binary with your own. If there is enough room in the existing
    function, your code is compiled and placed there. If not, your code is placed in a free spot in the binary, and
    the function will jump there instead.
    """

    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 original function is not big enough, file address to place the given code, 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["compile_opts"] if "compile_opts" in kwargs else {}

    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)
        compiled_size = len(
            p.compiler.compile(
                self.code,
                symbols=self.symbols,
                is_thumb=p.binary_analyzer.is_thumb(func["addr"]),
                **self.compile_opts,
            )
        )
        if compiled_size <= func["size"]:
            mem_addr = func["addr"]
            file_addr = p.binary_analyzer.mem_addr_to_file_offset(mem_addr)
        else:
            # TODO: mark the function as free (exclude jump instr)
            if self.detour_pos == -1:
                block = p.allocation_manager.allocate(
                    compiled_size + 0x20, align=0x4, flag=MemoryFlag.RX
                )
                mem_addr = block.mem_addr
                file_addr = block.file_addr
            else:
                mem_addr = self.detour_pos
                file_addr = p.binary_analyzer.mem_addr_to_file_offset(mem_addr)
            jmp_instr = p.archinfo.jmp_asm.format(dst=hex(mem_addr))
            jmp_bytes = p.assembler.assemble(
                jmp_instr,
                func["addr"],
                is_thumb=p.binary_analyzer.is_thumb(func["addr"]),
            )
            p.binfmt_tool.update_binary_content(
                p.binary_analyzer.mem_addr_to_file_offset(func["addr"]),
                jmp_bytes,
            )
        p.binfmt_tool.update_binary_content(
            file_addr,
            p.compiler.compile(
                self.code,
                mem_addr,
                symbols=self.symbols,
                is_thumb=p.binary_analyzer.is_thumb(func["addr"]),
                **self.compile_opts,
            ),
        )

__init__(addr_or_name, code, detour_pos=-1, symbols=None, **kwargs)

Constructor.

Parameters:

Name Type Description Default
addr_or_name int | str

The name or file address of the function.

required
code str

C code to replace the function.

required
detour_pos

If original function is not big enough, file address to place the given code, defaults to -1

-1
symbols dict[str, int] | None

Symbols to include when compiling, in format {symbol name: memory address}, defaults to None

None
Source code in src/patcherex2/patches/function_patches.py
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
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 original function is not big enough, file address to place the given code, 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["compile_opts"] if "compile_opts" in kwargs else {}

apply(p)

Applies the patch to the binary, intended to be called by a Patcherex instance.

Parameters:

Name Type Description Default
p Patcherex

Patcherex instance.

required
Source code in src/patcherex2/patches/function_patches.py
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
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)
    compiled_size = len(
        p.compiler.compile(
            self.code,
            symbols=self.symbols,
            is_thumb=p.binary_analyzer.is_thumb(func["addr"]),
            **self.compile_opts,
        )
    )
    if compiled_size <= func["size"]:
        mem_addr = func["addr"]
        file_addr = p.binary_analyzer.mem_addr_to_file_offset(mem_addr)
    else:
        # TODO: mark the function as free (exclude jump instr)
        if self.detour_pos == -1:
            block = p.allocation_manager.allocate(
                compiled_size + 0x20, align=0x4, flag=MemoryFlag.RX
            )
            mem_addr = block.mem_addr
            file_addr = block.file_addr
        else:
            mem_addr = self.detour_pos
            file_addr = p.binary_analyzer.mem_addr_to_file_offset(mem_addr)
        jmp_instr = p.archinfo.jmp_asm.format(dst=hex(mem_addr))
        jmp_bytes = p.assembler.assemble(
            jmp_instr,
            func["addr"],
            is_thumb=p.binary_analyzer.is_thumb(func["addr"]),
        )
        p.binfmt_tool.update_binary_content(
            p.binary_analyzer.mem_addr_to_file_offset(func["addr"]),
            jmp_bytes,
        )
    p.binfmt_tool.update_binary_content(
        file_addr,
        p.compiler.compile(
            self.code,
            mem_addr,
            symbols=self.symbols,
            is_thumb=p.binary_analyzer.is_thumb(func["addr"]),
            **self.compile_opts,
        ),
    )

ModifyInstructionPatch

Bases: Patch

Patch that directly modifies instructions in a binary (overwrites them) starting at address given. If ISA is variable length, then if there are remaining bytes in the last overwritten instruction, it will fill them with nops, but it will fail if remaining bytes are not divisible by nop length.

Source code in src/patcherex2/patches/instruction_patches.py
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
class ModifyInstructionPatch(Patch):
    """
    Patch that directly modifies instructions in a binary (overwrites them) starting at address given.
    If ISA is variable length, then if there are remaining bytes in the last overwritten instruction,
    it will fill them with nops, but it will fail if remaining bytes are not divisible by nop length.
    """

    def __init__(
        self, addr: int, instr: str, symbols: dict[str, int] | None = None
    ) -> None:
        """
        Constructor.

        :param addr: Memory address of instruction(s) to overwrite.
        :param instr: Assembly instruction(s) to place in binary. If you want to use any symbols from the program or from previous patches, you must surround them with curly braces.
        :param symbols: Symbols to include when assembling, in format {symbol name: memory address}, defaults to None
        """
        self.addr = addr
        self.instr = instr
        self.symbols = symbols if symbols else {}

    def apply(self, p) -> None:
        """
        Applies the patch to the binary, intended to be called by a Patcherex instance.

        :param p: Patcherex instance.
        :type p: Patcherex
        """
        asm_bytes = p.assembler.assemble(
            self.instr,
            self.addr,
            symbols=self.symbols,
            is_thumb=p.binary_analyzer.is_thumb(self.addr),
        )
        if p.archinfo.is_variable_length_isa:
            asm_size = len(asm_bytes)
            overwritten_size = 0
            num_instrs = 1
            while overwritten_size < asm_size:
                overwritten_size = len(
                    p.binary_analyzer.get_instr_bytes_at(
                        self.addr, num_instr=num_instrs
                    )
                )
                num_instrs += 1
            remaining_size = overwritten_size - asm_size
            assert (
                remaining_size % p.archinfo.nop_size == 0
            ), f"Cannot fill in {remaining_size} bytes when modifying instruction, must be a multiple of {p.archinfo.nop_size}"
            asm_bytes += p.archinfo.nop_bytes * (remaining_size // p.archinfo.nop_size)
        offset = p.binary_analyzer.mem_addr_to_file_offset(self.addr)
        p.binfmt_tool.update_binary_content(offset, asm_bytes)

__init__(addr, instr, symbols=None)

Constructor.

Parameters:

Name Type Description Default
addr int

Memory address of instruction(s) to overwrite.

required
instr str

Assembly instruction(s) to place in binary. If you want to use any symbols from the program or from previous patches, you must surround them with curly braces.

required
symbols dict[str, int] | None

Symbols to include when assembling, in format {symbol name: memory address}, defaults to None

None
Source code in src/patcherex2/patches/instruction_patches.py
27
28
29
30
31
32
33
34
35
36
37
38
39
def __init__(
    self, addr: int, instr: str, symbols: dict[str, int] | None = None
) -> None:
    """
    Constructor.

    :param addr: Memory address of instruction(s) to overwrite.
    :param instr: Assembly instruction(s) to place in binary. If you want to use any symbols from the program or from previous patches, you must surround them with curly braces.
    :param symbols: Symbols to include when assembling, in format {symbol name: memory address}, defaults to None
    """
    self.addr = addr
    self.instr = instr
    self.symbols = symbols if symbols else {}

apply(p)

Applies the patch to the binary, intended to be called by a Patcherex instance.

Parameters:

Name Type Description Default
p Patcherex

Patcherex instance.

required
Source code in src/patcherex2/patches/instruction_patches.py
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
def apply(self, p) -> None:
    """
    Applies the patch to the binary, intended to be called by a Patcherex instance.

    :param p: Patcherex instance.
    :type p: Patcherex
    """
    asm_bytes = p.assembler.assemble(
        self.instr,
        self.addr,
        symbols=self.symbols,
        is_thumb=p.binary_analyzer.is_thumb(self.addr),
    )
    if p.archinfo.is_variable_length_isa:
        asm_size = len(asm_bytes)
        overwritten_size = 0
        num_instrs = 1
        while overwritten_size < asm_size:
            overwritten_size = len(
                p.binary_analyzer.get_instr_bytes_at(
                    self.addr, num_instr=num_instrs
                )
            )
            num_instrs += 1
        remaining_size = overwritten_size - asm_size
        assert (
            remaining_size % p.archinfo.nop_size == 0
        ), f"Cannot fill in {remaining_size} bytes when modifying instruction, must be a multiple of {p.archinfo.nop_size}"
        asm_bytes += p.archinfo.nop_bytes * (remaining_size // p.archinfo.nop_size)
    offset = p.binary_analyzer.mem_addr_to_file_offset(self.addr)
    p.binfmt_tool.update_binary_content(offset, asm_bytes)

ModifyRawBytesPatch

Bases: Patch

Patch that modifies bytes of the binary.

Source code in src/patcherex2/patches/raw_patches.py
12
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
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)
            if not offset:
                logger.warning(
                    "failed to convert mem addr to file offset, will just default to raw addr"
                )
                offset = self.addr
        else:
            raise NotImplementedError()
        p.binfmt_tool.update_binary_content(offset, self.new_bytes)

__init__(addr, new_bytes, addr_type='mem')

Constructor.

Parameters:

Name Type Description Default
addr int

Starting address of bytes you want to change.

required
new_bytes bytes

New bytes to replace original ones.

required
addr_type

The type of address given, "mem" (memory address) or "raw" (file address), defaults to "mem"

'mem'
Source code in src/patcherex2/patches/raw_patches.py
17
18
19
20
21
22
23
24
25
26
27
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

apply(p)

Applies the patch to the binary, intended to be called by a Patcherex instance.

Parameters:

Name Type Description Default
p

Patcherex instance.

required

Raises:

Type Description
NotImplementedError

Raised if an address type other than "raw" or "mem" is specified.

Source code in src/patcherex2/patches/raw_patches.py
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
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)
        if not offset:
            logger.warning(
                "failed to convert mem addr to file offset, will just default to raw addr"
            )
            offset = self.addr
    else:
        raise NotImplementedError()
    p.binfmt_tool.update_binary_content(offset, self.new_bytes)

RemoveDataPatch

Bases: ModifyRawBytesPatch

Extends ModifyRawBytesPatch for removing data in the binary (fills it with null bytes starting at address given). Expects a memory address.

Source code in src/patcherex2/patches/data_patches.py
66
67
68
69
70
71
72
73
74
75
76
77
78
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")

__init__(addr, size)

Same as ModifyRawBytes Patch constructor, but adds size parameter and assumes memory address.

Parameters:

Name Type Description Default
size int

The number of bytes to remove.

required
Source code in src/patcherex2/patches/data_patches.py
72
73
74
75
76
77
78
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")

RemoveFunctionPatch

Bases: Patch

Patch that removes a function from the binary. Not implemented.

Source code in src/patcherex2/patches/function_patches.py
229
230
231
232
233
234
235
236
237
238
class RemoveFunctionPatch(Patch):
    """
    Patch that removes a function from the binary. Not implemented.
    """

    def __init__(self, parent=None) -> None:
        """
        Constructor.
        """
        raise NotImplementedError()

__init__(parent=None)

Constructor.

Source code in src/patcherex2/patches/function_patches.py
234
235
236
237
238
def __init__(self, parent=None) -> None:
    """
    Constructor.
    """
    raise NotImplementedError()

RemoveInstructionPatch

Bases: Patch

Patch that removes instructions in the binary. Currently only takes in a number of bytes and an starting address. The number of bytes must be divisible by the nop size of the architecture, otherwise it will fail.

Source code in src/patcherex2/patches/instruction_patches.py
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
class RemoveInstructionPatch(Patch):
    """
    Patch that removes instructions in the binary. Currently only takes in a number of bytes and an starting address.
    The number of bytes must be divisible by the nop size of the architecture, otherwise it will fail.
    """

    def __init__(
        self,
        addr: int,
        num_instr: int | None = None,
        num_bytes: int | None = None,
    ) -> None:
        """
        Constructor.

        :param addr: Memory address to remove instructions at.
        :param num_instr: Number of instructions to remove, currently not used, defaults to None
        :param num_bytes: Number of bytes to remove, must be divisible by nop size, defaults to None
        """
        self.addr = addr
        self.num_instr = num_instr
        self.num_bytes = num_bytes
        if self.num_instr is None and self.num_bytes is None:
            self.num_instr = 1

    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.num_bytes is None:
            raise NotImplementedError()
        if self.num_bytes and self.num_bytes % p.archinfo.nop_size != 0:
            raise Exception(
                f"Cannot remove {self.num_bytes} bytes, must be a multiple of {p.archinfo.nop_size}"
            )
        num_nops = self.num_bytes // p.archinfo.nop_size
        offset = p.binary_analyzer.mem_addr_to_file_offset(self.addr)
        p.binfmt_tool.update_binary_content(offset, p.archinfo.nop_bytes * num_nops)

__init__(addr, num_instr=None, num_bytes=None)

Constructor.

Parameters:

Name Type Description Default
addr int

Memory address to remove instructions at.

required
num_instr int | None

Number of instructions to remove, currently not used, defaults to None

None
num_bytes int | None

Number of bytes to remove, must be divisible by nop size, defaults to None

None
Source code in src/patcherex2/patches/instruction_patches.py
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
def __init__(
    self,
    addr: int,
    num_instr: int | None = None,
    num_bytes: int | None = None,
) -> None:
    """
    Constructor.

    :param addr: Memory address to remove instructions at.
    :param num_instr: Number of instructions to remove, currently not used, defaults to None
    :param num_bytes: Number of bytes to remove, must be divisible by nop size, defaults to None
    """
    self.addr = addr
    self.num_instr = num_instr
    self.num_bytes = num_bytes
    if self.num_instr is None and self.num_bytes is None:
        self.num_instr = 1

apply(p)

Applies the patch to the binary, intended to be called by a Patcherex instance.

Parameters:

Name Type Description Default
p Patcherex

Patcherex instance.

required
Source code in src/patcherex2/patches/instruction_patches.py
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
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.num_bytes is None:
        raise NotImplementedError()
    if self.num_bytes and self.num_bytes % p.archinfo.nop_size != 0:
        raise Exception(
            f"Cannot remove {self.num_bytes} bytes, must be a multiple of {p.archinfo.nop_size}"
        )
    num_nops = self.num_bytes // p.archinfo.nop_size
    offset = p.binary_analyzer.mem_addr_to_file_offset(self.addr)
    p.binfmt_tool.update_binary_content(offset, p.archinfo.nop_bytes * num_nops)