GP-4462: Marking up the Mach-O rebase opcode table

This commit is contained in:
Ryan Kurtz
2024-03-26 11:37:20 -04:00
parent f058bff5a4
commit eb39e13465
6 changed files with 384 additions and 55 deletions
@@ -20,8 +20,7 @@ import java.io.IOException;
import ghidra.app.util.bin.BinaryReader;
import ghidra.app.util.bin.format.macho.MachConstants;
import ghidra.app.util.bin.format.macho.MachHeader;
import ghidra.app.util.bin.format.macho.commands.dyld.BindOpcode;
import ghidra.app.util.bin.format.macho.commands.dyld.BindingTable;
import ghidra.app.util.bin.format.macho.commands.dyld.*;
import ghidra.app.util.importer.MessageLog;
import ghidra.program.flatapi.FlatProgramAPI;
import ghidra.program.model.address.Address;
@@ -47,6 +46,7 @@ public class DyldInfoCommand extends LoadCommand {
private int exportOff;
private int exportSize;
private RebaseTable rebaseTable;
private BindingTable bindingTable;
private BindingTable weakBindingTable;
private BindingTable lazyBindingTable;
@@ -77,7 +77,13 @@ public class DyldInfoCommand extends LoadCommand {
exportOff = loadCommandReader.readNextInt();
exportSize = loadCommandReader.readNextInt();
// TODO: rebase
if (rebaseOff > 0 && rebaseSize > 0) {
dataReader.setPointerIndex(header.getStartIndex() + rebaseOff);
rebaseTable = new RebaseTable(dataReader, header, rebaseSize);
}
else {
rebaseTable = new RebaseTable();
}
if (bindOff > 0 && bindSize > 0) {
dataReader.setPointerIndex(header.getStartIndex() + bindOff);
@@ -182,6 +188,13 @@ public class DyldInfoCommand extends LoadCommand {
return exportSize;
}
/**
* {@return The rebase table}
*/
public RebaseTable getRebaseTable() {
return rebaseTable;
}
/**
* {@return The binding table}
*/
@@ -246,22 +259,27 @@ public class DyldInfoCommand extends LoadCommand {
private void markupRebaseInfo(Program program, MachHeader header, String source,
TaskMonitor monitor, MessageLog log) {
Address rebaseAddr = fileOffsetToAddress(program, header, rebaseOff, rebaseSize);
markupPlateComment(program, fileOffsetToAddress(program, header, rebaseOff, rebaseSize),
source, "rebase");
markupOpcodeTable(program, rebaseAddr, rebaseTable, RebaseOpcode.toDataType(), source,
"rebase", log);
}
private void markupBindings(Program program, MachHeader header, String source,
TaskMonitor monitor, MessageLog log) {
Address bindAddr = fileOffsetToAddress(program, header, bindOff, bindSize);
markupPlateComment(program, bindAddr, source, "bind");
markupBindingTable(program, bindAddr, bindingTable, source, "bind", log);
markupOpcodeTable(program, bindAddr, bindingTable, BindOpcode.toDataType(), source, "bind",
log);
}
private void markupWeakBindings(Program program, MachHeader header, String source,
TaskMonitor monitor, MessageLog log) {
Address addr = fileOffsetToAddress(program, header, weakBindOff, weakBindSize);
markupPlateComment(program, addr, source, "weak bind");
markupBindingTable(program, addr, weakBindingTable, source, "weak bind", log);
markupOpcodeTable(program, addr, weakBindingTable, BindOpcode.toDataType(), source,
"weak bind", log);
}
@@ -269,18 +287,18 @@ public class DyldInfoCommand extends LoadCommand {
TaskMonitor monitor, MessageLog log) {
Address addr = fileOffsetToAddress(program, header, lazyBindOff, lazyBindSize);
markupPlateComment(program, addr, source, "lazy bind");
markupBindingTable(program, addr, lazyBindingTable, source, "lazy bind", log);
markupOpcodeTable(program, addr, lazyBindingTable, BindOpcode.toDataType(), source,
"lazy bind", log);
}
private void markupBindingTable(Program program, Address addr, BindingTable table,
String source, String additionalDescription, MessageLog log) {
private void markupOpcodeTable(Program program, Address addr, OpcodeTable table,
DataType opcodeDataType, String source, String additionalDescription, MessageLog log) {
if (addr == null) {
return;
}
try {
DataType bindOpcodeDataType = BindOpcode.toDataType();
for (long offset : table.getOpcodeOffsets()) {
DataUtilities.createData(program, addr.add(offset), bindOpcodeDataType, -1,
DataUtilities.createData(program, addr.add(offset), opcodeDataType, -1,
DataUtilities.ClearDataMode.CHECK_FOR_SPACE);
}
for (long offset : table.getUlebOffsets()) {
@@ -28,29 +28,18 @@ import ghidra.program.model.data.LEB128;
/**
* A Mach-O binding table
*
* @see <a href="https://github.com/apple-oss-distributions/dyld/blob/main/common/MachOLayout.cpp">common/MachOLayout.cpp</a>
* @see <a href="https://github.com/apple-oss-distributions/dyld/blob/main/common/MachOAnalyzer.cpp">common/MachOAnalyzer.cpp</a>
*/
public class BindingTable {
public class BindingTable extends OpcodeTable {
private List<Binding> bindings;
private List<Binding> bindings = new ArrayList<>();
private List<Binding> threadedBindings;
private List<Long> opcodeOffsets;
private List<Long> ulebOffsets;
private List<Long> slebOffsets;
private List<Long> stringOffsets;
/**
* Creates an empty {@link BindingTable}
*/
public BindingTable() {
bindings = new ArrayList<>();
threadedBindings = null;
opcodeOffsets = new ArrayList<>();
ulebOffsets = new ArrayList<>();
slebOffsets = new ArrayList<>();
stringOffsets = new ArrayList<>();
super();
}
/**
@@ -206,34 +195,6 @@ public class BindingTable {
return threadedBindings;
}
/**
* {@return opcode offsets from the start of the bind data}
*/
public List<Long> getOpcodeOffsets() {
return opcodeOffsets;
}
/**
* {@return ULEB128 offsets from the start of the bind data}
*/
public List<Long> getUlebOffsets() {
return ulebOffsets;
}
/**
* {@return SLEB128 offsets from the start of the bind data}
*/
public List<Long> getSlebOffsets() {
return slebOffsets;
}
/**
* {@return string offsets from the start of the bind data}
*/
public List<Long> getStringOffsets() {
return stringOffsets;
}
/**
* A piece of binding information from a {@link BindingTable}
*/
@@ -0,0 +1,61 @@
/* ###
* 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 ghidra.app.util.bin.format.macho.commands.dyld;
import java.util.ArrayList;
import java.util.List;
/**
* Abstract class used to represent the generic components of a Mach-O opcode table
*
* @see <a href="https://github.com/apple-oss-distributions/dyld/blob/main/common/MachOLayout.cpp">common/MachOLayout.cpp</a>
* @see <a href="https://github.com/apple-oss-distributions/dyld/blob/main/common/MachOAnalyzer.cpp">common/MachOAnalyzer.cpp</a>
*/
public abstract class OpcodeTable {
protected List<Long> opcodeOffsets = new ArrayList<>();
protected List<Long> ulebOffsets = new ArrayList<>();
protected List<Long> slebOffsets = new ArrayList<>();
protected List<Long> stringOffsets = new ArrayList<>();
/**
* {@return opcode offsets from the start of the bind data}
*/
public List<Long> getOpcodeOffsets() {
return opcodeOffsets;
}
/**
* {@return ULEB128 offsets from the start of the bind data}
*/
public List<Long> getUlebOffsets() {
return ulebOffsets;
}
/**
* {@return SLEB128 offsets from the start of the bind data}
*/
public List<Long> getSlebOffsets() {
return slebOffsets;
}
/**
* {@return string offsets from the start of the bind data}
*/
public List<Long> getStringOffsets() {
return stringOffsets;
}
}
@@ -0,0 +1,84 @@
/* ###
* 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 ghidra.app.util.bin.format.macho.commands.dyld;
import ghidra.app.util.bin.format.macho.MachConstants;
import ghidra.program.model.data.*;
/**
* Rebase opcodes
*
* @see <a href="https://github.com/apple-oss-distributions/xnu/blob/main/EXTERNAL_HEADERS/mach-o/loader.h">EXTERNAL_HEADERS/mach-o/loader.h</a>
*/
public enum RebaseOpcode {
REBASE_OPCODE_DONE(0x00),
REBASE_OPCODE_SET_TYPE_IMM(0x10),
REBASE_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB(0x20),
REBASE_OPCODE_ADD_ADDR_ULEB(0x30),
REBASE_OPCODE_ADD_ADDR_IMM_SCALED(0x40),
REBASE_OPCODE_DO_REBASE_IMM_TIMES(0x50),
REBASE_OPCODE_DO_REBASE_ULEB_TIMES(0x60),
REBASE_OPCODE_DO_REBASE_ADD_ADDR_ULEB(0x70),
REBASE_OPCODE_DO_REBASE_ULEB_TIMES_SKIPPING_ULEB(0x80);
private int opcode;
/**
* Creates a new {@link RebaseOpcode} for the given opcode value
*
* @param opcode The opcode value
*/
private RebaseOpcode(int opcode) {
this.opcode = opcode;
}
/**
* {@return the opcode value}
*/
public int getOpcode() {
return opcode;
}
/**
* {@return a new data type from this enum}
*/
public static DataType toDataType() {
EnumDataType enumDataType = new EnumDataType("rebase_opcode", 1);
enumDataType.setCategoryPath(new CategoryPath(MachConstants.DATA_TYPE_CATEGORY));
for (RebaseOpcode rebaseOpcode : RebaseOpcode.values()) {
enumDataType.add(rebaseOpcode.toString(), rebaseOpcode.getOpcode());
}
return enumDataType;
}
/**
* Gets the {@link RebaseOpcode} that corresponds to the given opcode value
*
* @param opcode The opcode value
* @return The {@link RebaseOpcode} that corresponds to the given opcode value, or null if it
* does not exist
*/
public static RebaseOpcode forOpcode(int opcode) {
for (RebaseOpcode rebaseOpcode : RebaseOpcode.values()) {
if (rebaseOpcode.getOpcode() == opcode) {
return rebaseOpcode;
}
}
return null;
}
}
@@ -0,0 +1,200 @@
/* ###
* 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 ghidra.app.util.bin.format.macho.commands.dyld;
import static ghidra.app.util.bin.format.macho.commands.DyldInfoCommandConstants.*;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import ghidra.app.util.bin.BinaryReader;
import ghidra.app.util.bin.format.macho.MachHeader;
import ghidra.program.model.data.LEB128;
/**
* A Mach-O rebase table
*/
public class RebaseTable extends OpcodeTable {
private List<Rebase> rebases = new ArrayList<>();
/**
* Creates an empty {@link RebaseTable}
*/
public RebaseTable() {
super();
}
/**
* Creates and parses a new {@link RebaseTable}
*
* @param reader A {@link BinaryReader reader} positioned at the start of the rebase table
* @param header The header
* @param tableSize The size of the table, in bytes
* @throws IOException if an IO-related error occurs while parsing
*/
public RebaseTable(BinaryReader reader, MachHeader header, int tableSize) throws IOException {
this();
int pointerSize = header.getAddressSize();
long origIndex = reader.getPointerIndex();
Rebase rebase = new Rebase();
while (reader.getPointerIndex() < origIndex + tableSize) {
opcodeOffsets.add(reader.getPointerIndex() - origIndex);
byte b = reader.readNextByte();
RebaseOpcode opcode = RebaseOpcode.forOpcode(b & REBASE_OPCODE_MASK);
int immediate = b & REBASE_IMMEDIATE_MASK;
switch (opcode) {
case REBASE_OPCODE_DONE: { // 0x00
return;
}
case REBASE_OPCODE_SET_TYPE_IMM: { // 0x10
rebase.type = immediate;
break;
}
case REBASE_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB: { // 0x20
ulebOffsets.add(reader.getPointerIndex() - origIndex);
rebase.segmentIndex = immediate;
rebase.segmentOffset = reader.readNextUnsignedVarIntExact(LEB128::unsigned);
break;
}
case REBASE_OPCODE_ADD_ADDR_ULEB: { // 0x30
ulebOffsets.add(reader.getPointerIndex() - origIndex);
rebase.segmentOffset += reader.readNextUnsignedVarIntExact(LEB128::unsigned);
break;
}
case REBASE_OPCODE_ADD_ADDR_IMM_SCALED: { // 0x40
rebase.segmentOffset += immediate * pointerSize;
break;
}
case REBASE_OPCODE_DO_REBASE_IMM_TIMES: { // 0x50
for (int i = 0; i < immediate; ++i) {
rebases.add(new Rebase(rebase));
rebase.segmentOffset += pointerSize;
}
break;
}
case REBASE_OPCODE_DO_REBASE_ULEB_TIMES: { // 0x60
ulebOffsets.add(reader.getPointerIndex() - origIndex);
int count = reader.readNextUnsignedVarIntExact(LEB128::unsigned);
for (int i = 0; i < count; ++i) {
rebases.add(new Rebase(rebase));
rebase.segmentOffset += pointerSize;
}
break;
}
case REBASE_OPCODE_DO_REBASE_ADD_ADDR_ULEB: { // 0x70
ulebOffsets.add(reader.getPointerIndex() - origIndex);
rebases.add(new Rebase(rebase));
rebase.segmentOffset += reader.readNextUnsignedVarIntExact(LEB128::unsigned);
break;
}
case REBASE_OPCODE_DO_REBASE_ULEB_TIMES_SKIPPING_ULEB: { // 0x80
ulebOffsets.add(reader.getPointerIndex() - origIndex);
int count = reader.readNextUnsignedVarIntExact(LEB128::unsigned);
ulebOffsets.add(reader.getPointerIndex() - origIndex);
int skip = reader.readNextUnsignedVarIntExact(LEB128::unsigned);
for (int i = 0; i < count; ++i) {
rebases.add(new Rebase(rebase));
rebase.segmentOffset += skip + pointerSize;
}
break;
}
default: {
Rebase unknownRebase = new Rebase(rebase);
unknownRebase.unknownOpcode = Byte.toUnsignedInt(b) & REBASE_OPCODE_MASK;
rebases.add(unknownRebase);
return;
}
}
}
}
/**
* {@return the rebases}
*/
public List<Rebase> getRebases() {
return rebases;
}
/**
* A piece of rebase information from a {@link RebaseTable}
*/
public static class Rebase {
private int type;
private long segmentOffset;
private int segmentIndex = -1;
private Integer unknownOpcode;
/**
* Creates a new {@link Rebase}
*/
public Rebase() {
// Nothing to do
}
/**
* Creates a copy of the given {@link Rebase}
*
* @param rebase The {@link Rebase} to copy
*/
public Rebase(Rebase rebase) {
this.segmentIndex = rebase.segmentIndex;
this.segmentOffset = rebase.segmentOffset;
this.type = rebase.type;
this.unknownOpcode = rebase.unknownOpcode;
}
/**
* {@return The segment index}
*/
public int getSegmentIndex() {
return segmentIndex;
}
/**
* {@return The segment offset}
*/
public long getSegmentOffset() {
return segmentOffset;
}
/**
* {@return The type}
*/
public int getType() {
return type;
}
/**
* {@return null if the opcode is known; otherwise, returns the unknown opcode's value}
*/
public Integer getUnknownOpcode() {
return unknownOpcode;
}
@Override
public String toString() {
return "segment: 0x%x, index: 0x%x, kind: %d".formatted(segmentIndex, segmentOffset,
type);
}
}
}
@@ -134,7 +134,7 @@ public class MachoProgramBuilder {
processAbsoluteSymbols();
List<String> libraryPaths = processLibraries();
List<Address> chainedFixups = processChainedFixups(libraryPaths);
processBindings(false, libraryPaths);
processDyldInfo(false, libraryPaths);
processSectionRelocations();
processExternalRelocations();
processLocalRelocations();
@@ -824,10 +824,11 @@ public class MachoProgramBuilder {
log, monitor);
}
protected void processBindings(boolean doClassic, List<String> libraryPaths) throws Exception {
protected void processDyldInfo(boolean doClassic, List<String> libraryPaths) throws Exception {
List<DyldInfoCommand> commands = machoHeader.getLoadCommands(DyldInfoCommand.class);
for (DyldInfoCommand command : commands) {
processRebases(command.getRebaseTable());
processBindings(command.getBindingTable(), libraryPaths);
processBindings(command.getLazyBindingTable(), libraryPaths);
processBindings(command.getWeakBindingTable(), libraryPaths);
@@ -854,6 +855,10 @@ public class MachoProgramBuilder {
}
}
private void processRebases(RebaseTable rebaseTable) throws Exception {
// If we ever support rebasing a Mach-O at load time, this should get implemented
}
private void processBindings(BindingTable bindingTable, List<String> libraryPaths)
throws Exception {
DataConverter converter = DataConverter.getInstance(program.getLanguage().isBigEndian());