Last active
March 13, 2019 22:09
-
-
Save roblabla/595804337223f1ff843a29bbeb07896b to your computer and use it in GitHub Desktop.
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
/* ### | |
* IP: GHIDRA | |
* | |
* 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. | |
* See the License for the specific language governing permissions and | |
* limitations under the License. | |
*/ | |
package kernel70loader; | |
import java.io.DataInputStream; | |
import java.io.IOException; | |
import java.io.PrintWriter; | |
import java.io.StringWriter; | |
import java.lang.reflect.InvocationTargetException; | |
import java.lang.reflect.Method; | |
import java.util.*; | |
import java.util.stream.IntStream; | |
import java.util.stream.LongStream; | |
import org.apache.commons.collections4.MapUtils; | |
import org.python.google.common.io.ByteStreams; | |
import generic.continues.RethrowContinuesFactory; | |
import ghidra.app.script.ScriptMessage; | |
import ghidra.app.util.MemoryBlockUtil; | |
import ghidra.app.util.Option; | |
import ghidra.app.util.bin.BinaryReader; | |
import ghidra.app.util.bin.ByteProvider; | |
import ghidra.app.util.bin.format.FactoryBundledWithBinaryReader; | |
import ghidra.app.util.bin.format.elf.ElfDynamicType; | |
import ghidra.app.util.bin.format.elf.ElfHeader; | |
import ghidra.app.util.bin.format.elf.ElfRelocationTable; | |
import ghidra.app.util.bin.format.elf.ElfSectionHeader; | |
import ghidra.app.util.bin.format.elf.ElfStringTable; | |
import ghidra.app.util.bin.format.elf.ElfSymbol; | |
import ghidra.app.util.bin.format.elf.ElfSymbolTable; | |
import ghidra.app.util.bin.format.elf.relocation.AARCH64_ElfRelocationConstants; | |
import ghidra.app.util.bin.format.elf.relocation.ElfRelocationContext; | |
import ghidra.app.util.importer.MemoryConflictHandler; | |
import ghidra.app.util.importer.MessageLog; | |
import ghidra.app.util.opinion.AbstractProgramLoader; | |
import ghidra.app.util.opinion.LoadSpec; | |
import ghidra.app.util.opinion.LoaderTier; | |
import ghidra.app.util.opinion.QueryOpinionService; | |
import ghidra.app.util.opinion.QueryResult; | |
import ghidra.framework.model.DomainFolder; | |
import ghidra.framework.model.DomainObject; | |
import ghidra.framework.plugintool.PluginTool; | |
import ghidra.framework.store.LockException; | |
import ghidra.program.model.address.Address; | |
import ghidra.program.model.address.AddressOutOfBoundsException; | |
import ghidra.program.model.address.AddressOverflowException; | |
import ghidra.program.model.address.AddressSpace; | |
import ghidra.program.model.lang.CompilerSpec; | |
import ghidra.program.model.lang.Language; | |
import ghidra.program.model.lang.LanguageCompilerSpecPair; | |
import ghidra.program.model.listing.Program; | |
import ghidra.program.model.mem.MemoryAccessException; | |
import ghidra.util.Msg; | |
import ghidra.util.exception.CancelledException; | |
import ghidra.util.task.TaskMonitor; | |
/** | |
* TODO: Provide class-level documentation that describes what this loader does. | |
*/ | |
public class Kernel70LoaderLoader extends AbstractProgramLoader { | |
/** | |
* Prints the message to the console followed by a line feed. | |
* | |
* @param message the message to print | |
* @see #printf(String, Object...) | |
*/ | |
public void println(String message) { | |
String decoratedMessage = "Kernel70Loader > " + message; | |
// note: use a Message object to facilite script message log filtering | |
Msg.info(Kernel70LoaderLoader.class, new ScriptMessage(decoratedMessage)); | |
} | |
@Override | |
public String getName() { | |
return "NX Kernel"; | |
} | |
// Examine the bytes in 'provider' to determine if this loader can load it. If it | |
// can load it, return the appropriate load specifications | |
@Override | |
public Collection<LoadSpec> findSupportedLoadSpecs(ByteProvider provider) throws IOException { | |
byte[] header = { (byte)0xDF, 'O', (byte)0x03, (byte)0xD5, 'A', 'B', '8', (byte)0xD5 }; | |
List<LoadSpec> loadSpecs = new ArrayList<>(); | |
if (Arrays.equals(provider.readBytes(0, 8), header)) { | |
this.println("Looking for version"); | |
if (this.find_version(provider) != 0) { | |
List<QueryResult> results = QueryOpinionService.query(getName(), "0", null); | |
for (QueryResult result : results) { | |
loadSpecs.add(new LoadSpec(this, 0, result)); | |
} | |
if (loadSpecs.isEmpty()) { | |
loadSpecs.add(new LoadSpec(this, 0, true)); | |
} | |
return loadSpecs; | |
} | |
} | |
return null; | |
} | |
@Override | |
public LoaderTier getTier() { | |
return LoaderTier.SPECIALIZED_TARGET_LOADER; | |
} | |
@Override | |
public int getTierPriority() { | |
return 0; | |
} | |
@Override | |
protected List<Program> loadProgram(ByteProvider provider, String programName, DomainFolder programFolder, | |
LoadSpec loadSpec, List<Option> options, MessageLog log, Object consumer, TaskMonitor monitor) | |
throws IOException, CancelledException { | |
ArrayList<Program> programs = new ArrayList<Program>(); | |
LanguageCompilerSpecPair pair = loadSpec.getLanguageCompilerSpec(); | |
Language language = getLanguageService().getLanguage(pair.languageID); | |
CompilerSpec compilerSpec = language.getCompilerSpecByID(pair.compilerSpecID); | |
Address imageBaseAddr = language.getAddressFactory().getDefaultAddressSpace().getAddress( | |
loadSpec.getDesiredImageBase()); | |
Program program = createProgram(provider, programName, imageBaseAddr, getName(), | |
language, compilerSpec, consumer); | |
if (this.loadProgramInto(provider, loadSpec, options, log, program, monitor, MemoryConflictHandler.ALWAYS_OVERWRITE)) { | |
programs.add(program); | |
} | |
return programs; | |
} | |
@Override | |
protected boolean loadProgramInto(ByteProvider provider, LoadSpec loadSpec, List<Option> options, | |
MessageLog log, Program program, TaskMonitor monitor, MemoryConflictHandler memoryConflictHandler) | |
throws IOException, CancelledException { | |
MemoryBlockUtil mbu = new MemoryBlockUtil(program, memoryConflictHandler); | |
int id = program.startTransaction("Loading"); | |
boolean success = false; | |
try { | |
AddressSpace aspace = program.getAddressFactory().getDefaultAddressSpace(); | |
Address base = aspace.getAddress(0x80060000L); | |
program.setImageBase(base, true); | |
log.appendMsg("Doing stuff"); | |
processSections(provider, program, aspace, mbu, log, monitor); | |
success = true; | |
} catch (AddressOverflowException | IllegalStateException | LockException | AddressOutOfBoundsException | NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException | InvocationTargetException | MemoryAccessException e) { | |
log.appendException(e); | |
StringWriter s = new StringWriter(); | |
e.printStackTrace(new PrintWriter(s)); | |
println(s.toString()); | |
return false; | |
} finally { | |
mbu.dispose(); | |
program.endTransaction(id, success); | |
} | |
return true; | |
} | |
int find_version(ByteProvider provider) throws IOException { | |
byte[] disambiguator_5 = { (byte)0x62, (byte)0xBF, (byte)0x21, (byte)0x10 }; | |
//byte[] disambiguator_6 = { (byte)0xDF, 'O', (byte)0x03, (byte)0xD5, 'A', 'B', '8', (byte)0xD5 }; | |
byte[] disambiguator_7 = { (byte)0x21, (byte)0x00, (byte)0x40, (byte)0xF9 }; | |
byte[] disambiguator = provider.readBytes(0x814, 4); | |
if (Arrays.equals(disambiguator, disambiguator_5)) { | |
return 5; | |
} else if (Arrays.equals(disambiguator, disambiguator_7)) { | |
return 7; | |
} else { | |
return 0; | |
} | |
} | |
void processSections(ByteProvider provider, Program program, AddressSpace aspace, MemoryBlockUtil mbu, MessageLog log, TaskMonitor monitor) throws IOException, AddressOverflowException, AddressOutOfBoundsException, CancelledException, NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, MemoryAccessException { | |
monitor.setMessage("Process sections..."); | |
int version = this.find_version(provider); | |
// TODO: Get base addr | |
long base_addr = 0x80060000L; | |
long text_end; | |
if (version == 5) { | |
text_end = 0x800BD000L; | |
} else if (version == 6) { | |
text_end = 0x800BF000L; | |
} else if (version == 7) { | |
text_end = 0x800C0000L; | |
} else { | |
return; | |
} | |
long textoff = 0; | |
long textsize = text_end - base_addr; | |
long rodataoff = text_end - base_addr; | |
long rodatasize = 0x800D0000L - text_end; | |
long dataoff = 0x800D0000L - base_addr; | |
long dataend_off; | |
if (version == 5) { | |
dataend_off = 0x800D1A40L; | |
} else if (version == 6) { | |
dataend_off = 0x800D1B18L; | |
} else if (version == 7) { | |
dataend_off = 0x800D1B28L; | |
} else { | |
return; | |
} | |
long datasize = dataend_off - 0x800D0000L; // 0x800E4000 - 0x800D0000 | |
long flatsize = dataoff + datasize; | |
long dynamicoff_addr; | |
if (version == 5) { | |
dynamicoff_addr = 0x800D1378L; | |
} else if (version == 6) { | |
dynamicoff_addr = 0x800D1440L; | |
} else if (version == 7) { | |
dynamicoff_addr = 0x800D1440L; | |
} else { | |
return; | |
} | |
long dynamicoff = dynamicoff_addr - base_addr; | |
long bssoff = dataend_off - base_addr; | |
long bssend; | |
if (version == 5 || version == 6) { | |
bssend = 0x800E4000L - base_addr; | |
} else if (version == 7) { | |
bssend = 0x800E5000L - base_addr; | |
} else { | |
return; | |
} | |
datasize = bssoff - dataoff; | |
long bsssize = bssend - bssoff; | |
println(String.format(".text: %x or %x", textoff, base_addr + textoff)); | |
mbu.createInitializedBlock(".text", aspace.getAddress(base_addr + textoff), provider.getInputStream(textoff), textsize, "", null, true, false, true, monitor); | |
mbu.createInitializedBlock(".rodata", aspace.getAddress(base_addr + rodataoff), provider.getInputStream(rodataoff), rodatasize, "", null, true, false, false, monitor); | |
mbu.createInitializedBlock(".data", aspace.getAddress(base_addr + dataoff), provider.getInputStream(dataoff), datasize, "", null, true, true, false, monitor); | |
mbu.createUninitializedBlock(false, ".bss", aspace.getAddress(base_addr + bssoff), bsssize, "", null, true, true, false); | |
// Read dynamic | |
BinaryReader reader = new BinaryReader(provider, true); | |
HashMap<Integer, Object> dynamic = new HashMap<>(); | |
dynamic.put(ElfDynamicType.DT_NEEDED.value, new ArrayList<Long>()); | |
HashMap<Integer, ElfDynamicType> dyntypes = new HashMap<>(); | |
ElfDynamicType.addDefaultTypes(dyntypes); | |
MapUtils.debugPrint(System.out, "mymap", dyntypes); | |
long i; | |
for (i = 0; i < (flatsize - dynamicoff) / 0x10; i++) { | |
monitor.checkCanceled(); | |
int tag = (int)reader.readLong(dynamicoff + i * 0x10); | |
long val = reader.readLong(dynamicoff + i * 0x10 + 8); | |
String name = "UNKNOWN"; | |
if (dyntypes.containsKey(tag)) { | |
name = dyntypes.get(tag).name; | |
} | |
println(String.format("Got .dynamic entry tag %s (%x) val %x", name, tag, val)); | |
if (tag == ElfDynamicType.DT_NULL.value) { | |
break; | |
} | |
if (tag == ElfDynamicType.DT_NEEDED.value) { | |
ArrayList<Long> v = (ArrayList<Long>)dynamic.get(tag); | |
v.add(val); | |
} else { | |
dynamic.put(tag, val); | |
} | |
} | |
long dynamicend = dynamicoff_addr + i * 0x10; | |
long dynamicsize = dynamicend - dynamicoff_addr; | |
println(String.format("Adding .dynamic at %x", dynamicoff)); | |
// TODO: Create comment to point out start of dynamic | |
byte[] dynstr; | |
if (dynamic.containsKey(ElfDynamicType.DT_STRTAB.value) && dynamic.containsKey(ElfDynamicType.DT_STRSZ.value)) { | |
dynstr = provider.readBytes((long)dynamic.get(ElfDynamicType.DT_STRTAB.value), (long)dynamic.get(ElfDynamicType.DT_STRSZ.value)); | |
} else { | |
println("Warning: no dynstr"); | |
dynstr = new byte[0]; | |
} | |
// TODO: Add section for dynstr, init_array, fini_array, .rela.dyn, .rel.dyn, .rel.plt | |
// TODO: Get needed | |
// TODO: Load dynsym | |
FactoryBundledWithBinaryReader factreader = new FactoryBundledWithBinaryReader(RethrowContinuesFactory.INSTANCE, provider, true); | |
ElfStringTable strtable = ElfStringTable.createElfStringTable(factreader, null, null, | |
(Long)dynamic.get(ElfDynamicType.DT_STRTAB.value), | |
(Long)dynamic.get(ElfDynamicType.DT_STRTAB.value), | |
(Long)dynamic.get(ElfDynamicType.DT_STRSZ.value)); | |
// Get symbol table length through DT_HASH. DT_HASH.nchain is equal to the length of the | |
// symbol table | |
long hash_off = (long)dynamic.get(ElfDynamicType.DT_HASH.value); | |
long nchain = reader.readUnsignedInt(hash_off + 4); | |
Method m = ElfSymbolTable.class.getDeclaredMethod("createElfSymbolTable", FactoryBundledWithBinaryReader.class, | |
ElfHeader.class, ElfSectionHeader.class, long.class, long.class, | |
long.class, long.class, ElfStringTable.class, boolean.class); | |
m.setAccessible(true); | |
ElfSymbolTable symtable = (ElfSymbolTable)m.invoke(null, factreader, new ElfHeader(), null, | |
dynamic.get(ElfDynamicType.DT_SYMTAB.value), | |
dynamic.get(ElfDynamicType.DT_SYMTAB.value), | |
nchain * (long)dynamic.get(ElfDynamicType.DT_SYMENT.value), | |
dynamic.get(ElfDynamicType.DT_SYMENT.value), | |
strtable, true); | |
// Relocations | |
ArrayList<Relocation> relocs = new ArrayList<>(); | |
if (dynamic.containsKey(ElfDynamicType.DT_REL.value)) { | |
processRelocations(program, reader, relocs, symtable, | |
(long)dynamic.get(ElfDynamicType.DT_REL.value), | |
(long)dynamic.get(ElfDynamicType.DT_RELSZ.value)); | |
} | |
if (dynamic.containsKey(ElfDynamicType.DT_RELA.value)) { | |
processRelocations(program, reader, relocs, symtable, | |
(long)dynamic.get(ElfDynamicType.DT_RELA.value), | |
(long)dynamic.get(ElfDynamicType.DT_RELASZ.value)); | |
} | |
if (dynamic.containsKey(ElfDynamicType.DT_JMPREL.value)) { | |
println("Got DT_JMPREL. This probably needs to be handled!"); | |
} | |
// TODO: Handle .got | |
// TODO: Handle imports | |
// Relocations again | |
for (Relocation reloc : relocs) { | |
Address target = aspace.getAddress(reloc.offset + base_addr); | |
if (reloc.r_type == AARCH64_ElfRelocationConstants.R_AARCH64_GLOB_DAT || | |
reloc.r_type == AARCH64_ElfRelocationConstants.R_AARCH64_JUMP_SLOT || | |
reloc.r_type == AARCH64_ElfRelocationConstants.R_AARCH64_ABS32) { | |
if (reloc.sym == null) { | |
println(String.format("Error: Relocation at %x failed", target)); | |
} else { | |
program.getMemory().setLong(target, reloc.sym.getValue() + base_addr + reloc.addend); | |
} | |
} else if (reloc.r_type == AARCH64_ElfRelocationConstants.R_AARCH64_RELATIVE) { | |
long target_val = program.getMemory().getLong(target); | |
program.getMemory().setLong(target, target_val + base_addr); | |
} else { | |
println(String.format("TODO: r_type %x", reloc.r_type)); | |
} | |
} | |
// TODO: Find functions. | |
} | |
class Relocation { | |
public Relocation(long offset, long r_type, ElfSymbol sym, long addend) { | |
this.offset = offset; | |
this.r_type = r_type; | |
this.sym = sym; | |
this.addend = addend; | |
} | |
long offset; | |
long r_type; | |
ElfSymbol sym; | |
long addend; | |
} | |
private Set<Long> processRelocations(Program program, BinaryReader provider, List<Relocation> relocs, ElfSymbolTable symtab, long rel, long relsz) throws IOException { | |
Set<Long> locations = new HashSet<Long>(); | |
for (long i = 0; i < relsz / 0x18; i++) { | |
long offset = provider.readLong(rel + i * 0x18); | |
long info = provider.readLong(rel + i * 0x18 + 8); | |
long addend = provider.readLong(rel + i * 0x18 + 0x10); | |
long r_type = info & 0xffffffff; | |
long r_sym = info >> 32; | |
ElfSymbol sym; | |
if (r_sym != 0) { | |
sym = symtab.getSymbolAt(r_sym); | |
} else { | |
sym = null; | |
} | |
if (r_type != AARCH64_ElfRelocationConstants.R_AARCH64_TLSDESC) { | |
locations.add(offset); | |
} | |
relocs.add(new Relocation(offset, r_type, sym, addend)); | |
} | |
return locations; | |
} | |
} |
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
<opinions> | |
<!-- The primary and secondary constraint values must be specified as a decimal string --> | |
<constraint loader="NX Kernel" compilerSpecID="default"> | |
<constraint primary="0" processor="AARCH64" endian="little" size="64" variant="v8A" /> | |
</constraint> | |
</opinions> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment