diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/macho/commands/DyldInfoCommand.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/macho/commands/DyldInfoCommand.java
index 192feb9584..e06186f538 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/macho/commands/DyldInfoCommand.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/macho/commands/DyldInfoCommand.java
@@ -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()) {
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/macho/commands/dyld/BindingTable.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/macho/commands/dyld/BindingTable.java
index 4115a4d6c1..ed5b1bd88c 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/macho/commands/dyld/BindingTable.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/macho/commands/dyld/BindingTable.java
@@ -28,29 +28,18 @@ import ghidra.program.model.data.LEB128;
/**
* A Mach-O binding table
- *
- * @see common/MachOLayout.cpp
- * @see common/MachOAnalyzer.cpp
*/
-public class BindingTable {
+public class BindingTable extends OpcodeTable {
- private List bindings;
+ private List bindings = new ArrayList<>();
private List threadedBindings;
- private List opcodeOffsets;
- private List ulebOffsets;
- private List slebOffsets;
- private List 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 getOpcodeOffsets() {
- return opcodeOffsets;
- }
-
- /**
- * {@return ULEB128 offsets from the start of the bind data}
- */
- public List getUlebOffsets() {
- return ulebOffsets;
- }
-
- /**
- * {@return SLEB128 offsets from the start of the bind data}
- */
- public List getSlebOffsets() {
- return slebOffsets;
- }
-
- /**
- * {@return string offsets from the start of the bind data}
- */
- public List getStringOffsets() {
- return stringOffsets;
- }
-
/**
* A piece of binding information from a {@link BindingTable}
*/
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/macho/commands/dyld/OpcodeTable.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/macho/commands/dyld/OpcodeTable.java
new file mode 100644
index 0000000000..5888a6f3bb
--- /dev/null
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/macho/commands/dyld/OpcodeTable.java
@@ -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 common/MachOLayout.cpp
+ * @see common/MachOAnalyzer.cpp
+ */
+public abstract class OpcodeTable {
+
+ protected List opcodeOffsets = new ArrayList<>();
+ protected List ulebOffsets = new ArrayList<>();
+ protected List slebOffsets = new ArrayList<>();
+ protected List stringOffsets = new ArrayList<>();
+
+ /**
+ * {@return opcode offsets from the start of the bind data}
+ */
+ public List getOpcodeOffsets() {
+ return opcodeOffsets;
+ }
+
+ /**
+ * {@return ULEB128 offsets from the start of the bind data}
+ */
+ public List getUlebOffsets() {
+ return ulebOffsets;
+ }
+
+ /**
+ * {@return SLEB128 offsets from the start of the bind data}
+ */
+ public List getSlebOffsets() {
+ return slebOffsets;
+ }
+
+ /**
+ * {@return string offsets from the start of the bind data}
+ */
+ public List getStringOffsets() {
+ return stringOffsets;
+ }
+}
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/macho/commands/dyld/RebaseOpcode.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/macho/commands/dyld/RebaseOpcode.java
new file mode 100644
index 0000000000..ae2b0eff99
--- /dev/null
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/macho/commands/dyld/RebaseOpcode.java
@@ -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 EXTERNAL_HEADERS/mach-o/loader.h
+ */
+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;
+ }
+}
+
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/macho/commands/dyld/RebaseTable.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/macho/commands/dyld/RebaseTable.java
new file mode 100644
index 0000000000..9b6753dcda
--- /dev/null
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bin/format/macho/commands/dyld/RebaseTable.java
@@ -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 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 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);
+ }
+ }
+}
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/MachoProgramBuilder.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/MachoProgramBuilder.java
index e7e2c09b4e..ccd2366a21 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/MachoProgramBuilder.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/MachoProgramBuilder.java
@@ -134,7 +134,7 @@ public class MachoProgramBuilder {
processAbsoluteSymbols();
List libraryPaths = processLibraries();
List 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 libraryPaths) throws Exception {
+ protected void processDyldInfo(boolean doClassic, List libraryPaths) throws Exception {
List 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 libraryPaths)
throws Exception {
DataConverter converter = DataConverter.getInstance(program.getLanguage().isBigEndian());