Skip to content

Commit

Permalink
Add Terse Executable binary loader
Browse files Browse the repository at this point in the history
  • Loading branch information
al3xtjames committed Jul 24, 2019
1 parent c81a34e commit a458659
Show file tree
Hide file tree
Showing 9 changed files with 569 additions and 1 deletion.
4 changes: 3 additions & 1 deletion README.md
Expand Up @@ -20,9 +20,11 @@ This was accepted as a [coreboot project][2] for GSoC 2019.
- Implements a FS loader for UEFI firmware volumes and nested firmware
filesystem (FFS) file/FFS section parsing

### Terse Executable (TE) loader
- Implements a binary loader for TE binaries (frequently used in UEFI PI)

## Planned functionality / TODO
### Firmware image loader
- Support for parsing extended headers in UEFI firmware volumes
- Support for parsing FFSv3 files

### UEFI loader
Expand Down
1 change: 1 addition & 0 deletions src/main/java/firmware/uefi_fv/FFSCompressedSection.java
Expand Up @@ -130,6 +130,7 @@ public byte getCompressionType() {
*
* @return an InputStream for the contents of the current compressed section
*/
@Override
public InputStream getData() {
return null;
}
Expand Down
1 change: 1 addition & 0 deletions src/main/java/firmware/uefi_fv/FFSGUIDDefinedSection.java
Expand Up @@ -157,6 +157,7 @@ private static void parseNestedSections(BinaryReader reader, long length,
*
* @return an InputStream for the contents of the current GUID-defined section
*/
@Override
public InputStream getData() {
return null;
}
Expand Down
1 change: 1 addition & 0 deletions src/main/java/firmware/uefi_fv/FFSGenericSection.java
Expand Up @@ -61,6 +61,7 @@ public FFSGenericSection(BinaryReader reader, UEFIFirmwareVolumeFileSystem fs,
*
* @return an InputStream for the contents of the current FFS section
*/
@Override
public InputStream getData() {
return new ByteArrayInputStream(data);
}
Expand Down
79 changes: 79 additions & 0 deletions src/main/java/firmware/uefi_te/EFIImageDataDirectory.java
@@ -0,0 +1,79 @@
/* ###
* 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 firmware.uefi_te;

import ghidra.app.util.bin.BinaryReader;
import ghidra.app.util.bin.StructConverter;
import ghidra.program.model.data.DataType;
import ghidra.program.model.data.Structure;
import ghidra.program.model.data.StructureDataType;

import java.io.IOException;

/**
* Parser for PE32/TE image data directories, which have the following fields:
*
* PE32/TE Image Data Directory
* +------+------+-----------------+
* | Type | Size | Description |
* +------+------+-----------------+
* | u32 | 4 | Virtual Address |
* | u32 | 4 | Size |
* +------+------+-----------------+
*
* Ghidra has existing classes to parse this structure, but they are dependent on PE32 NT headers.
*/
public class EFIImageDataDirectory implements StructConverter {
private int virtualAddress;
private int size;

/**
* Constructs an EFIImageDataDirectory from a specified BinaryReader.
*
* @param reader the specified BinaryReader
*/
public EFIImageDataDirectory(BinaryReader reader) throws IOException {
virtualAddress = reader.readNextInt();
size = reader.readNextInt();
}

/**
* Returns the virtual address of the current data directory.
*
* @return the virtual address of the current data directory
*/
public int getVirtualAddress() {
return virtualAddress;
}

/**
* Returns the size of the current data directory.
*
* @return the size of the current data directory
*/
public int getSize() {
return size;
}

@Override
public DataType toDataType() {
Structure structure = new StructureDataType("efi_image_data_dir_t", 0);
structure.add(DWORD, 4, "virtual_address", null);
structure.add(DWORD, 4, "size", null);
return structure;
}
}
150 changes: 150 additions & 0 deletions src/main/java/firmware/uefi_te/EFIImageSectionHeader.java
@@ -0,0 +1,150 @@
/* ###
* 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 firmware.uefi_te;

import ghidra.app.util.bin.BinaryReader;
import ghidra.app.util.bin.StructConverter;
import ghidra.program.model.data.ArrayDataType;
import ghidra.program.model.data.DataType;
import ghidra.program.model.data.Structure;
import ghidra.program.model.data.StructureDataType;

import java.io.IOException;

/**
* Parser for PE32/TE section headers, which have the following fields:
*
* PE32/TE Image Section Header
* +---------+------+------------------------+
* | Type | Size | Description |
* +---------+------+------------------------+
* | char[8] | 8 | Name |
* | u32 | 4 | Virtual Size |
* | u32 | 4 | Virtual Address |
* | u32 | 4 | Raw Data Size |
* | u32 | 4 | Raw Data Pointer |
* | u32 | 4 | Relocations Pointer |
* | u32 | 4 | Line Numbers Pointer |
* | u16 | 2 | Number of Relocations |
* | u16 | 2 | Number of Line Numbers |
* | u32 | 4 | Characteristics |
* +---------+------+------------------------+
*
* Ghidra has existing classes to parse this structure, but they are dependent on PE32 NT headers.
*/
public class EFIImageSectionHeader implements StructConverter {
// Original header fields
private String name;
private int virtualSize;
private int virtualAddress;
private int rawDataSize;
private int rawDataPointer;
private int relocationsPointer;
private int lineNumbersPointer;
private short numRelocations;
private short numLineNumbers;
private int characteristics;

/**
* Constructs an EFIImageSectionHeader from a specified BinaryReader.
*
* @param reader the specified BinaryReader
*/
public EFIImageSectionHeader(BinaryReader reader) throws IOException {
name = reader.readNextAsciiString(8);
virtualSize = reader.readNextInt();
virtualAddress = reader.readNextInt();
rawDataSize = reader.readNextInt();
rawDataPointer = reader.readNextInt();
relocationsPointer = reader.readNextInt();
lineNumbersPointer = reader.readNextInt();
numRelocations = reader.readNextShort();
numLineNumbers = reader.readNextShort();
characteristics = reader.readNextInt();
}

/**
* Returns the name of the current section.
*
* @return the name of the current section
*/
public String getName() {
return name;
}

/**
* Returns the virtual address for the current section.
*
* @return the virtual address for the current section
*/
public int getVirtualAddress() {
return virtualAddress;
}

/**
* Returns the virtual size for the current section.
*
* @return the virtual size for the current section
*/
public int getVirtualSize() {
return virtualSize;
}

/**
* Returns whether the current section is executable.
*
* @return whether the current section is executable
*/
public boolean isExecutable() {
return (characteristics & TerseExecutableConstants.SectionCharacteristics.MEM_EXECUTE)
!= 0;
}

/**
* Returns whether the current section is readable.
*
* @return whether the current section is readable
*/
public boolean isReadable() {
return (characteristics & TerseExecutableConstants.SectionCharacteristics.MEM_READ) != 0;
}

/**
* Returns whether the current section is writable.
*
* @return whether the current section is writable
*/
public boolean isWritable() {
return (characteristics & TerseExecutableConstants.SectionCharacteristics.MEM_WRITE) != 0;
}

@Override
public DataType toDataType() {
Structure structure = new StructureDataType("efi_image_section_hdr_t", 0);
structure.add(new ArrayDataType(ASCII, 8, 1), "name", null);
structure.add(DWORD, 4, "virtual_size", null);
structure.add(DWORD, 4, "virtual_addr", null);
structure.add(DWORD, 4, "raw_data_size", null);
structure.add(DWORD, 4, "raw_data_ptr", null);
structure.add(DWORD, 4, "relocations_ptr", null);
structure.add(DWORD, 4, "line_numbers_ptr", null);
structure.add(WORD, 2, "num_relocations", null);
structure.add(WORD, 2, "num_line_numbers", null);
structure.add(DWORD, 4, "characteristics", null);
return structure;
}
}
130 changes: 130 additions & 0 deletions src/main/java/firmware/uefi_te/TELoader.java
@@ -0,0 +1,130 @@
/* ###
* 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 firmware.uefi_te;

import ghidra.app.util.Option;
import ghidra.app.util.bin.BinaryReader;
import ghidra.app.util.bin.ByteProvider;
import ghidra.app.util.bin.format.pe.MachineConstants;
import ghidra.app.util.importer.MemoryConflictHandler;
import ghidra.app.util.importer.MessageLog;
import ghidra.app.util.opinion.AbstractLibrarySupportLoader;
import ghidra.app.util.opinion.LoadSpec;
import ghidra.program.flatapi.FlatProgramAPI;
import ghidra.program.model.address.Address;
import ghidra.program.model.lang.LanguageCompilerSpecPair;
import ghidra.program.model.listing.Program;
import ghidra.program.model.mem.MemoryBlock;
import ghidra.util.Msg;
import ghidra.util.task.TaskMonitor;

import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

/**
* Loader for Terse Executable (TE) binaries. Terse Executables are simplified PE/COFF executables,
* used in the UEFI PI stage.
*/
public class TELoader extends AbstractLibrarySupportLoader {
@Override
public Collection<LoadSpec> findSupportedLoadSpecs(ByteProvider provider) {
ArrayList<LoadSpec> loadSpecs = new ArrayList<>();
BinaryReader reader = new BinaryReader(provider, true);
TerseExecutableHeader header = null;
try {
header = new TerseExecutableHeader(reader);
} catch (IOException e) {
return loadSpecs;
}

switch (header.getMachineType()) {
case MachineConstants.IMAGE_FILE_MACHINE_I386:
loadSpecs.add(new LoadSpec(this, header.getImageBase(),
new LanguageCompilerSpecPair("x86:LE:32:default", "windows"), true));
break;
case MachineConstants.IMAGE_FILE_MACHINE_AMD64:
loadSpecs.add(new LoadSpec(this, header.getImageBase(),
new LanguageCompilerSpecPair("x86:LE:64:default", "windows"), true));
break;
}

return loadSpecs;
}

@Override
protected void load(ByteProvider provider, LoadSpec loadSpec, List<Option> options,
Program program, MemoryConflictHandler handler, TaskMonitor monitor,
MessageLog log) throws IOException {
BinaryReader reader = new BinaryReader(provider, true);
TerseExecutableHeader teHeader = new TerseExecutableHeader(reader);
EFIImageSectionHeader[] sectionHeaders = teHeader.getSections();
FlatProgramAPI api = new FlatProgramAPI(program, monitor);
InputStream inputStream = provider.getInputStream(0);

try {
// Create a segment for the TE header and section headers.
api.createMemoryBlock("Headers", api.toAddr(
teHeader.getImageBase() + teHeader.getHeaderOffset()), inputStream,
TerseExecutableConstants.TE_HEADER_SIZE + teHeader.getNumSections() *
TerseExecutableConstants.SECTION_HEADER_SIZE, false);

// Mark the TE header and section headers as data.
api.createData(api.toAddr(teHeader.getImageBase() + teHeader.getHeaderOffset()),
teHeader.toDataType());
long sectionHeaderOffset = teHeader.getImageBase() + teHeader.getHeaderOffset() +
TerseExecutableConstants.TE_HEADER_SIZE;
for (EFIImageSectionHeader sectionHeader : sectionHeaders) {
api.createData(api.toAddr(sectionHeaderOffset), sectionHeader.toDataType());
sectionHeaderOffset += TerseExecutableConstants.SECTION_HEADER_SIZE;
}

// Create a segment for each section.
for (EFIImageSectionHeader sectionHeader : sectionHeaders) {
inputStream = provider.getInputStream(sectionHeader.getVirtualAddress() -
teHeader.getHeaderOffset());
long startAddress = teHeader.getImageBase() + sectionHeader.getVirtualAddress();
MemoryBlock section = api.createMemoryBlock(sectionHeader.getName(),
api.toAddr(startAddress), inputStream, sectionHeader.getVirtualSize(),
false);

// Set the appropriate permissions for this segment.
section.setRead(sectionHeader.isReadable());
section.setWrite(sectionHeader.isWritable());
section.setExecute(sectionHeader.isExecutable());

Msg.debug(this, String.format("Added %s section: 0x%X-0x%X",
sectionHeader.getName(), startAddress, startAddress +
sectionHeader.getVirtualSize()));
}

// Define the entry point function.
Address entryPoint = api.toAddr(teHeader.getImageBase() + teHeader.getEntryPointAddress());
api.addEntryPoint(entryPoint);
api.createFunction(entryPoint, "_ModuleEntryPoint");
} catch (Exception e) {
Msg.showError(this, null, getName() + " Loader", e.getMessage(), e);
}
}

@Override
public String getName() {
return "Terse Executable (TE)";
}
}

0 comments on commit a458659

Please sign in to comment.