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

implemented x86_64 with tests #1

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
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",
]
69 changes: 69 additions & 0 deletions src/patcherex2/targets/elf_x86_64_linux.py
Original file line number Diff line number Diff line change
@@ -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()
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()
Loading