From 4d7580e94ee6c4c4f36e52eee285cfb7bb2fc791 Mon Sep 17 00:00:00 2001 From: Nick Andry Date: Wed, 27 Dec 2023 12:24:35 -0500 Subject: [PATCH 1/2] implemented x86_64 with tests --- src/patcherex2/targets/__init__.py | 2 + src/patcherex2/targets/elf_x86_64_linux.py | 69 ++++++ tests/test_binaries/x86_64/printf_nopie | Bin 0 -> 15904 bytes .../x86_64/replace_function_patch | Bin 0 -> 16168 bytes tests/test_x86_64.py | 209 ++++++++++++++++++ 5 files changed, 280 insertions(+) create mode 100644 src/patcherex2/targets/elf_x86_64_linux.py create mode 100755 tests/test_binaries/x86_64/printf_nopie create mode 100755 tests/test_binaries/x86_64/replace_function_patch create mode 100644 tests/test_x86_64.py diff --git a/src/patcherex2/targets/__init__.py b/src/patcherex2/targets/__init__.py index f9ccc9f..25fd2b1 100644 --- a/src/patcherex2/targets/__init__.py +++ b/src/patcherex2/targets/__init__.py @@ -2,6 +2,7 @@ from .elf_arm_linux import ElfArmLinux from .elf_arm_mimxrt1052 import ElfArmMimxrt1052 from .elf_leon3_bare import ElfLeon3Bare +from .elf_x86_64_linux import ElfX8664Linux from .ihex_ppc_bare import IHexPPCBare from .target import Target @@ -10,6 +11,7 @@ "ElfArmLinux", "ElfArmMimxrt1052", "ElfLeon3Bare", + "ElfX8664Linux", "IHexPPCBare", "Target", ] diff --git a/src/patcherex2/targets/elf_x86_64_linux.py b/src/patcherex2/targets/elf_x86_64_linux.py new file mode 100644 index 0000000..33e825b --- /dev/null +++ b/src/patcherex2/targets/elf_x86_64_linux.py @@ -0,0 +1,69 @@ +from ..components.allocation_managers.allocation_manager import AllocationManager +from ..components.assemblers.keystone import Keystone, keystone +from ..components.binary_analyzers.angr import Angr +from ..components.binfmt_tools.elf import ELF +from ..components.compilers.clang import Clang +from ..components.disassemblers.capstone import Capstone, capstone +from ..components.utils.utils import Utils +from .target import Target + + +class ElfX8664Linux(Target): + NOP_BYTES = b"\x90" + NOP_SIZE = 1 + JMP_ASM = "jmp {dst}" + JMP_SIZE = 6 + + @staticmethod + def detect_target(binary_path): + with open(binary_path, "rb") as f: + magic = f.read(0x14) + if magic.startswith(b"\x7fELF") and magic.startswith( + b"\x3e\x00", 0x12 + ): # EM_X86_64 + return True + return False + + def get_assembler(self, assembler): + assembler = assembler or "keystone" + if assembler == "keystone": + return Keystone( + self.p, keystone.KS_ARCH_X86, keystone.KS_MODE_LITTLE_ENDIAN + keystone.KS_MODE_64 + ) + raise NotImplementedError() + + def get_allocation_manager(self, allocation_manager): + allocation_manager = allocation_manager or "default" + if allocation_manager == "default": + return AllocationManager(self.p) + raise NotImplementedError() + + def get_compiler(self, compiler): + compiler = compiler or "clang" + if compiler == "clang": + return Clang(self.p) + raise NotImplementedError() + + def get_disassembler(self, disassembler): + disassembler = disassembler or "capstone" + if disassembler == "capstone": + return Capstone(capstone.CS_ARCH_X86, capstone.CS_MODE_LITTLE_ENDIAN + capstone.CS_MODE_64) + raise NotImplementedError() + + def get_binfmt_tool(self, binfmt_tool): + binfmt_tool = binfmt_tool or "pyelftools" + if binfmt_tool == "pyelftools": + return ELF(self.p, self.binary_path) + raise NotImplementedError() + + def get_binary_analyzer(self, binary_analyzer): + binary_analyzer = binary_analyzer or "angr" + if binary_analyzer == "angr": + return Angr(self.binary_path) + raise NotImplementedError() + + def get_utils(self, utils): + utils = utils or "default" + if utils == "default": + return Utils(self.p, self.binary_path) + raise NotImplementedError() diff --git a/tests/test_binaries/x86_64/printf_nopie b/tests/test_binaries/x86_64/printf_nopie new file mode 100755 index 0000000000000000000000000000000000000000..3031babae423bfc0b42ef379d238aaddff84208a GIT binary patch literal 15904 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 Date: Wed, 27 Dec 2023 12:31:42 -0500 Subject: [PATCH 2/2] ruff reformatting --- src/patcherex2/targets/elf_x86_64_linux.py | 21 ++++-- tests/test_x86_64.py | 88 +++++++++++----------- 2 files changed, 57 insertions(+), 52 deletions(-) diff --git a/src/patcherex2/targets/elf_x86_64_linux.py b/src/patcherex2/targets/elf_x86_64_linux.py index 33e825b..b2b7d87 100644 --- a/src/patcherex2/targets/elf_x86_64_linux.py +++ b/src/patcherex2/targets/elf_x86_64_linux.py @@ -23,14 +23,16 @@ def detect_target(binary_path): ): # EM_X86_64 return True return False - + def get_assembler(self, assembler): - assembler = assembler or "keystone" - if assembler == "keystone": - return Keystone( - self.p, keystone.KS_ARCH_X86, keystone.KS_MODE_LITTLE_ENDIAN + keystone.KS_MODE_64 - ) - raise NotImplementedError() + assembler = assembler or "keystone" + if assembler == "keystone": + return Keystone( + self.p, + keystone.KS_ARCH_X86, + keystone.KS_MODE_LITTLE_ENDIAN + keystone.KS_MODE_64, + ) + raise NotImplementedError() def get_allocation_manager(self, allocation_manager): allocation_manager = allocation_manager or "default" @@ -47,7 +49,10 @@ def get_compiler(self, compiler): def get_disassembler(self, disassembler): disassembler = disassembler or "capstone" if disassembler == "capstone": - return Capstone(capstone.CS_ARCH_X86, capstone.CS_MODE_LITTLE_ENDIAN + capstone.CS_MODE_64) + return Capstone( + capstone.CS_ARCH_X86, + capstone.CS_MODE_LITTLE_ENDIAN + capstone.CS_MODE_64, + ) raise NotImplementedError() def get_binfmt_tool(self, binfmt_tool): diff --git a/tests/test_x86_64.py b/tests/test_x86_64.py index e419523..7527ffc 100644 --- a/tests/test_x86_64.py +++ b/tests/test_x86_64.py @@ -43,13 +43,13 @@ def test_modify_instruction_patch(self): self.run_one( "printf_nopie", [ - ModifyInstructionPatch(0x40113e, "lea rax, [0x402007]"), + ModifyInstructionPatch(0x40113E, "lea rax, [0x402007]"), ModifyInstructionPatch(0x401145, "mov rsi, rax"), ], expected_output=b"%s", expected_returnCode=0, ) - + def test_insert_instruction_patch(self): instrs = """ mov rax, 0x1 @@ -60,7 +60,7 @@ def test_insert_instruction_patch(self): """ self.run_one( "printf_nopie", - [InsertInstructionPatch(0x40115c, instrs)], + [InsertInstructionPatch(0x40115C, instrs)], expected_output=b"Hi\x00Hi", expected_returnCode=0, ) @@ -75,7 +75,7 @@ def test_insert_instruction_patch_2(self): "printf_nopie", [ InsertInstructionPatch("return_0x32", instrs), - ModifyInstructionPatch(0x40115c, "jmp {return_0x32}"), + ModifyInstructionPatch(0x40115C, "jmp {return_0x32}"), ], expected_returnCode=0x32, ) @@ -107,7 +107,7 @@ def test_insert_data_patch(self, tlen=5): mov rdx, %s syscall """ % hex(tlen) - p2 = InsertInstructionPatch(0x40115c, instrs) + p2 = InsertInstructionPatch(0x40115C, instrs) self.run_one( "printf_nopie", [p1, p2], @@ -160,45 +160,45 @@ def test_replace_function_patch_with_function_reference_and_rodata(self): ) def run_one( - self, - filename, - patches, - set_oep=None, - inputvalue=None, - expected_output=None, - expected_returnCode=None, - ): - filepath = os.path.join(self.bin_location, filename) - pipe = subprocess.PIPE - - with tempfile.TemporaryDirectory() as td: - tmp_file = os.path.join(td, "patched") - p = Patcherex(filepath) - for patch in patches: - p.patches.append(patch) - p.apply_patches() - p.binfmt_tool.save_binary(tmp_file) - # os.system(f"readelf -hlS {tmp_file}") - - p = subprocess.Popen( - [tmp_file], - stdin=pipe, - stdout=pipe, - stderr=pipe, - ) - res = p.communicate(inputvalue) - if expected_output: - if res[0] != expected_output: - self.fail( - f"AssertionError: {res[0]} != {expected_output}, binary dumped: {self.dump_file(tmp_file)}" - ) - # self.assertEqual(res[0], expected_output) - if expected_returnCode: - if p.returncode != expected_returnCode: - self.fail( - f"AssertionError: {p.returncode} != {expected_returnCode}, binary dumped: {self.dump_file(tmp_file)}" - ) - # self.assertEqual(p.returncode, expected_returnCode) + self, + filename, + patches, + set_oep=None, + inputvalue=None, + expected_output=None, + expected_returnCode=None, + ): + filepath = os.path.join(self.bin_location, filename) + pipe = subprocess.PIPE + + with tempfile.TemporaryDirectory() as td: + tmp_file = os.path.join(td, "patched") + p = Patcherex(filepath) + for patch in patches: + p.patches.append(patch) + p.apply_patches() + p.binfmt_tool.save_binary(tmp_file) + # os.system(f"readelf -hlS {tmp_file}") + + p = subprocess.Popen( + [tmp_file], + stdin=pipe, + stdout=pipe, + stderr=pipe, + ) + res = p.communicate(inputvalue) + if expected_output: + if res[0] != expected_output: + self.fail( + f"AssertionError: {res[0]} != {expected_output}, binary dumped: {self.dump_file(tmp_file)}" + ) + # self.assertEqual(res[0], expected_output) + if expected_returnCode: + if p.returncode != expected_returnCode: + self.fail( + f"AssertionError: {p.returncode} != {expected_returnCode}, binary dumped: {self.dump_file(tmp_file)}" + ) + # self.assertEqual(p.returncode, expected_returnCode) def dump_file(self, file): shutil.copy(file, "/tmp/patcherex_failed_binary")