Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Insert instruction patch, lang="C" via copy-and-micropatch strategy #31

Merged
merged 31 commits into from
May 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
fe1483d
Initial support for C instruction patch
calebh Apr 8, 2024
20524a0
Fixed jump offsets when insert_trampoline_code when using C language.…
calebh Apr 9, 2024
57bd787
Added support for subregisters to InsertInstructionPatch C lang
calebh Apr 9, 2024
c39dd07
Bug fix for InsertFunctionPatch C lang, related to how arguments are …
calebh Apr 9, 2024
8a644de
Minor changes to type hints in instruction_patches.py
calebh Apr 9, 2024
48b6d25
Change input and output registers to scratch registers only. Implemen…
calebh Apr 10, 2024
371a151
Adding missing amd64.py file for previous commit
calebh Apr 10, 2024
81e4953
Added register & asm trick to force the compiler to allow access to e…
calebh Apr 10, 2024
30dc1f2
Added callee saved logic for pinning extra registers outside of the c…
calebh Apr 11, 2024
73ae273
Added register info for aarch64
calebh Apr 11, 2024
819c959
Added support for InsertInstructPatch C floating point registers. Add…
calebh Apr 11, 2024
ce75f2b
Updated insert_instruction_patch_c to use print out stack pointer. II…
calebh Apr 12, 2024
f69f909
Added working test case for InstructionPatchC
calebh Apr 15, 2024
4fd8059
Merge branch 'main' of github.com:purseclab/Patcherex2
calebh Apr 23, 2024
c66db41
Added test case for IIP C subregister feature
calebh Apr 23, 2024
c6416b6
Added IIP C ASM header testcase
calebh Apr 23, 2024
bc10dc3
Added additional float test case for IIP C x64
calebh Apr 23, 2024
fac6ce9
Changed size diff to truncate and emit a warning. In tests with sampl…
calebh Apr 23, 2024
b357a43
Added another compiler flag to ommit debug information that was getti…
calebh Apr 24, 2024
8071074
Merge branch 'main' of github.com:purseclab/Patcherex2
calebh Apr 24, 2024
9c81fec
Removed -fno-asynchronous-unwind-tables compilation flag as it is no …
calebh Apr 24, 2024
0677688
Improved handling of register types and subregisters
calebh Apr 29, 2024
167ade4
Added support for various 128 bit floating types depending on archite…
calebh May 2, 2024
a93df99
Added additional testcases for aarch64
calebh May 8, 2024
8e9caa3
Added aarch64 iip_c_float test
calebh May 8, 2024
4b65981
Updated README color block for aarch64 to indicate partial support
calebh May 8, 2024
a60ae1b
Merge remote-tracking branch 'upstream/main'
calebh May 8, 2024
32ca3df
Updated x64 calling convention to match latest change in LLVM-19
calebh May 21, 2024
d3ea778
Merge branch 'main' of github.com:purseclab/Patcherex2
calebh May 21, 2024
1b0013d
Removed .idea directory
calebh May 21, 2024
5408733
Removed unused property from angr binary analyzer. Added NotImplement…
calebh May 21, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 9 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,14 +51,15 @@ General documentation and API reference for Patcherex2 can be found at [pursecla

| | Linux x86 | Linux amd64 | Linux arm | Linux aarch64 | Linux PowerPC (32bit) | Linux PowerPC (64bit) | Linux PowerPCle (64bit) | Linux MIPS (32bit) | Linux MIPS (64bit) | Linux MIPSEL<br>​(32bit) | Linux MIPSEL<br>(64bit) | SPARCv8 (LEON3) | PowerPC (VLE) (IHEX)
|-|-|-|-|-|-|-|-|-|-|-|-|-|-|
InsertDataPatch | 🟩 | 🟩 | 🟩 | 🟩 | 🟩 | 🟩 | 🟩 | 🟩 | 🟩 | 🟩 | 🟩 | ⬜ | ⬜ |
RemoveDataPatch | 🟩 | 🟩 | 🟩 | 🟩 | 🟩 | 🟩 | 🟩 | 🟩 | 🟩 | 🟩 | 🟩 | ⬜ | ⬜ |
ModifyDataPatch | 🟩 | 🟩 | 🟩 | 🟩 | 🟩 | 🟩 | 🟩 | 🟩 | 🟩 | 🟩 | 🟩 | ⬜ | ⬜ |
InsertInstructionPatch | 🟩 | 🟩 | 🟩 | 🟩 | 🟩 | 🟩 | 🟩 | 🟩 | 🟩 | 🟩 | 🟩 | ⬜ | ⬜ |
RemoveInstructionPatch | 🟩 | 🟩 | 🟩 | 🟩 | 🟩 | 🟩 | 🟩 | 🟩 | 🟩 | 🟩 | 🟩 | ⬜ | ⬜ |
ModifyInstructionPatch | 🟩 | 🟩 | 🟩 | 🟩 | 🟩 | 🟩 | 🟩 | 🟩 | 🟩 | 🟩 | 🟩 | ⬜ | ⬜ |
InsertFunctionPatch | 🟩 | 🟩 | 🟩 | 🟩 | 🟩 | 🟩 | 🟩 | 🟩 | 🟩 | 🟩 | 🟩 | ⬜ | ⬜ |
ModifyFunctionPatch | 🟨 | 🟩 | 🟩 | 🟩 | 🟨 | 🟨 | 🟨 | 🟨 | 🟨 | 🟨 | 🟨 | ⬜ | ⬜ |
InsertDataPatch | 🟩 | 🟩 | 🟩 | 🟩 | 🟩 | 🟩 | 🟩 | 🟩 | 🟩 | 🟩 | 🟩 | ⬜ | ⬜ |
RemoveDataPatch | 🟩 | 🟩 | 🟩 | 🟩 | 🟩 | 🟩 | 🟩 | 🟩 | 🟩 | 🟩 | 🟩 | ⬜ | ⬜ |
ModifyDataPatch | 🟩 | 🟩 | 🟩 | 🟩 | 🟩 | 🟩 | 🟩 | 🟩 | 🟩 | 🟩 | 🟩 | ⬜ | ⬜ |
InsertInstructionPatch (ASM) | 🟩 | 🟩 | 🟩 | 🟩 | 🟩 | 🟩 | 🟩 | 🟩 | 🟩 | 🟩 | 🟩 | ⬜ | ⬜ |
InsertInstructionPatch (C) | 🟥 | 🟩 | 🟥 | 🟨 | 🟥 | 🟥 | 🟥 | 🟥 | 🟥 | 🟥 | 🟥 | 🟥 | 🟥 |
RemoveInstructionPatch | 🟩 | 🟩 | 🟩 | 🟩 | 🟩 | 🟩 | 🟩 | 🟩 | 🟩 | 🟩 | 🟩 | ⬜ | ⬜ |
ModifyInstructionPatch | 🟩 | 🟩 | 🟩 | 🟩 | 🟩 | 🟩 | 🟩 | 🟩 | 🟩 | 🟩 | 🟩 | ⬜ | ⬜ |
InsertFunctionPatch | 🟩 | 🟩 | 🟩 | 🟩 | 🟩 | 🟩 | 🟩 | 🟩 | 🟩 | 🟩 | 🟩 | ⬜ | ⬜ |
ModifyFunctionPatch | 🟨 | 🟩 | 🟩 | 🟩 | 🟨 | 🟨 | 🟨 | 🟨 | 🟨 | 🟨 | 🟨 | ⬜ | ⬜ |

🟩 Fully Functional, 🟨 Limited Functionality, 🟥 Not Working, ⬜ Not Tested, 🟪 Work in Progress

Expand Down
Binary file added examples/insert_instruction_patch_c/add
Binary file not shown.
10 changes: 10 additions & 0 deletions examples/insert_instruction_patch_c/add.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#include <stdio.h>

int add(int a, int b) {
return a + b;
}

int main() {
printf("2 + 3 = %d\n", add(2, 3));
return 0;
}
57 changes: 57 additions & 0 deletions examples/insert_instruction_patch_c/patch.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
from patcherex2 import *
import logging

logger = logging.getLogger("patcherex2.patches.instruction_patches")
logger.setLevel(logging.INFO)

p = Patcherex("add", target_opts={"compiler": "clang19"})

c_forward_header = """
// This string will be inserted outside the micropatch function. It will be inserted before your code.
// This is how you can define types and function forward declarations used by your C micropatch
#include <stdio.h>
"""

# 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 unavailable in our C
# code. In this example we have moved rsp to the r12 register, which is a register that is accessible.
# This means that inside the C code we can access variables on the stack by using the r10 variable.
# There is also an asm_footer
asm_header = "mov r12, rsp"

# We can access assembly registers directly by using their name, while still using high level C constructs
# as well as intermediate variables. Note that you can use a return statement anywhere in your C micropatch
# to jump back to the next instruction after the micropatch insertion point.
c_str = """
rdi += rdi;
rdi += 5;
// Print out rsp as it was before the patch was started
printf("%p\\n", (void *) r12);
"""

# 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.
c_scratch_regs = [
'r8', 'r9', 'r10', 'r11', 'r13', 'r14', 'r15'
'xmm0', 'xmm1', 'xmm2', 'xmm3', 'xmm4', 'xmm5', 'xmm6', 'xmm7', 'xmm9', 'xmm10', 'xmm11', 'xmm12', 'xmm13', 'xmm14', 'xmm15'
]

# By default floating point registers will have the 'float' type. We can use c_float_types to override
# certain registers so they hold different types. In this example we denote that xmm8 is of type double
c_float_types = {'xmm8': 'double'}

config = InsertInstructionPatch.CConfig(
c_forward_header = c_forward_header,
scratch_regs=c_scratch_regs,
float_types=c_float_types,
asm_header=asm_header
)

p.patches.append(InsertInstructionPatch(0x114d, c_str, language="C", c_config=config))
p.apply_patches()

p.binfmt_tool.save_binary()
49 changes: 49 additions & 0 deletions src/patcherex2/components/archinfo/aarch64.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ class Aarch64Info:
jmp_asm = "b {dst}"
jmp_size = 4
alignment = 4
bits = 64
is_variable_length_isa = False
instr_size = 4
call_asm = "bl {dst}"
Expand Down Expand Up @@ -46,3 +47,51 @@ class Aarch64Info:
ldr x30, [sp, #0xf0]
add sp, sp, #0x1f0
"""

cc = {
'default': ['x0', 'x1', 'x2', 'x3', 'x4', 'x5', 'x6', 'x7'],
'defaultPreserveNone': None # TODO once aarch64 support lands in LLVM for preserve_none
}
callee_saved = {
'default': ['x19', 'x20', 'x21', 'x22', 'x23', 'x24', 'x25', 'x26', 'x27', 'x28', 'x29', 'x30']
}
cc_float = {
'default': ['v0', 'v1', 'v2', 'v3', 'v4', 'v5', 'v6', 'v7']
}
callee_saved_float = {
'default': ['v8', 'v9', 'v10', 'v11', 'v12', 'v13', 'v14', 'v15']
}

float_types = {
32: 'float',
64: 'double',
128: 'long double'
}

@property
def regs(self):
return list(self.subregisters.keys())

@property
def regs_float(self):
return list(self.subregisters_float.keys())

subregisters = {
'x{}'.format(i):
{
64: ['x{}'.format(i)],
32: ['w{}'.format(i)]
}
for i in range(0, 30 + 1)
}

subregisters_float = {
'v{}'.format(i): {
128: ['v{}'.format(i)],
64: ['d{}'.format(i)],
32: ['s{}'.format(i)],
16: ['h{}'.format(i)],
8: ['b{}'.format(i)]
}
for i in range(0, 30 + 1)
}
140 changes: 140 additions & 0 deletions src/patcherex2/components/archinfo/amd64.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ class Amd64Info:
jmp_asm = "jmp {dst}"
jmp_size = 6
alignment = 4
bits = 64
is_variable_length_isa = True
instr_size = -1 # variable length
call_asm = "call {dst}"
Expand Down Expand Up @@ -44,3 +45,142 @@ class Amd64Info:
pop rbx
pop rax
"""

cc = {
'Linux': ['rdi', 'rsi', 'rdx', 'rcx', 'r8', 'r9'],
'LinuxPreserveNone': ['r12', 'r13', 'r14', 'r15', 'rdi', 'rsi', 'rdx', 'rcx', 'r8', 'r9', 'r11', 'rax'],
'Windows': ['rcx', 'rdx', 'r8', 'r9']
}
callee_saved = {
'Linux': ['r12', 'r13', 'r14', 'r15', 'rbx', 'rsp', 'rbp']
}
cc_float = {
'Linux': ['xmm0', 'xmm1', 'xmm2', 'xmm3', 'xmm4', 'xmm5', 'xmm6', 'xmm7']
}
callee_saved_float = {
'Linux': []
}

float_types = {
32: 'float',
64: 'double',
128: '__float128'
}

@property
def regs(self):
return list(self.subregisters.keys())

@property
def regs_float(self):
return list(self.subregisters_float.keys())

subregisters = {
'rax': {
64: ['rax'],
32: ['eax'],
16: ['ax'],
# Note that the order of the children registers is important. Only the 0th
# element of this list (al) is used when determining the calling convention.
# That is, we can only use the following argument 'uint8_t al' in the
# calling convention at the rax position. 'uint8_t ah' is NOT allowed.
8: ['al', 'ah']
},
'rbx': {
64: ['rbx'],
32: ['ebx'],
16: ['bx'],
8: ['bl', 'bh']
},
'rcx': {
64: ['rcx'],
32: ['ecx'],
16: ['cx'],
8: ['cl', 'ch']
},
'rdx': {
64: ['rdx'],
32: ['edx'],
16: ['dx'],
8: ['dl', 'dh']
},
'rsi': {
64: ['rsi'],
32: ['esi'],
16: ['si'],
8: ['sil']
},
'rdi': {
64: ['rdi'],
32: ['edi'],
16: ['di'],
8: ['dil']
},
'rbp': {
64: ['rbp'],
32: ['ebp'],
16: ['bp'],
8: ['bpl']
},
'rsp': {
64: ['rsp'],
32: ['esp'],
16: ['sp'],
8: ['spl']
},
'r8': {
64: ['r8'],
32: ['r8d'],
16: ['r8w'],
8: ['r8b']
},
'r9': {
64: ['r9'],
32: ['r9d'],
16: ['r9w'],
8: ['r9b']
},
'r10': {
64: ['r10'],
32: ['r10d'],
16: ['r10w'],
8: ['r10b']
},
'r11': {
64: ['r11'],
32: ['r11d'],
16: ['r11w'],
8: ['r11b']
},
'r12': {
64: ['r12'],
32: ['r12d'],
16: ['r12w'],
8: ['r12b']
},
'r13': {
64: ['r13'],
32: ['r13d'],
16: ['r13w'],
8: ['r13b']
},
'r14': {
64: ['r14'],
32: ['r14d'],
16: ['r14w'],
8: ['r14b']
},
'r15': {
64: ['r15'],
32: ['r15d'],
16: ['r15w'],
8: ['r15b']
}
}

subregisters_float = {
'xmm{}'.format(i): {
128: ['xmm{}'.format(i)]
}
for i in range(0, 15 + 1)
}
1 change: 1 addition & 0 deletions src/patcherex2/components/archinfo/arm.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ class ArmInfo:
jmp_asm = "b {dst}"
jmp_size = 4
alignment = 4
bits = 32
is_variable_length_isa = False
instr_size = 4 # TODO: thumb 2
call_asm = "bl {dst}"
Expand Down
1 change: 1 addition & 0 deletions src/patcherex2/components/archinfo/mips.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ class MipsInfo:
# NOTE: keystone will always add nop for branch delay slot, so include it in size
jmp_size = 8
alignment = 4
bits = 32
is_variable_length_isa = False
instr_size = 4
call_asm = "jal {dst}"
Expand Down
1 change: 1 addition & 0 deletions src/patcherex2/components/archinfo/mips64.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ class Mips64Info:
# NOTE: keystone will aldays add nop for branch delay slot, so include it in size
jmp_size = 8
alignment = 4
bits = 64
is_variable_length_isa = False
instr_size = 4
call_asm = "jal {dst}"
Expand Down
1 change: 1 addition & 0 deletions src/patcherex2/components/archinfo/ppc.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ class PpcInfo:
jmp_asm = "b {dst}"
jmp_size = 4
alignment = 4
bits = 32
is_variable_length_isa = False
instr_size = 4
call_asm = "bl {dst}"
Expand Down
1 change: 1 addition & 0 deletions src/patcherex2/components/archinfo/ppc64.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ class Ppc64Info:
jmp_asm = "b {dst}"
jmp_size = 4
alignment = 4
bits = 64
is_variable_length_isa = False
instr_size = 4
call_asm = "bl {dst}"
Expand Down
1 change: 1 addition & 0 deletions src/patcherex2/components/archinfo/ppc_vle.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ class PpcVleInfo:
jmp_asm = "b {dst}"
jmp_size = 4
alignment = 4
bits = 32
is_variable_length_isa = True
instr_size = -1 # variable length
call_asm = "bl {dst}"
Expand Down
1 change: 1 addition & 0 deletions src/patcherex2/components/archinfo/x86.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ class X86Info:
jmp_asm = "jmp {dst}"
jmp_size = 5
alignment = 4
bits = 32
is_variable_length_isa = True
instr_size = -1 # variable length
call_asm = "call {dst}"
Expand Down
2 changes: 2 additions & 0 deletions src/patcherex2/components/assemblers/assembler.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ def _pre_assemble_hook(self, code: str, base=0) -> None:
return code

def assemble(self, code: str, base=0, symbols=None, **kwargs) -> None:
if code == "":
return bytes()
if symbols is None:
symbols = {}
logger.debug(f"Assembling `{code}` at {hex(base)}")
Expand Down
2 changes: 2 additions & 0 deletions src/patcherex2/components/compilers/clang.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ def __init__(
self, p, clang_version=15, compiler_flags: list[str] | None = None
) -> None:
super().__init__(p)
if clang_version >= 19:
self.preserve_none = True
if compiler_flags is None:
compiler_flags = []
self._compiler = f"clang-{clang_version}"
Expand Down
3 changes: 3 additions & 0 deletions src/patcherex2/components/compilers/compiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@
class Compiler:
def __init__(self, p) -> None:
self.p = p
# preserve_none is a special attribute flag to allow us to control more registers as input to a C function
# This feature is used for a C instruction patch
self.preserve_none = False

def compile(
self,
Expand Down
Loading
Loading