Skip to content

Commit

Permalink
Merge pull request #13 from purseclab/mips64el
Browse files Browse the repository at this point in the history
Implemented and added tests for mips64el
  • Loading branch information
Bilbin authored Feb 14, 2024
2 parents 09b8870 + 6fdabd6 commit 8c9051b
Show file tree
Hide file tree
Showing 5 changed files with 308 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 @@ -5,6 +5,7 @@
from .elf_i386_linux import ElfI386Linux
from .elf_leon3_bare import ElfLeon3Bare
from .elf_mips64_linux import ElfMips64Linux
from .elf_mips64el_linux import ElfMips64elLinux
from .elf_mips_linux import ElfMipsLinux
from .elf_mipsel_linux import ElfMipselLinux
from .elf_ppc64_linux import ElfPpc64Linux
Expand All @@ -22,6 +23,7 @@
"ElfI386Linux",
"ElfLeon3Bare",
"ElfMips64Linux",
"ElfMips64elLinux",
"ElfMipsLinux",
"ElfMipselLinux",
"ElfPpc64Linux",
Expand Down
75 changes: 75 additions & 0 deletions src/patcherex2/targets/elf_mips64el_linux.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
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 ElfMips64elLinux(Target):
NOP_BYTES = b"\x00\x00\x00\x00"
NOP_SIZE = 4
JMP_ASM = "j {dst}"
# NOTE: keystone will always add nop for branch delay slot, so include it in size
JMP_SIZE = 8

@staticmethod
def detect_target(binary_path):
with open(binary_path, "rb") as f:
magic = f.read(0x14)
if magic.startswith(b"\x7fELF\x02") and magic.startswith(
b"\x08\x00", 0x12
): # EM_MIPS
return True
return False

def get_assembler(self, assembler):
assembler = assembler or "keystone"
if assembler == "keystone":
return Keystone(
self.p,
keystone.KS_ARCH_MIPS,
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, compiler_flags=["--target=mips64el-linux-gnuabi64"])
raise NotImplementedError()

def get_disassembler(self, disassembler):
disassembler = disassembler or "capstone"
if disassembler == "capstone":
return Capstone(
capstone.CS_ARCH_MIPS,
capstone.CS_MODE_LITTLE_ENDIAN + capstone.CS_MODE_MIPS64,
)
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/mips64el/printf_nopie
Binary file not shown.
Binary file not shown.
231 changes: 231 additions & 0 deletions tests/test_mips64el.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,231 @@
#!/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/mips64el",
)
)

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

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

def test_modify_instruction_patch(self):
self.run_one(
"printf_nopie",
[
ModifyInstructionPatch(0x120000AB0, "daddiu $a1, $at, 0xbd0"),
],
expected_output=b"%s",
expected_returnCode=0,
)

def test_insert_instruction_patch(self):
instrs = """
li $v0, 0x1389
li $a0, 0x1
ld $at, -0x7fc0($gp)
daddiu $a1, $at, 0xbd3
li $a2, 0x3
syscall
"""
self.run_one(
"printf_nopie",
[InsertInstructionPatch(0x120000AC0, instrs)],
expected_output=b"Hi\x00Hi",
expected_returnCode=0,
)

def test_insert_instruction_patch_2(self):
instrs = """
li $v0, 0x13c2
li $a0, 0x32
syscall
"""
self.run_one(
"printf_nopie",
[
InsertInstructionPatch("return_0x32", instrs),
ModifyInstructionPatch(0x120000AC0, "j {return_0x32}"),
],
expected_returnCode=0x32,
)

def test_remove_instruction_patch(self):
self.run_one(
"printf_nopie",
[
RemoveInstructionPatch(0x120000BD4, num_bytes=4),
],
expected_output=b"H",
expected_returnCode=0,
)

def test_modify_data_patch(self):
self.run_one(
"printf_nopie",
[ModifyDataPatch(0x120000BD3, 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 = """
li $v0, 0x1389
li $a0, 0x1
dla $a1, {added_data}
li $a2, %s
syscall
""" % hex(tlen)
p2 = InsertInstructionPatch(0x120000AC0, 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(0x120000BD4, 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(0xC00, 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(0xD00, code)],
expected_output=b"-21-21",
expected_returnCode=0,
)

@pytest.mark.skip(reason="waiting for cle relocation support")
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(0xD00, code)],
expected_output=b"Hello World Hello Hello Hello 21\nHello World\n2121",
expected_returnCode=0,
)

@pytest.mark.skip(reason="waiting for cle relocation support")
def test_insert_function_patch(self):
insert_code = """
int min(int a, int b) { return (a < b) ? a : b; }
"""
replace_code = """
extern int min(int, int);
int max(int a, int b) { return min(a, b); }
"""
self.run_one(
"replace_function_patch",
[
InsertFunctionPatch("min", insert_code),
ModifyFunctionPatch(0xE88, replace_code),
],
expected_output=b"2121212121",
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(
["qemu-mips64el", "-L", "/usr/mips64el-linux-gnuabi64", 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 8c9051b

Please sign in to comment.