Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 35 additions & 37 deletions src/main/java/ghidraps4loader/GhidraPS4Loader.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
Expand All @@ -29,10 +29,8 @@

import ghidra.framework.Application;
import generic.util.Path;
import ghidra.app.plugin.assembler.sleigh.util.GhidraDBTransaction;
import ghidra.app.util.Option;
import ghidra.app.util.bin.ByteProvider;
import ghidra.app.util.importer.MemoryConflictHandler;
import ghidra.app.util.importer.MessageLog;
import ghidra.app.util.opinion.LoadSpec;
import ghidra.app.util.opinion.BinaryLoader;
Expand All @@ -50,28 +48,29 @@
import ghidra.util.task.TaskMonitor;
import ghidra.app.util.bin.format.elf.ElfHeader;
import ghidra.app.util.bin.format.elf.ElfException;
import ghidra.util.Msg;

public class GhidraPS4Loader extends BinaryLoader {
private String getDatabasePath() throws IOException {
String databasePath = Application.getModuleDataFile("ps4database.xml").toString();
return databasePath;
}

private Document parsePS4Database() throws ParserConfigurationException, IOException, SAXException {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
//factory.setValidating(true);
factory.setIgnoringElementContentWhitespace(true);

DocumentBuilder builder = factory.newDocumentBuilder();
File file = new File(getDatabasePath());
Document doc = builder.parse(file);

return doc;
}

private String getNameForNID(Document database, String nid) {
String result = "__import_" + nid;

NodeList nidlist = database.getElementsByTagName("DynlibDatabase").item(0).getChildNodes();
for(int j = 0; j < nidlist.getLength(); j++) {
Node node = nidlist.item(j);
Expand All @@ -86,10 +85,10 @@ private String getNameForNID(Document database, String nid) {
}
}
}

return result;
}

@Override
public String getName() {
return "PlayStation 4 ELF";
Expand All @@ -98,7 +97,7 @@ public String getName() {
@Override
public Collection<LoadSpec> findSupportedLoadSpecs(ByteProvider provider) throws IOException {
List<LoadSpec> loadSpecs = new ArrayList<>();

ElfHeader elfHeader;
try {
elfHeader = PS4ElfParser.getElfHeader(provider);
Expand All @@ -111,15 +110,15 @@ public Collection<LoadSpec> findSupportedLoadSpecs(ByteProvider provider) throws
if(!exists) {
return loadSpecs;
}

int type = elfHeader.e_type();
int machine = elfHeader.e_machine();

// TODO: support all the different types
//if(type != PS4_ELF_TYPE || machine != PS4_MACHINE_TYPE) {
// return loadSpecs;
//}

loadSpecs.add(new LoadSpec(this, 0x400000, new LanguageCompilerSpecPair("x86:LE:64:default", "gcc"), true));

return loadSpecs;
Expand All @@ -136,51 +135,51 @@ protected List<Program> loadProgram(ByteProvider provider, String programName,
Address baseAddress = importerLanguage.getAddressFactory().getDefaultAddressSpace().getAddress(0);
List<Program> results = new ArrayList<Program>();
boolean success = false;

Program program = createProgram(provider, programName, baseAddress, getName(), importerLanguage, importerCompilerSpec, consumer);

try {
success = this.loadInto(provider, loadSpec, options, log, program, monitor, MemoryConflictHandler.ALWAYS_OVERWRITE);
success = this.loadInto(provider, loadSpec, options, log, program, monitor);
} finally {
if(!success) {
program.release(consumer);
}
}

if (success) {
// Start a transaction on the program database
GhidraDBTransaction trans = new GhidraDBTransaction(program, "PlayStation 4 Loader");
int transactionID = program.startTransaction("PlayStation 4 Loader");

// Function manager
FunctionManager funcManager = program.getFunctionManager();

// ELF Header
ElfHeader elfHeader;
try {
elfHeader = PS4ElfParser.getElfHeader(provider);
} catch (ElfException e) {
throw new IOException("Failed to parse ELF header!");
}

// TODO: fix this, make it dynamic
long endOfHeader = 0x4000;

// Load all the imports from the XML file
Document ps4database;
try {
ps4database = parsePS4Database();
} catch (Exception e) {
throw new IOException("Failed to load 'ps4database.xml'!");
}

// Parse all the imports
Map<Long, String> imports = PS4ElfParser.getSonyElfImports(provider, elfHeader);
if(imports.size() > 0) {
// Label all the imports
for(Map.Entry<Long, String> importEntry : imports.entrySet()) {
Long address = endOfHeader + importEntry.getKey();
String nid = importEntry.getValue();

Address addr = importerLanguage.getAddressFactory().getDefaultAddressSpace().getAddress(address);

// Label the import
Expand All @@ -190,28 +189,27 @@ protected List<Program> loadProgram(ByteProvider provider, String programName,
funcManager.createFunction(name, addr, addrSet, SourceType.IMPORTED);
} catch (Exception ex) {
ex.printStackTrace();
System.out.println("error: could not created imported function '" + name + "'!");
Msg.error(this, "error: could not created imported function '" + name + "'!");
}
}
}

// the entry point
long entryAddress = endOfHeader + elfHeader.e_entry();
Address entryAddr = importerLanguage.getAddressFactory().getDefaultAddressSpace().getAddress(entryAddress);
AddressSet addrSet = new AddressSet(entryAddr);
try {
funcManager.createFunction("entrypoint", entryAddr, addrSet, SourceType.IMPORTED);
funcManager.createFunction("entrypoint", entryAddr, addrSet, SourceType.IMPORTED);
} catch (Exception ex) {
System.out.println("error: could not set up entrypoint function!");
Msg.error(this, "error: could not set up entrypoint function!");
}

trans.commit();
trans.close();


program.endTransaction(transactionID, true);

// Add the program to the results
results.add(program);
}

return results;
}

Expand All @@ -227,11 +225,11 @@ public List<Option> getDefaultOptions(ByteProvider provider, LoadSpec loadSpec,
}

@Override
public String validateOptions(ByteProvider provider, LoadSpec loadSpec, List<Option> options) {
public String validateOptions(ByteProvider provider, LoadSpec loadSpec, List<Option> options, Program program) {

// TODO: If this loader has custom options, validate them here. Not all options require
// validation.

return super.validateOptions(provider, loadSpec, options);
return super.validateOptions(provider, loadSpec, options, program);
}
}
75 changes: 35 additions & 40 deletions src/main/java/ghidraps4loader/PS4ElfParser.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,14 @@
import java.util.Map;
import java.util.TreeMap;

import generic.continues.RethrowContinuesFactory;
import ghidra.app.util.bin.BinaryReader;
import ghidra.app.util.bin.ByteProvider;
import ghidra.app.util.bin.format.elf.*;
import ghidra.program.model.address.Address;
import ghidra.app.util.bin.format.FactoryBundledWithBinaryReader;
import ghidra.app.util.bin.format.elf.ElfDynamic;
import ghidra.app.util.bin.format.elf.ElfDynamicTable;
import ghidra.app.util.bin.format.elf.ElfException;
import ghidra.util.Msg;

public class PS4ElfParser {
// ELF Types
Expand All @@ -22,7 +21,7 @@ public class PS4ElfParser {
public static final long ET_SCE_STUBLIB = 0xFE0C;
public static final long ET_SCE_DYNEXEC = 0xFE10;
public static final long ET_SCE_DYNAMIC = 0xFE18;

// Program Segment Type
public static final long PT_SCE_RELA = 0x60000000;
public static final long PT_SCE_DYNLIBDATA = 0x61000000;
Expand All @@ -31,7 +30,7 @@ public class PS4ElfParser {
public static final long PT_SCE_RELRO = 0x61000010;
public static final long PT_SCE_COMMENT = 0X6FFFFF00;
public static final long PT_SCE_LIBVERSION = 0X6FFFFF01;

// Dynamic Section Types
public static final long DT_SCE_IDTABENTSZ = 0x61000005;
public static final long DT_SCE_FINGERPRINT = 0x61000007;
Expand Down Expand Up @@ -62,20 +61,20 @@ public class PS4ElfParser {
public static final long DT_SCE_HASHSZ = 0x6100003D;
public static final long DT_SCE_SYMTABSZ = 0x6100003F;
public static final long DT_SCE_HIOS = 0X6FFFF000;

public static class Elf64_Rela {
public long r_offset; /* Location at which to apply the action */
public long r_info; /* index and type of relocation */
public long r_addend; /* Constant addend used to compute value */
public static final int SIZE = 24;

public Elf64_Rela(BinaryReader br) throws IOException {
r_offset = br.readNextLong();
r_info = br.readNextLong();
r_addend = br.readNextLong();
}
}

public static class Elf64_Sym {
public int st_name; /* Symbol name, index in string tbl */
public byte st_info; /* Type and binding attributes */
Expand All @@ -84,7 +83,7 @@ public static class Elf64_Sym {
public long st_value; /* Value of the symbol */
public long st_size; /* Associated symbol size */
public static final int SIZE = 24;

public Elf64_Sym(BinaryReader br) throws IOException {
st_name = br.readNextInt();
st_info = br.readNextByte();
Expand All @@ -94,67 +93,63 @@ public Elf64_Sym(BinaryReader br) throws IOException {
st_size = br.readNextLong();
}
}

public static ElfHeader getElfHeader(ByteProvider provider) throws ElfException, IOException {
ElfHeader elfHeader = ElfHeader.createElfHeader(RethrowContinuesFactory.INSTANCE, provider);
ElfHeader elfHeader = new ElfHeader(provider, msg -> Msg.error(PS4ElfParser.class, msg));
elfHeader.parse();
return elfHeader;
}

public static Map<Long, String> getSonyElfImports(ByteProvider provider, ElfHeader elfHeader) throws IOException {
// Parse the specific Sony ELF region and build the map
BinaryReader br = new BinaryReader(provider, true);
FactoryBundledWithBinaryReader fbrdr = new FactoryBundledWithBinaryReader(RethrowContinuesFactory.INSTANCE, provider, true);
Map<Long, String> results = new TreeMap<Long, String>();
long dynlibdataAddr = 0;

ElfDynamicTable dynTable = null;
for(ElfProgramHeader prog : elfHeader.getProgramHeaders()) {
if(prog.getTypeAsString().equals("PT_DYNAMIC")) {
dynTable = ElfDynamicTable.createDynamicTable(fbrdr, elfHeader, prog.getOffset(), prog.getVirtualAddress());
} else if(prog.getType() == PT_SCE_DYNLIBDATA) {
dynlibdataAddr = prog.getOffset();
}

ElfDynamicTable dynTable = elfHeader.getDynamicTable();
for(ElfProgramHeader prog : elfHeader.getProgramHeaders((int)PT_SCE_DYNLIBDATA)) {
dynlibdataAddr = prog.getOffset();
}

long symAddr = dynlibdataAddr;
long relocAddr = dynlibdataAddr;
long strtableAddr = dynlibdataAddr;
long strtableSize = 0;
long number = 0;
for(ElfDynamic dyn : dynTable.getDynamics()) {
long tag = dyn.getTag();
if(tag == DT_SCE_JMPREL) {
relocAddr += dyn.getValue();
} else if(tag == DT_SCE_SYMTAB) {
symAddr += dyn.getValue();
} else if(tag == DT_SCE_STRTAB) {
strtableAddr += dyn.getValue();
} else if(tag == DT_SCE_STRSZ) {
strtableSize += dyn.getValue();
} else if(tag == DT_SCE_PLTRELSZ) {
number = dyn.getValue() / 24;
if (null != dynTable)
for(ElfDynamic dyn : dynTable.getDynamics()) {
long tag = dyn.getTag();
if(tag == DT_SCE_JMPREL) {
relocAddr += dyn.getValue();
} else if(tag == DT_SCE_SYMTAB) {
symAddr += dyn.getValue();
} else if(tag == DT_SCE_STRTAB) {
strtableAddr += dyn.getValue();
} else if(tag == DT_SCE_STRSZ) {
strtableSize += dyn.getValue();
} else if(tag == DT_SCE_PLTRELSZ) {
number = dyn.getValue() / 24;
}
}
}


// Parse through the relocation addresses and associate the symbol with each relocation
for(int i = 0; i < number; i++) {
br.setPointerIndex(relocAddr);
Elf64_Rela rela = new Elf64_Rela(br);

// find symbol for relocation
br.setPointerIndex(symAddr + (Elf64_Sym.SIZE * (rela.r_info >> 32)));
Elf64_Sym sym = new Elf64_Sym(br);

br.setPointerIndex(strtableAddr + sym.st_name);
String nid = br.readNextAsciiString().split("#")[0];
//System.out.println(nid);

results.put(rela.r_offset, nid);

relocAddr += Elf64_Rela.SIZE;
}

return results;
}
}