Skip to content

Commit

Permalink
Support for PPC VLE as a sub-isa. Patchmaker support, plus automated …
Browse files Browse the repository at this point in the history
…tests. Ghidra analysis and unpackers support.
  • Loading branch information
Paul Noalhyt committed Jun 26, 2023
1 parent 7bdeeef commit b3c1af4
Show file tree
Hide file tree
Showing 17 changed files with 390 additions and 10 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from typing import Tuple, Dict, Union, List, Iterable

from ofrak.core.architecture import ProgramAttributes
from ofrak_type.architecture import InstructionSet, InstructionSetMode
from ofrak_type.architecture import InstructionSet, InstructionSetMode, SubInstructionSet
from ofrak.core.basic_block import BasicBlockUnpacker, BasicBlock
from ofrak.core.instruction import Instruction
from ofrak.resource import ResourceFactory, Resource
Expand Down Expand Up @@ -174,6 +174,22 @@ def _asm_fixups(
operand = re.sub(r"a([0-7])", r"%A\1", operand)
operand = re.sub(r"d([0-7])[bw]?", r"%D\1", operand)
operands += operand
elif program_attrs.sub_isa is SubInstructionSet.PPCVLE:
# in Ghidra, offsets from a register like in `se_stw r0,0x9(r1)` are expressed in words.
# so in this example r0 is stored at r1+0x9*4=r1+0x24
# But it is more natural to express it in bytes, to get the instruction `se_stw r0,0x24(r1)`
# (this is also the convention used by the VLE assembler)

def replace_offset(match):
new_operand = match.group(1)
new_operand += f"0x{int(match.group(2), 0)*4:x}"
new_operand += match.group(3)
return new_operand

mnemonic = base_mnemonic
operands = re.sub(
r"(.*, )(0x[0-9]+)(\(r[0-9]+\))", lambda match: replace_offset(match), operands
)
else:
mnemonic = base_mnemonic
return mnemonic, operands
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from typing import Optional, List

from ofrak import ResourceFilter
from ofrak.core import CodeRegion
from ofrak.core import CodeRegion, ProgramAttributes, Elf, ElfUnpacker
from ofrak.component.analyzer import Analyzer
from ofrak.component.modifier import Modifier
from ofrak.model.component_model import ComponentConfig
Expand All @@ -33,6 +33,7 @@
OfrakGhidraMixin,
GhidraComponentException,
)
from ofrak_type import InstructionSet, SubInstructionSet

LOGGER = logging.getLogger(__name__)

Expand Down Expand Up @@ -106,7 +107,16 @@ async def analyze(

ghidra_project = f"{GHIDRA_REPOSITORY_HOST}:{GHIDRA_REPOSITORY_PORT}/ofrak"

program_name = await self._do_ghidra_import(ghidra_project, full_fname)
program_attributes = None
if resource.has_tag(Elf):
await resource.run(ElfUnpacker)
program_attributes = await resource.analyze(ProgramAttributes)
else:
logging.warning(
f"Could not get ProgramAttributes for resource {resource.get_id()} because it doesn't have the Elf tag."
)

program_name = await self._do_ghidra_import(ghidra_project, full_fname, program_attributes)
await self._do_ghidra_analyze_and_serve(
ghidra_project, program_name, skip_analysis=config is not None
)
Expand All @@ -116,7 +126,7 @@ async def analyze(

return GhidraProject(ghidra_project, f"{GHIDRA_SERVER_HOST}:{GHIDRA_SERVER_PORT}")

async def _do_ghidra_import(self, ghidra_project: str, full_fname: str):
async def _do_ghidra_import(self, ghidra_project: str, full_fname: str, program_attributes):
args = [
ghidra_project,
"-connect",
Expand All @@ -127,6 +137,14 @@ async def _do_ghidra_import(self, ghidra_project: str, full_fname: str):
"-overwrite",
]

if (
program_attributes is not None
and program_attributes.isa == InstructionSet.PPC
and program_attributes.sub_isa == SubInstructionSet.PPCVLE
):
args.extend(["-scriptPath", "'" + (";".join(self._script_directories)) + "'"])
args.extend(["-preScript", "PreAnalyzePPCVLE.java"])

cmd_str = " ".join([GHIDRA_HEADLESS_EXEC] + args)
LOGGER.debug(f"Running command: {cmd_str}")
ghidra_proc = await asyncio.create_subprocess_exec(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import ghidra.app.script.GhidraScript;
import ghidra.program.model.mem.*;
import ghidra.program.model.lang.*;
import ghidra.program.model.pcode.*;
import ghidra.program.model.util.*;
import ghidra.program.model.reloc.*;
import ghidra.program.model.data.*;
import ghidra.program.model.block.*;
import ghidra.program.model.symbol.*;
import ghidra.program.model.scalar.*;
import ghidra.program.model.listing.*;
import ghidra.program.model.address.*;

import java.math.BigInteger;

public class PreAnalyzePPCVLE extends GhidraScript {

@Override
public void run() throws Exception {
try {
// Set the language to PPC VLE
Language language = (Language) getLanguage(new LanguageID("PowerPC:BE:64:VLE-32addr"));
Program p = currentProgram;
p.setLanguage(language, language.getDefaultCompilerSpec().getCompilerSpecID(), false, monitor);
ProgramContext programContext = p.getProgramContext();
// Set the vle bit (Ghidra has a "vle" register for that, but on real devices, the VLE
// bit is defined per memory page, as a page attribute bit) so that instructions are
// decoded correctly.
for (Register register : programContext.getContextRegisters()) {
if (register.getName().equals("vle")){
RegisterValue newValue = new RegisterValue(programContext.getBaseContextRegister());
BigInteger value = BigInteger.ONE;
newValue = setRegisterValue(newValue, register, value);
programContext.setDefaultDisassemblyContext(newValue);
println("Set the vle bit.");
}
}
} catch(Exception e) {
println(e.toString());
e.printStackTrace(System.out);
throw e;
}
}

private RegisterValue setRegisterValue(RegisterValue registerValue, Register register,
BigInteger value) {
RegisterValue newValue = new RegisterValue(register, value);
return registerValue.combineValues(newValue);
}

}
3 changes: 2 additions & 1 deletion ofrak_core/ofrak/core/elf/analyzer.py
Original file line number Diff line number Diff line change
Expand Up @@ -370,6 +370,7 @@ class ElfProgramAttributesAnalyzer(Analyzer[None, ProgramAttributes]):
async def analyze(
self, resource: Resource, config: Optional[ComponentConfig] = None
) -> ProgramAttributes:
elf_resource = await resource.view_as(Elf)
elf_header = await resource.get_only_descendant_as_view(
ElfHeader, r_filter=ResourceFilter.with_tags(ElfHeader)
)
Expand All @@ -379,7 +380,7 @@ async def analyze(

return ProgramAttributes(
elf_header.get_isa(),
None,
await elf_resource.get_sub_isa(),
elf_basic_header.get_bitwidth(),
elf_basic_header.get_endianness(),
None,
Expand Down
25 changes: 24 additions & 1 deletion ofrak_core/ofrak/core/elf/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

from ofrak.model.viewable_tag_model import AttributesType

from ofrak_type.architecture import InstructionSet
from ofrak_type.architecture import InstructionSet, SubInstructionSet
from ofrak.core.program import Program
from ofrak.core.program_section import NamedProgramSection, ProgramSegment
from ofrak.model.resource_model import index
Expand Down Expand Up @@ -863,5 +863,28 @@ async def get_program_header_by_index(self, index: int) -> ElfProgramHeader:
),
)

async def get_sub_isa(self) -> Optional[SubInstructionSet]:
elf_header = await self.get_header()
isa = elf_header.get_isa()
if isa == InstructionSet.PPC:
# We can detect whether the elf is PPC VLE by looking at the section header flags, as described here: https://reverseengineering.stackexchange.com/questions/20863/powerpc-elf32-detecting-vle
PF_PPC_VLE = 0x10000000
SHF_PPC_VLE = 0x10000000
ppc_vle = False
program_headers = await self.get_program_headers()
for program_header in program_headers:
if program_header.p_flags & PF_PPC_VLE != 0:
ppc_vle = True
break
if not ppc_vle:
section_headers = await self.get_section_headers()
for section_header in section_headers:
if section_header.sh_flags & SHF_PPC_VLE != 0:
ppc_vle = True
break
if ppc_vle:
return SubInstructionSet.PPCVLE
return None


MagicDescriptionIdentifier.register(Elf, lambda s: s.startswith("ELF "))
13 changes: 13 additions & 0 deletions ofrak_patch_maker/Dockerstub
Original file line number Diff line number Diff line change
Expand Up @@ -75,3 +75,16 @@ RUN apt-get update && apt-get install -y gcc-avr binutils-avr avr-libc
RUN if [ "$TARGETARCH" = "amd64" ]; then \
apt-get update && apt-get install -y gcc-10-powerpc-linux-gnu; \
fi;

#PPCVLE 4 NXP GCC Fork
# Download the toolchain into your OFRAK directory from here (requires sign up):
# https://www.nxp.com/design/software/development-software/s32-design-studio-ide/s32-design-studio-for-power-architecture:S32DS-PA
ARG OFRAK_DIR=.
COPY $OFRAK_DIR/gcc-4.9.4-Ee200-eabivle-x86_64-linux-g2724867.zip /tmp
RUN cd /tmp && \
unzip gcc-4.9.4-Ee200-eabivle-x86_64-linux-g2724867.zip && \
cd powerpc-eabivle-4_9 && \
for f in *; do test -d "${f}" && cp -r "${f}" /usr/; done && \
dpkg --add-architecture i386 && \
apt-get update && \
apt-get install -y libc6:i386 libncurses5:i386 libstdc++6:i386 lib32z1
7 changes: 7 additions & 0 deletions ofrak_patch_maker/ofrak_patch_maker/toolchain.conf
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,17 @@ COMPILER = /usr/bin/powerpc-linux-gnu-gcc-10
LINKER = /usr/bin/powerpc-linux-gnu-ld
BIN_PARSER = /usr/bin/powerpc-linux-gnu-objdump

[GNU_PPCVLE_4]
PREPROCESSOR = /usr/bin/powerpc-eabivle-gcc
COMPILER = /usr/bin/powerpc-eabivle-gccbloop
LINKER = /usr/bin/powerpc-eabivle-ld
BIN_PARSER = /usr/bin/powerpc-eabivle-objdump

[ASM]
ARM_ASM_PATH = /opt/rbs/toolchain/gcc-arm-none-eabi-10-2020-q4-major/bin/arm-none-eabi-as
X86_64_ASM_PATH = /opt/rbs/toolchain/binutils-2.34/gas/as-new
M68K_ASM_PATH = /usr/bin/m68k-linux-gnu-as
AARCH64_ASM_PATH = /usr/bin/aarch64-linux-gnu-as
AVR_ASM_PATH = /usr/bin/avr-as
PPC_ASM_PATH = /usr/bin/powerpc-linux-gnu-as
PPCVLE_ASM_PATH = /usr/bin/powerpc-eabivle-as
7 changes: 6 additions & 1 deletion ofrak_patch_maker/ofrak_patch_maker/toolchain/abstract.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
from ofrak_patch_maker.binary_parser.abstract import AbstractBinaryFileParser
from ofrak_patch_maker.toolchain.model import Segment, ToolchainConfig
from ofrak_patch_maker.toolchain.utils import get_repository_config
from ofrak_type.architecture import InstructionSet
from ofrak_type.architecture import InstructionSet, SubInstructionSet
from ofrak_type.bit_width import BitWidth
from ofrak_type.memory_permissions import MemoryPermissions
from ofrak_type.symbol_type import LinkableSymbolType
Expand Down Expand Up @@ -156,6 +156,11 @@ def _assembler_path(self) -> str:
and self._processor.bit_width == BitWidth.BIT_64
):
assembler_path = "X86_64_ASM_PATH"
elif (
self._processor.isa == InstructionSet.PPC
and self._processor.sub_isa == SubInstructionSet.PPCVLE
):
assembler_path = "PPCVLE_ASM_PATH"
else:
assembler_path = f"{self._processor.isa.value.upper()}_ASM_PATH"
return get_repository_config("ASM", assembler_path)
Expand Down
83 changes: 81 additions & 2 deletions ofrak_patch_maker/ofrak_patch_maker/toolchain/gnu_ppc.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@
from typing import Optional

from ofrak_patch_maker.binary_parser.gnu import GNU_V10_ELF_Parser
from ofrak_patch_maker.toolchain.gnu import GNU_10_Toolchain
from ofrak_patch_maker.toolchain.gnu import GNU_10_Toolchain, Abstract_GNU_Toolchain
from ofrak_patch_maker.toolchain.model import ToolchainConfig
from ofrak_type import ArchInfo, InstructionSet, MemoryPermissions
from ofrak_type import ArchInfo, InstructionSet, MemoryPermissions, SubInstructionSet


class GNU_PPC_LINUX_10_Toolchain(GNU_10_Toolchain):
Expand Down Expand Up @@ -83,3 +83,82 @@ def ld_generate_placeholder_reloc_sections(self):
regions.append(got_region)
sections.append(self._ld_generate_got_section(got_name))
return regions, sections


class GNU_PPCVLE_4_Toolchain(Abstract_GNU_Toolchain):
binary_file_parsers = [GNU_V10_ELF_Parser()]

def __init__(
self,
processor: ArchInfo,
toolchain_config: ToolchainConfig,
logger: logging.Logger = logging.getLogger(__name__),
):
super().__init__(processor, toolchain_config, logger=logger)
logging.warning(f"PLOUF: {self._config}")
if self._config.hard_float:
self._compiler_flags.append("-mhard-float")
else:
self._compiler_flags.append("-msoft-float")
self._assembler_flags.append("-mvle")

@property
def segment_alignment(self) -> int:
return 4 # TODO: Check

@property
def name(self) -> str:
return "GNU_PPCVLE_4"

def _get_assembler_target(self, processor: ArchInfo) -> Optional[str]:
if processor.isa != InstructionSet.PPC or processor.sub_isa != SubInstructionSet.PPCVLE:
raise ValueError(
f"The GNU PPCVLE toolchain does not support ISAs that are not PPCVLE. "
f"(Got: {processor.isa.name})"
)
if self._config.assembler_target:
return self._config.assembler_target

# PPCVLE GNU 4 does not implement -march
return None

@staticmethod
def _ld_generate_rel_dyn_section(
memory_region_name: str,
) -> str:
rel_dyn_section_name = ".rela.dyn"
return (
f" {rel_dyn_section_name} : {{\n"
f" *.o({rel_dyn_section_name})\n"
f" }} > {memory_region_name}"
)

@staticmethod
def _ld_generate_got_plt_section(
memory_region_name: str,
) -> str:
got_plt_section_name = ".got2"
return (
f" {got_plt_section_name} : {{\n"
f" *.o({got_plt_section_name})\n"
f" }} > {memory_region_name}"
)

def _ld_generate_got_region(self, vm_address, length):
region_name = '".got_mem"'
perms_string = self._ld_perm2str(MemoryPermissions.R)
return (
f" {region_name} ({perms_string}) : ORIGIN = {hex(vm_address)}, "
f"LENGTH = {hex(length)}",
region_name,
)

def ld_generate_placeholder_reloc_sections(self):
regions, sections = super().ld_generate_placeholder_reloc_sections()
(
got_region,
got_name,
) = self._ld_generate_got_region(0xDEADBEEF + 0x30000, 0x1000)
regions.append(got_region)
sections.append(self._ld_generate_got_section(got_name))
return regions, sections
7 changes: 7 additions & 0 deletions ofrak_patch_maker/ofrak_patch_maker_test/example_2/bom1.vle.S
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
.global _main1
_main1:
add 1, 1, 2
add 1, 1, 2
add 1, 1, 2
add 1, 1, 2
add 1, 1, 2
7 changes: 7 additions & 0 deletions ofrak_patch_maker/ofrak_patch_maker_test/example_2/bom2.vle.S
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
.global _main2
_main2:
add 2, 2, 3
add 2, 2, 3
add 2, 2, 3
add 2, 2, 3
add 2, 2, 3
7 changes: 7 additions & 0 deletions ofrak_patch_maker/ofrak_patch_maker_test/example_2/bom3.vle.S
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
.global _main3
_main3:
add 3, 3, 4
add 3, 3, 4
add 3, 3, 4
add 3, 3, 4
add 3, 3, 4
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
.align 1
se_mflr 0
Loading

0 comments on commit b3c1af4

Please sign in to comment.