-
Notifications
You must be signed in to change notification settings - Fork 6
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
6 changed files
with
285 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 not shown.
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() |