Skip to content

Commit

Permalink
Merge pull request #6 from purseclab/ppc
Browse files Browse the repository at this point in the history
Ppc init imp
  • Loading branch information
DennyDai authored Jan 30, 2024
2 parents 516faaa + 2910b15 commit ed68408
Show file tree
Hide file tree
Showing 6 changed files with 299 additions and 8 deletions.
16 changes: 8 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,13 @@ Coming soon.

| | Linux x86 | Linux amd64 | Linux arm | Linux aarch64 | Linux PowerPC (32bit) | Linux PowerPC (64bit) | Linux MIPS (32bit) | Linux MIPS (64bit) | SPARCv8 (LEON3) | PowerPC (VLE) (IHEX)
|-|-|-|-|-|-|-|-|-|-|-|
InsertDataPatch | 🟩 | 🟩 | 🟩 | 🟩 | 🟪 | 🟪 | 🟩 | 🟩 | ⬜ | ⬜ | 🟩 | 🟩 |
RemoveDataPatch | 🟩 | 🟩 | 🟩 | 🟩 | 🟪 | 🟪 | 🟩 | 🟩 | ⬜ | ⬜ | 🟩 | 🟩 |
ModifyDataPatch | 🟩 | 🟩 | 🟩 | 🟩 | 🟪 | 🟪 | 🟩 | 🟩 | ⬜ | ⬜ | 🟩 | 🟩 |
InsertInstructionPatch | 🟩 | 🟩 | 🟩 | 🟩 | 🟪 | 🟪 | 🟩 | 🟩 | ⬜ | ⬜ | 🟩 | 🟩 |
RemoveInstructionPatch | 🟩 | 🟩 | 🟩 | 🟩 | 🟪 | 🟪 | 🟩 | 🟩 | ⬜ | ⬜ | 🟩 | 🟩 |
ModifyInstructionPatch | 🟩 | 🟩 | 🟩 | 🟩 | 🟪 | 🟪 | 🟩 | 🟩 | ⬜ | ⬜ | 🟩 | 🟩 |
InsertFunctionPatch | ⬜ | ⬜ | ⬜ | ⬜ | 🟪 | 🟪 | ⬜ | ⬜ | ⬜ | ⬜ | ⬜ | ⬜ |
ModifyFunctionPatch | 🟨 | 🟩 | 🟩 | 🟩 | 🟪 | 🟪 | 🟨 | 🟨 | ⬜ | ⬜ | 🟩 | 🟩 |
InsertDataPatch | 🟩 | 🟩 | 🟩 | 🟩 | 🟩 | 🟪 | 🟩 | 🟩 | ⬜ | ⬜ | 🟩 | 🟩 |
RemoveDataPatch | 🟩 | 🟩 | 🟩 | 🟩 | 🟩 | 🟪 | 🟩 | 🟩 | ⬜ | ⬜ | 🟩 | 🟩 |
ModifyDataPatch | 🟩 | 🟩 | 🟩 | 🟩 | 🟩 | 🟪 | 🟩 | 🟩 | ⬜ | ⬜ | 🟩 | 🟩 |
InsertInstructionPatch | 🟩 | 🟩 | 🟩 | 🟩 | 🟩 | 🟪 | 🟩 | 🟩 | ⬜ | ⬜ | 🟩 | 🟩 |
RemoveInstructionPatch | 🟩 | 🟩 | 🟩 | 🟩 | 🟩 | 🟪 | 🟩 | 🟩 | ⬜ | ⬜ | 🟩 | 🟩 |
ModifyInstructionPatch | 🟩 | 🟩 | 🟩 | 🟩 | 🟩 | 🟪 | 🟩 | 🟩 | ⬜ | ⬜ | 🟩 | 🟩 |
InsertFunctionPatch | ⬜ | ⬜ | ⬜ | ⬜ | | 🟪 | ⬜ | ⬜ | ⬜ | ⬜ | ⬜ | ⬜ |
ModifyFunctionPatch | 🟨 | 🟩 | 🟩 | 🟩 | 🟨 | 🟪 | 🟨 | 🟨 | ⬜ | ⬜ | 🟩 | 🟩 |

🟩 Fully Functional, 🟨 Limited Functionality, 🟥 Not Working, ⬜ Not Tested, 🟪 Work in Progress
2 changes: 2 additions & 0 deletions src/patcherex2/targets/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from .elf_leon3_bare import ElfLeon3Bare
from .elf_mips64_linux import ElfMips64Linux
from .elf_mips_linux import ElfMipsLinux
from .elf_ppc_linux import ElfPpcLinux
from .elf_x86_64_linux import ElfX8664Linux
from .elf_x86_64_linux_recomp import ElfX8664LinuxRecomp
from .ihex_ppc_bare import IHexPPCBare
Expand All @@ -20,6 +21,7 @@
"ElfLeon3Bare",
"ElfMips64Linux",
"ElfMipsLinux",
"ElfPpcLinux",
"ElfX8664Linux",
"ElfX8664LinuxRecomp",
"IHexPPCBare",
Expand Down
76 changes: 76 additions & 0 deletions src/patcherex2/targets/elf_ppc_linux.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
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 ElfPpcLinux(Target):
NOP_BYTES = b"\x60\x00\x00\x00"
NOP_SIZE = 4
JMP_ASM = "b {dst}"
JMP_SIZE = 4

@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"\x00\x14", 0x12
): # EM_PPC
return True
return False

def get_assembler(self, assembler):
assembler = assembler or "keystone"
if assembler == "keystone":
return Keystone(
self.p,
keystone.KS_ARCH_PPC,
keystone.KS_MODE_BIG_ENDIAN + keystone.KS_MODE_PPC32,
)
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", "powerpc-linux-gnu"])
raise NotImplementedError()

def get_disassembler(self, disassembler):
disassembler = disassembler or "capstone"
if disassembler == "capstone":
cs = Capstone(
capstone.CS_ARCH_PPC, capstone.CS_MODE_BIG_ENDIAN + capstone.CS_MODE_32
)
# NOTE: Doing this because keystone expects registers to just be numbers
cs.cs.syntax = capstone.CS_OPT_SYNTAX_NOREGNAME
return cs
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/ppc/printf_nopie
Binary file not shown.
Binary file added tests/test_binaries/ppc/replace_function_patch
Binary file not shown.
213 changes: 213 additions & 0 deletions tests/test_ppc.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,213 @@
#!/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/ppc",
)
)

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

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

def test_modify_instruction_patch(self):
self.run_one(
"printf_nopie",
[
ModifyInstructionPatch(0x10000514, "addi 4, 9, 0x6ac"),
],
expected_output=b"%s",
expected_returnCode=0,
)

def test_insert_instruction_patch(self):
instrs = """
li 0, 0x4
li 3, 1
lis 9, 0x1000
addi 4, 9, 0x6a4
li 5, 0x3
sc
"""
self.run_one(
"printf_nopie",
[InsertInstructionPatch(0x10000528, instrs)],
expected_output=b"Hi\x00Hi",
expected_returnCode=0,
)

def test_insert_instruction_patch_2(self):
instrs = """
li 3, 0x32
li 0, 0x1
sc
"""
self.run_one(
"printf_nopie",
[
InsertInstructionPatch("return_0x32", instrs),
ModifyInstructionPatch(0x10000528, "b {return_0x32}"),
],
expected_returnCode=0x32,
)

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

def test_modify_data_patch(self):
self.run_one(
"printf_nopie",
[ModifyDataPatch(0x100006A4, 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 0, 0x4
li 3, 0x1
lis 9, {added_data}@h
addi 4, 9, {added_data}@l
li 5, %s
sc
""" % hex(tlen)
p2 = InsertInstructionPatch(0x10000528, 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(0x100006A5, 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(0x100004FC, 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(0x100005AC, 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(0x100005AC, 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(
["qemu-ppc", "-L", "/usr/powerpc-linux-gnu", 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 ed68408

Please sign in to comment.