Skip to content

Commit

Permalink
Merge branch 'i386'
Browse files Browse the repository at this point in the history
  • Loading branch information
DennyDai committed Jan 8, 2024
2 parents 8572f72 + 2dc9cf0 commit d800d7d
Show file tree
Hide file tree
Showing 6 changed files with 285 additions and 2 deletions.
12 changes: 10 additions & 2 deletions src/patcherex2/components/utils/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,20 @@ def insert_trampoline_code(self, addr, instrs, force_insert=False, detour_pos=-1
assert force_insert or self.is_valid_insert_point(
addr
), f"Cannot insert instruction at {hex(addr)}"
moved_instrs = self.get_instrs_to_be_moved(addr)
moved_instrs_len = len(
self.p.assembler.assemble(
moved_instrs,
addr, # TODO: we don't really need this addr, but better than 0x0 because 0x0 is too far away from the code
is_thumb=self.p.binary_analyzer.is_thumb(addr),
)
)
trempoline_instrs_with_jump_back = (
instrs
+ "\n"
+ self.get_instrs_to_be_moved(addr)
+ moved_instrs
+ "\n"
+ self.p.target.JMP_ASM.format(dst=hex(addr + self.p.target.JMP_SIZE))
+ self.p.target.JMP_ASM.format(dst=hex(addr + moved_instrs_len))
)
trempoline_size = (
len(
Expand Down
2 changes: 2 additions & 0 deletions src/patcherex2/targets/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from .elf_aarch64_linux import ElfAArch64Linux
from .elf_arm_linux import ElfArmLinux
from .elf_arm_mimxrt1052 import ElfArmMimxrt1052
from .elf_i386_linux import ElfI386Linux
from .elf_leon3_bare import ElfLeon3Bare
from .elf_x86_64_linux import ElfX8664Linux
from .ihex_ppc_bare import IHexPPCBare
Expand All @@ -10,6 +11,7 @@
"ElfAArch64Linux",
"ElfArmLinux",
"ElfArmMimxrt1052",
"ElfI386Linux",
"ElfLeon3Bare",
"ElfX8664Linux",
"IHexPPCBare",
Expand Down
74 changes: 74 additions & 0 deletions src/patcherex2/targets/elf_i386_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 ElfI386Linux(Target):
NOP_BYTES = b"\x90"
NOP_SIZE = 1
JMP_ASM = "jmp {dst}"
JMP_SIZE = 5

@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"\x03\x00", 0x12
): # EM_386
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_32,
)
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, compiler_flags=["-m32"])
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_32,
)
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/i386/printf_nopie
Binary file not shown.
Binary file added tests/test_binaries/i386/replace_function_patch
Binary file not shown.
199 changes: 199 additions & 0 deletions tests/test_i386.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
#!/usr/bin/env python

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

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/i386",
)
)

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

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

def test_modify_instruction_patch(self):
self.run_one(
"printf_nopie",
[
ModifyInstructionPatch(0x8049192, "lea edx, [0x804a00b]"),
ModifyInstructionPatch(0x8049198, "push edx"),
],
expected_output=b"%s",
expected_returnCode=0,
)

def test_insert_instruction_patch(self):
instrs = """
mov eax, 0x4
mov ebx, 0x1
lea ecx, [0x804a008]
mov edx, 0x3
int 0x80
"""
self.run_one(
"printf_nopie",
[InsertInstructionPatch(0x80491A7, instrs)],
expected_output=b"Hi\x00Hi",
expected_returnCode=0,
)

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

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

def test_modify_data_patch(self):
self.run_one(
"printf_nopie",
[ModifyDataPatch(0x804A008, 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 eax, 0x4
mov ebx, 0x1
lea ecx, [{added_data}]
mov edx, %s
int 0x80
""" % hex(tlen)
p2 = InsertInstructionPatch(0x80491A7, 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(0x804A009, 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(0x119D, code)],
expected_output=b"70707070",
expected_returnCode=0,
)

@pytest.mark.skip(reason="waiting for cle relocation support")
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(0x11C9, code)],
expected_output=b"-21-21",
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 d800d7d

Please sign in to comment.