Skip to content

Commit

Permalink
Merge pull request #2 from purseclab/x86_64
Browse files Browse the repository at this point in the history
implemented x86_64 with tests
  • Loading branch information
Bilbin authored Dec 27, 2023
2 parents 4d28a32 + 63d8e64 commit 8572f72
Show file tree
Hide file tree
Showing 5 changed files with 285 additions and 0 deletions.
2 changes: 2 additions & 0 deletions src/patcherex2/targets/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -10,6 +11,7 @@
"ElfArmLinux",
"ElfArmMimxrt1052",
"ElfLeon3Bare",
"ElfX8664Linux",
"IHexPPCBare",
"Target",
]
74 changes: 74 additions & 0 deletions src/patcherex2/targets/elf_x86_64_linux.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
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()
Binary file added tests/test_binaries/x86_64/printf_nopie
Binary file not shown.
Binary file added tests/test_binaries/x86_64/replace_function_patch
Binary file not shown.
209 changes: 209 additions & 0 deletions tests/test_x86_64.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
#!/usr/bin/env python

# ruff: noqa
import logging
import os
import shutil
import subprocess
import tempfile
import unittest

from patcherex2 import *

logging.getLogger("patcherex2").setLevel("DEBUG")


class Tests(unittest.TestCase):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.bin_location = str(
os.path.join(
os.path.dirname(os.path.realpath(__file__)),
"./test_binaries/x86_64",
)
)

def test_raw_file_patch(self):
self.run_one(
"printf_nopie",
[ModifyRawBytesPatch(0x2004, b"No", addr_type="raw")],
expected_output=b"No",
expected_returnCode=0,
)

def test_raw_mem_patch(self):
self.run_one(
"printf_nopie",
[ModifyRawBytesPatch(0x402004, b"No")],
expected_output=b"No",
expected_returnCode=0,
)

def test_modify_instruction_patch(self):
self.run_one(
"printf_nopie",
[
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
mov rdi, 0x1
lea rsi, [0x402004]
mov rdx, 0x3
syscall
"""
self.run_one(
"printf_nopie",
[InsertInstructionPatch(0x40115C, instrs)],
expected_output=b"Hi\x00Hi",
expected_returnCode=0,
)

def test_insert_instruction_patch_2(self):
instrs = """
mov rax, 0x32
leave
ret
"""
self.run_one(
"printf_nopie",
[
InsertInstructionPatch("return_0x32", instrs),
ModifyInstructionPatch(0x40115C, "jmp {return_0x32}"),
],
expected_returnCode=0x32,
)

def test_remove_instruction_patch(self):
self.run_one(
"printf_nopie",
[
RemoveInstructionPatch(0x402005, num_bytes=1),
],
expected_output=b"H\x90",
expected_returnCode=0,
)

def test_modify_data_patch(self):
self.run_one(
"printf_nopie",
[ModifyDataPatch(0x402004, b"No")],
expected_output=b"No",
expected_returnCode=0,
)

def test_insert_data_patch(self, tlen=5):
p1 = InsertDataPatch("added_data", b"A" * tlen)
instrs = """
mov rax, 1
mov rdi, 1
lea rsi, [{added_data}]
mov rdx, %s
syscall
""" % hex(tlen)
p2 = InsertInstructionPatch(0x40115C, instrs)
self.run_one(
"printf_nopie",
[p1, p2],
expected_output=b"A" * tlen + b"Hi",
expected_returnCode=0,
)

def test_remove_data_patch(self):
self.run_one(
"printf_nopie",
[RemoveDataPatch(0x402005, 1)],
expected_output=b"H",
expected_returnCode=0,
)

def test_replace_function_patch(self):
code = """
int add(int a, int b){ for(;; b--, a+=2) if(b <= 0) return a; }
"""
self.run_one(
"replace_function_patch",
[ModifyFunctionPatch(0x1149, code)],
expected_output=b"70707070",
expected_returnCode=0,
)

def test_replace_function_patch_with_function_reference(self):
code = """
extern int add(int, int);
extern int subtract(int, int);
int multiply(int a, int b){ for(int c = 0;; b = subtract(b, 1), c = subtract(c, a)) if(b <= 0) return c; }
"""
self.run_one(
"replace_function_patch",
[ModifyFunctionPatch(0x1177, code)],
expected_output=b"-21-21",
expected_returnCode=0,
)

def test_replace_function_patch_with_function_reference_and_rodata(self):
code = """
extern int printf(const char *format, ...);
int multiply(int a, int b){ printf("%sWorld %s %s %s %d\\n", "Hello ", "Hello ", "Hello ", "Hello ", a * b);printf("%sWorld\\n", "Hello "); return a * b; }
"""
self.run_one(
"replace_function_patch",
[ModifyFunctionPatch(0x1177, code)],
expected_output=b"Hello World Hello Hello Hello 21\nHello World\n2121",
expected_returnCode=0,
)

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)

def dump_file(self, file):
shutil.copy(file, "/tmp/patcherex_failed_binary")
return "/tmp/patcherex_failed_binary"


if __name__ == "__main__":
unittest.main()

0 comments on commit 8572f72

Please sign in to comment.