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());