mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2026-05-10 06:23:41 +08:00
GP-6327: Improving Objective-C msgSend() support
This commit is contained in:
-324
@@ -1,324 +0,0 @@
|
||||
/* ###
|
||||
* 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.plugin.core.analysis.objc;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import ghidra.app.services.*;
|
||||
import ghidra.app.util.bin.format.macho.SectionNames;
|
||||
import ghidra.app.util.bin.format.objc.ObjcUtils;
|
||||
import ghidra.app.util.bin.format.objc.objc1.Objc1Constants;
|
||||
import ghidra.app.util.importer.MessageLog;
|
||||
import ghidra.program.model.address.*;
|
||||
import ghidra.program.model.listing.*;
|
||||
import ghidra.program.model.mem.MemoryBlock;
|
||||
import ghidra.program.model.symbol.*;
|
||||
import ghidra.util.exception.*;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
public class Objc1MessageAnalyzer extends AbstractAnalyzer {
|
||||
private static final String DESCRIPTION =
|
||||
"An analyzer for extracting _objc_msgSend information.";
|
||||
|
||||
private static final String NAME = "Objective-C Message";
|
||||
|
||||
public Objc1MessageAnalyzer() {
|
||||
super(NAME, DESCRIPTION, AnalyzerType.FUNCTION_ANALYZER);
|
||||
setDefaultEnablement(true);
|
||||
setPriority(new AnalysisPriority(10000000));
|
||||
}
|
||||
|
||||
/* ************************************************************************** */
|
||||
/* ************************************************************************** */
|
||||
|
||||
@Override
|
||||
public boolean added(Program program, AddressSetView set, TaskMonitor monitor, MessageLog log)
|
||||
throws CancelledException {
|
||||
CurrentState state = new CurrentState(program);
|
||||
|
||||
monitor.initialize(set.getNumAddresses());
|
||||
int progress = 0;
|
||||
|
||||
AddressIterator iterator = set.getAddresses(true);
|
||||
while (iterator.hasNext()) {
|
||||
if (monitor.isCancelled()) {
|
||||
break;
|
||||
}
|
||||
|
||||
monitor.setProgress(++progress);
|
||||
Address address = iterator.next();
|
||||
|
||||
Function function = program.getListing().getFunctionAt(address);
|
||||
|
||||
try {
|
||||
inspectFunction(program, function, state, monitor);
|
||||
}
|
||||
catch (Exception e) {
|
||||
// do nothing
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canAnalyze(Program program) {
|
||||
return Objc1Constants.isObjectiveC(program);
|
||||
}
|
||||
|
||||
/* ************************************************************************** */
|
||||
/* ************************************************************************** */
|
||||
|
||||
private void inspectFunction(Program program, Function function, CurrentState state,
|
||||
TaskMonitor monitor) {
|
||||
if (function == null) {
|
||||
return;
|
||||
}
|
||||
InstructionIterator instructionIterator =
|
||||
program.getListing().getInstructions(function.getBody(), true);
|
||||
while (instructionIterator.hasNext()) {
|
||||
if (monitor.isCancelled()) {
|
||||
break;
|
||||
}
|
||||
|
||||
Instruction instruction = instructionIterator.next();
|
||||
|
||||
if (isCallingObjcMsgSend(instruction)) {
|
||||
String eolComment = instruction.getComment(CommentType.EOL);
|
||||
|
||||
if (eolComment != null) {//if a comment already exists, ignore...
|
||||
continue;
|
||||
}
|
||||
|
||||
markupInstruction(instruction, state, monitor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isCallingObjcMsgSend(Instruction instruction) {
|
||||
if (instruction.getNumOperands() != 1) {
|
||||
return false;
|
||||
}
|
||||
Reference reference = instruction.getPrimaryReference(0);
|
||||
if (reference == null) {
|
||||
return false;
|
||||
}
|
||||
if (!reference.getReferenceType().isCall() && !reference.getReferenceType().isJump()) {
|
||||
return false;
|
||||
}
|
||||
SymbolTable symbolTable = instruction.getProgram().getSymbolTable();
|
||||
Symbol symbol = symbolTable.getPrimarySymbol(reference.getToAddress());
|
||||
return isObjcNameMatch(symbol);
|
||||
}
|
||||
|
||||
private boolean isObjcNameMatch(Symbol symbol) {
|
||||
String name = symbol.getName();
|
||||
return name.startsWith(Objc1Constants.OBJC_MSG_SEND) ||
|
||||
name.equals(Objc1Constants.READ_UNIX2003) ||
|
||||
name.startsWith("thunk" + Objc1Constants.OBJC_MSG_SEND);
|
||||
}
|
||||
|
||||
private class CurrentState {
|
||||
Program program;
|
||||
Namespace globalNamespace;
|
||||
Namespace selectorNamespace;
|
||||
Namespace idNamespace;
|
||||
|
||||
String currentClassName = null;
|
||||
String currentMethodName = null;
|
||||
|
||||
//Function currentFunction = null;
|
||||
|
||||
CurrentState(Program program) {
|
||||
this.program = program;
|
||||
globalNamespace = program.getGlobalNamespace();
|
||||
SymbolTable symbolTable = program.getSymbolTable();
|
||||
selectorNamespace = findMatchingChildNamespace("@sel", globalNamespace, symbolTable);
|
||||
idNamespace = findMatchingChildNamespace("@id", globalNamespace, symbolTable);
|
||||
}
|
||||
|
||||
boolean isValid() {
|
||||
return currentMethodName != null && currentClassName != null;
|
||||
}
|
||||
|
||||
void reset() {
|
||||
currentClassName = null;
|
||||
currentMethodName = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "[" + currentClassName + " " + currentMethodName + "]";
|
||||
}
|
||||
|
||||
private Namespace findMatchingChildNamespace(String namespaceName,
|
||||
Namespace parentNamespace, SymbolTable symbolTable) {
|
||||
SymbolIterator it = symbolTable.getSymbols(parentNamespace);
|
||||
while (it.hasNext()) {
|
||||
Symbol s = it.next();
|
||||
if (s.getSymbolType() == SymbolType.NAMESPACE) {
|
||||
if (namespaceName.equals(s.getName())) {
|
||||
return (Namespace) s.getObject();
|
||||
}
|
||||
}
|
||||
}
|
||||
try {
|
||||
return symbolTable.createNameSpace(parentNamespace, namespaceName,
|
||||
SourceType.ANALYSIS);
|
||||
}
|
||||
catch (InvalidInputException | DuplicateNameException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void markupInstruction(Instruction instruction, CurrentState state,
|
||||
TaskMonitor monitor) {
|
||||
Address fromAddress = instruction.getMinAddress();
|
||||
Function function = state.program.getListing().getFunctionContaining(fromAddress);
|
||||
if (function == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
state.reset();
|
||||
InstructionIterator iter = state.program.getListing().getInstructions(fromAddress, false);
|
||||
while (iter.hasNext()) {
|
||||
if (monitor.isCancelled()) {
|
||||
break;
|
||||
}
|
||||
|
||||
Instruction instructionBefore = iter.next();
|
||||
|
||||
if (!function.getBody().contains(instructionBefore.getMinAddress())) {
|
||||
break;//don't look outside of the function
|
||||
}
|
||||
if (!isValidInstruction(instructionBefore)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Reference[] opRefs = instructionBefore.getOperandReferences(1);
|
||||
if (opRefs.length != 1) {
|
||||
continue;
|
||||
}
|
||||
Address toAddress = opRefs[0].getToAddress();
|
||||
|
||||
MemoryBlock block = state.program.getMemory().getBlock(toAddress);
|
||||
if (block == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
pullNameThrough(state, toAddress, null);
|
||||
|
||||
if (state.isValid()) {
|
||||
instruction.setComment(CommentType.EOL, state.toString());
|
||||
setReference(fromAddress, state);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Tries to lay down a reference to the function that is actually being called
|
||||
private void setReference(Address fromAddress, CurrentState state) {
|
||||
SymbolTable symbolTable = state.program.getSymbolTable();
|
||||
Symbol classSymbol = symbolTable.getClassSymbol(state.currentClassName, (Namespace) null);
|
||||
if (classSymbol == null) {
|
||||
return;
|
||||
}
|
||||
Namespace namespace = (Namespace) classSymbol.getObject();
|
||||
List<Symbol> functionSymbols = symbolTable.getSymbols(state.currentMethodName, namespace);
|
||||
if (functionSymbols.size() >= 1) {
|
||||
Address toAddress = functionSymbols.get(0).getAddress();
|
||||
ReferenceManager referenceManager = state.program.getReferenceManager();
|
||||
Reference reference = referenceManager.addMemoryReference(fromAddress, toAddress,
|
||||
RefType.UNCONDITIONAL_CALL, SourceType.ANALYSIS, 0);
|
||||
referenceManager.setPrimary(reference, true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Objective-C class and method names are stored in the
|
||||
* "__cstring" memory block. The strings are referenced
|
||||
* by either the "class" block or the "message" block.
|
||||
* The references are through n-levels of pointer indirection
|
||||
* based on the specific target (x86 vs ppc vs arm).
|
||||
* This method will pull the string through the pointer indirection
|
||||
* and set the appropriate value in the current state.
|
||||
*/
|
||||
String pullNameThrough(CurrentState state, Address address, Namespace space) {
|
||||
MemoryBlock block = state.program.getMemory().getBlock(address);
|
||||
if (block == null) {
|
||||
return null;
|
||||
}
|
||||
if (block.getName().equals(SectionNames.TEXT_CSTRING)) {
|
||||
return ObjcUtils.createString(state.program, address);
|
||||
}
|
||||
Data data = state.program.getListing().getDataAt(address);
|
||||
if (data == null) {
|
||||
data = state.program.getListing().getDataContaining(address);
|
||||
if (data == null) {
|
||||
return null;
|
||||
}
|
||||
data = data.getComponentContaining((int) address.subtract(data.getAddress()));
|
||||
if (data == null) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
Reference[] references = data.getValueReferences();
|
||||
if (references.length == 0) {
|
||||
return null;
|
||||
}
|
||||
if (address.equals(references[0].getToAddress())) {
|
||||
return null;//self reference
|
||||
}
|
||||
if (isClassBlock(block)) {
|
||||
space = state.idNamespace;
|
||||
}
|
||||
else if (isMessageBlock(block)) {
|
||||
space = state.selectorNamespace;
|
||||
}
|
||||
String name = pullNameThrough(state, references[0].getToAddress(), space);
|
||||
if (isClassBlock(block)) {
|
||||
if (state.currentClassName == null) {
|
||||
state.currentClassName = name;
|
||||
}
|
||||
}
|
||||
else if (isMessageBlock(block)) {
|
||||
if (state.currentMethodName == null) {
|
||||
state.currentMethodName = name;
|
||||
}
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
private boolean isMessageBlock(MemoryBlock block) {
|
||||
return block.getName().equals(Objc1Constants.OBJC_SECTION_MESSAGE_REFS);
|
||||
}
|
||||
|
||||
private boolean isClassBlock(MemoryBlock block) {
|
||||
return block.getName().equals(Objc1Constants.OBJC_SECTION_CLASS_REFS) ||
|
||||
block.getName().equals(Objc1Constants.OBJC_SECTION_CLASS);
|
||||
}
|
||||
|
||||
private boolean isValidInstruction(Instruction instruction) {
|
||||
if (instruction.getNumOperands() != 2) {
|
||||
return false;
|
||||
}
|
||||
boolean isMOV = instruction.getMnemonicString().equals("MOV");//intel
|
||||
boolean isLWZ = instruction.getMnemonicString().equals("lwz");//powerpc
|
||||
boolean isLDR = instruction.getMnemonicString().equals("ldr");//arm
|
||||
return isMOV || isLWZ || isLDR;
|
||||
}
|
||||
}
|
||||
-371
@@ -1,371 +0,0 @@
|
||||
/* ###
|
||||
* 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.plugin.core.analysis.objc;
|
||||
|
||||
import java.math.BigInteger;
|
||||
|
||||
import ghidra.app.services.*;
|
||||
import ghidra.app.util.bin.format.macho.SectionNames;
|
||||
import ghidra.app.util.bin.format.objc.objc1.Objc1Constants;
|
||||
import ghidra.app.util.bin.format.objc.objc2.Objc2Constants;
|
||||
import ghidra.app.util.importer.MessageLog;
|
||||
import ghidra.program.model.address.*;
|
||||
import ghidra.program.model.lang.Register;
|
||||
import ghidra.program.model.listing.*;
|
||||
import ghidra.program.model.mem.MemoryBlock;
|
||||
import ghidra.program.model.scalar.Scalar;
|
||||
import ghidra.program.model.symbol.*;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
public class Objc2MessageAnalyzer extends AbstractAnalyzer {
|
||||
private static final String NAME = "Objective-C 2 Message";
|
||||
private static final String DESCRIPTION =
|
||||
"An analyzer for extracting Objective-C 2.0 message information.";
|
||||
|
||||
/* ************************************************************************** */
|
||||
/* ************************************************************************** */
|
||||
|
||||
public Objc2MessageAnalyzer() {
|
||||
super(NAME, DESCRIPTION, AnalyzerType.FUNCTION_ANALYZER);
|
||||
setPrototype();
|
||||
//The Objective-C 2.0 analyzer should always run after the class analyzer.
|
||||
//It knows the deal!
|
||||
setPriority(AnalysisPriority.FORMAT_ANALYSIS.after());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean added(Program program, AddressSetView set, TaskMonitor monitor, MessageLog log)
|
||||
throws CancelledException {
|
||||
AddressIterator iterator = set.getAddresses(true);
|
||||
while (iterator.hasNext()) {
|
||||
Address address = iterator.next();
|
||||
|
||||
Function function = program.getListing().getFunctionAt(address);
|
||||
|
||||
try {
|
||||
inspectFunction(program, function, monitor);
|
||||
}
|
||||
catch (Exception e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canAnalyze(Program program) {
|
||||
return Objc2Constants.isObjectiveC2(program);
|
||||
}
|
||||
|
||||
/* ************************************************************************** */
|
||||
/* ************************************************************************** */
|
||||
|
||||
private void inspectFunction(Program program, Function function, TaskMonitor monitor) {
|
||||
if (function == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
InstructionIterator instructionIterator =
|
||||
program.getListing().getInstructions(function.getBody(), true);
|
||||
while (instructionIterator.hasNext()) {
|
||||
Instruction instruction = instructionIterator.next();
|
||||
|
||||
if (isCallingObjcMsgSend(instruction)) {
|
||||
String eolComment = instruction.getComment(CommentType.EOL);
|
||||
|
||||
if (eolComment != null) {//if a comment already exists, ignore...
|
||||
continue;
|
||||
}
|
||||
|
||||
markupInstruction(program, instruction, monitor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isCallingObjcMsgSend(Instruction instruction) {
|
||||
if (instruction.getNumOperands() != 1) {
|
||||
return false;
|
||||
}
|
||||
Reference reference = instruction.getPrimaryReference(0);
|
||||
if (reference == null) {
|
||||
return false;
|
||||
}
|
||||
// if (!reference.getReferenceType().isCall() && !reference.getReferenceType().isJump()) {
|
||||
// return false;
|
||||
// }
|
||||
SymbolTable symbolTable = instruction.getProgram().getSymbolTable();
|
||||
Symbol symbol = symbolTable.getPrimarySymbol(reference.getToAddress());
|
||||
return isObjcNameMatch(symbol);
|
||||
}
|
||||
|
||||
private boolean isObjcNameMatch(Symbol symbol) {
|
||||
String name = symbol.getName();
|
||||
return name.startsWith(Objc1Constants.OBJC_MSG_SEND) ||
|
||||
name.equals(Objc1Constants.READ_UNIX2003);
|
||||
}
|
||||
|
||||
private void markupInstruction(Program program, Instruction instruction, TaskMonitor monitor) {
|
||||
Address fromAddress = instruction.getMinAddress();
|
||||
Function function = program.getListing().getFunctionContaining(fromAddress);
|
||||
if (function == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
String currentClass = null;
|
||||
String currentMethod = null;
|
||||
|
||||
InstructionIterator iter = program.getListing().getInstructions(fromAddress, false);
|
||||
while (iter.hasNext()) {
|
||||
if (monitor.isCancelled()) {
|
||||
break;
|
||||
}
|
||||
Instruction instructionBefore = iter.next();
|
||||
|
||||
if (!function.getBody().contains(instructionBefore.getMinAddress())) {
|
||||
break;//don't look outside of the function
|
||||
}
|
||||
|
||||
boolean is64bit = program.getDefaultPointerSize() == 8;
|
||||
boolean isX86 = program.getLanguageID().getIdAsString().equals("x86");
|
||||
final String CLASS_REGISTER = is64bit ? "x0" : "r0";
|
||||
final String METHOD_REGISTER = is64bit ? "x1" : "r1";
|
||||
|
||||
boolean isRegisterModified = false;
|
||||
|
||||
if (isRegisterModified(instructionBefore, CLASS_REGISTER)) {
|
||||
currentClass = null;
|
||||
isRegisterModified = true;
|
||||
}
|
||||
|
||||
if (isRegisterModified(instructionBefore, METHOD_REGISTER)) {
|
||||
currentClass = null;
|
||||
isRegisterModified = true;
|
||||
}
|
||||
|
||||
if (!isValidInstruction(instructionBefore)) {
|
||||
if (isRegisterModified) {
|
||||
break;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
Object[] firstOperandObjects = instructionBefore.getOpObjects(0);
|
||||
if (firstOperandObjects.length != 1) {
|
||||
continue;
|
||||
}
|
||||
if (!(firstOperandObjects[0] instanceof Register)) {
|
||||
continue;
|
||||
}
|
||||
Register register = (Register) firstOperandObjects[0];
|
||||
|
||||
if (!register.getName().equals(CLASS_REGISTER) &&
|
||||
!register.getName().equals(METHOD_REGISTER)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Object[] secondOperandObjects = instructionBefore.getOpObjects(1);
|
||||
if (secondOperandObjects.length < 1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Address toAddress = null;
|
||||
if (secondOperandObjects.length == 1 &&
|
||||
secondOperandObjects[0] instanceof Address addr) {
|
||||
toAddress = addr;
|
||||
}
|
||||
else if (secondOperandObjects.length == 2 &&
|
||||
secondOperandObjects[0] instanceof Register reg &&
|
||||
secondOperandObjects[1] instanceof Scalar scalar) {
|
||||
Address instrAddr = instructionBefore.getAddress();
|
||||
ProgramContext programContext = program.getProgramContext();
|
||||
BigInteger registerValue = programContext.getValue(reg, instrAddr, false);
|
||||
toAddress = instrAddr.getNewAddress(registerValue.longValue() + scalar.getValue());
|
||||
}
|
||||
|
||||
if (toAddress == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
MemoryBlock block = program.getMemory().getBlock(toAddress);
|
||||
if (block == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (register.getName().equals(CLASS_REGISTER)) {
|
||||
currentClass = getClassName(program, toAddress);
|
||||
}
|
||||
else if (register.getName().equals(METHOD_REGISTER)) {
|
||||
currentMethod = getMethodName(program, toAddress);
|
||||
}
|
||||
|
||||
if (currentClass != null && currentMethod != null) {
|
||||
instruction.setComment(CommentType.EOL,
|
||||
"[" + currentClass + " " + currentMethod + "]");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isRegisterModified(Instruction instruction, String registerName) {
|
||||
Object[] destinationOperandObjects = instruction.getOpObjects(0);
|
||||
if (destinationOperandObjects.length != 1) {
|
||||
return false;
|
||||
}
|
||||
if (!(destinationOperandObjects[0] instanceof Register)) {
|
||||
return false;
|
||||
}
|
||||
Register register = (Register) destinationOperandObjects[0];
|
||||
if (register.getName().equals(registerName)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private String getClassName(Program program, Address toAddress) {
|
||||
try {
|
||||
int classPointerValue = program.getMemory().getInt(toAddress);
|
||||
Address classPointerAddress = toAddress.getNewAddress(classPointerValue);
|
||||
|
||||
if (!isObjcClassRefBlock(program, classPointerAddress)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Data classPointerData = program.getListing().getDefinedDataAt(classPointerAddress);
|
||||
|
||||
Address classAddress = (Address) classPointerData.getValue();
|
||||
|
||||
if (!isObjcDataBlock(program, classAddress)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Data classData = program.getListing().getDefinedDataAt(classAddress);
|
||||
|
||||
Data classRwPointerData = classData.getComponent(4);
|
||||
Address classRwPointerAddress = (Address) classRwPointerData.getValue();
|
||||
|
||||
if (!isObjcConstBlock(program, classRwPointerAddress)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Data classRwData = program.getListing().getDefinedDataAt(classRwPointerAddress);
|
||||
Data classNamePointerData = classRwData.getComponent(4);
|
||||
|
||||
Address classNameAddress = (Address) classNamePointerData.getValue();
|
||||
|
||||
if (!isCStringBlock(program, classNameAddress)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Data classNameData = program.getListing().getDefinedDataAt(classNameAddress);
|
||||
String className = (String) classNameData.getValue();
|
||||
return className;
|
||||
}
|
||||
catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private String getMethodName(Program program, Address toAddress) {
|
||||
try {
|
||||
int methodNamePointerValue = program.getMemory().getInt(toAddress);
|
||||
Address methodNamePointerAddress = toAddress.getNewAddress(methodNamePointerValue);
|
||||
|
||||
if (!isObjcSelectorRefBlock(program, methodNamePointerAddress)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Data methodNamePointerData =
|
||||
program.getListing().getDefinedDataAt(methodNamePointerAddress);
|
||||
|
||||
Address methodNameAddress = (Address) methodNamePointerData.getValue();
|
||||
|
||||
if (!isCStringBlock(program, methodNameAddress)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Data methodNameData = program.getListing().getDefinedDataAt(methodNameAddress);
|
||||
String methodName = (String) methodNameData.getValue();
|
||||
return methodName;
|
||||
}
|
||||
catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private boolean isValidInstruction(Instruction instruction) {
|
||||
if (instruction.getNumOperands() != 2) {
|
||||
return false;
|
||||
}
|
||||
boolean isMOV = instruction.getMnemonicString().equals("MOV");//intel
|
||||
boolean isLWZ = instruction.getMnemonicString().equals("lwz");//powerpc
|
||||
boolean isLDR = instruction.getMnemonicString().equals("ldr");//arm
|
||||
return isMOV || isLWZ || isLDR;
|
||||
}
|
||||
|
||||
private boolean isCStringBlock(Program program, Address address) {
|
||||
MemoryBlock block = program.getMemory().getBlock(address);
|
||||
if (block != null) {
|
||||
if (block.getName().equals(SectionNames.TEXT_CSTRING)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean isObjcSelectorRefBlock(Program program, Address address) {
|
||||
MemoryBlock block = program.getMemory().getBlock(address);
|
||||
if (block != null) {
|
||||
if (block.getName().equals(Objc2Constants.OBJC2_SELECTOR_REFS)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean isObjcClassRefBlock(Program program, Address address) {
|
||||
MemoryBlock block = program.getMemory().getBlock(address);
|
||||
if (block != null) {
|
||||
if (block.getName().equals(Objc2Constants.OBJC2_CLASS_REFS)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean isObjcConstBlock(Program program, Address address) {
|
||||
MemoryBlock block = program.getMemory().getBlock(address);
|
||||
if (block != null) {
|
||||
if (block.getName().equals(Objc2Constants.OBJC2_CONST)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean isObjcDataBlock(Program program, Address address) {
|
||||
MemoryBlock block = program.getMemory().getBlock(address);
|
||||
if (block != null) {
|
||||
if (block.getName().equals(Objc2Constants.OBJC2_DATA)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -15,24 +15,32 @@
|
||||
*/
|
||||
package ghidra.app.util.bin.format.objc;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.*;
|
||||
import java.math.BigInteger;
|
||||
import java.util.*;
|
||||
|
||||
import org.xml.sax.SAXException;
|
||||
|
||||
import generic.jar.ResourceFile;
|
||||
import ghidra.app.cmd.data.CreateDataCmd;
|
||||
import ghidra.app.cmd.disassemble.DisassembleCommand;
|
||||
import ghidra.app.cmd.function.CreateFunctionCmd;
|
||||
import ghidra.app.cmd.register.SetRegisterCmd;
|
||||
import ghidra.app.plugin.processors.sleigh.SleighException;
|
||||
import ghidra.app.util.bin.BinaryReader;
|
||||
import ghidra.app.util.bin.format.objc.objc2.Objc2Constants;
|
||||
import ghidra.app.util.importer.MessageLog;
|
||||
import ghidra.framework.Application;
|
||||
import ghidra.framework.cmd.BackgroundCommand;
|
||||
import ghidra.framework.cmd.Command;
|
||||
import ghidra.framework.store.LockException;
|
||||
import ghidra.program.database.SpecExtension;
|
||||
import ghidra.program.database.SpecExtension.DocInfo;
|
||||
import ghidra.program.database.symbol.ClassSymbol;
|
||||
import ghidra.program.model.address.*;
|
||||
import ghidra.program.model.data.*;
|
||||
import ghidra.program.model.data.DataUtilities.ClearDataMode;
|
||||
import ghidra.program.model.lang.Processor;
|
||||
import ghidra.program.model.lang.Register;
|
||||
import ghidra.program.model.lang.*;
|
||||
import ghidra.program.model.listing.Data;
|
||||
import ghidra.program.model.listing.Program;
|
||||
import ghidra.program.model.mem.Memory;
|
||||
@@ -43,9 +51,34 @@ import ghidra.util.Msg;
|
||||
import ghidra.util.exception.DuplicateNameException;
|
||||
import ghidra.util.exception.InvalidInputException;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
import ghidra.xml.XmlParseException;
|
||||
|
||||
/**
|
||||
* Objective-C utilities
|
||||
*/
|
||||
public final class ObjcUtils {
|
||||
|
||||
/**
|
||||
* The Objective-C compiler name, used by {@link Program#setCompiler(String)}
|
||||
*/
|
||||
public static final String OBJC_COMPILER = "objc";
|
||||
|
||||
/**
|
||||
* The Objective-C {@code _objc_msgSend} stub calling convention name (added with processor
|
||||
* extension)
|
||||
*/
|
||||
public static final String OBJC_MSGSEND_STUBS_CC = "__objc_msgSend_stub";
|
||||
|
||||
/**
|
||||
* String that prefixes Objective-C class symbols
|
||||
*/
|
||||
public static final String OBJC_CLASS_SYMBOL_PREFIX = "_OBJC_CLASS_$_";
|
||||
|
||||
/**
|
||||
* String that prefixes Objective-C meta-class symbols
|
||||
*/
|
||||
public static final String OBJC_META_CLASS_SYMBOL_PREFIX = "_OBJC_METACLASS_$_";
|
||||
|
||||
/**
|
||||
* {@return the next read index value}
|
||||
* <p>
|
||||
@@ -370,4 +403,99 @@ public final class ObjcUtils {
|
||||
.filter(b -> b.getName().equals(section))
|
||||
.toList();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@return true if the given {@link Program} is an Objective-C program; otherwise, false}
|
||||
* <p>
|
||||
* NOTE: This method only identifies Mach-O Objective-C programs. ELF Objective-C programs
|
||||
* produced with GCC use different section names.
|
||||
*
|
||||
* @param program The {@link Program} to check
|
||||
*/
|
||||
public static boolean isObjc(Program program) {
|
||||
return isObjc(
|
||||
Arrays.stream(program.getMemory().getBlocks()).map(MemoryBlock::getName).toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@return true if the given {@link List} of section names contains an Objective-C section
|
||||
* name; otherwise, false}
|
||||
* <p>
|
||||
* NOTE: This method only identifies Mach-O Objective-C programs. ELF Objective-C programs
|
||||
* produced with GCC use different section names.
|
||||
*
|
||||
* @param sectionNames The {@link List} of section names to check
|
||||
*/
|
||||
public static boolean isObjc(List<String> sectionNames) {
|
||||
return sectionNames.stream().anyMatch(n -> n.startsWith(Objc2Constants.OBJC2_PREFIX));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@return the given name with any Objective-C class prefixes stripped off}
|
||||
*
|
||||
* @param name The name to strip
|
||||
* @see #OBJC_CLASS_SYMBOL_PREFIX
|
||||
* @see #OBJC_META_CLASS_SYMBOL_PREFIX
|
||||
*/
|
||||
public static String stripClassPrefix(String name) {
|
||||
if (name.startsWith(ObjcUtils.OBJC_CLASS_SYMBOL_PREFIX)) {
|
||||
return name.substring(ObjcUtils.OBJC_CLASS_SYMBOL_PREFIX.length());
|
||||
}
|
||||
if (name.startsWith(ObjcUtils.OBJC_META_CLASS_SYMBOL_PREFIX)) {
|
||||
return name.substring(ObjcUtils.OBJC_META_CLASS_SYMBOL_PREFIX.length());
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds Objective-C processor extensions to the {@link Program}, which include:
|
||||
* <ul>
|
||||
* <li>A special calling convention used by objc_msgSend stubs</li>
|
||||
* <li>Call fixups to clear out a lot of Objective-C Automatic Reference Counting (ARC) clutter</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param program The {@link Program} to add the extensions to
|
||||
* @param monitor A cancelable task monitor
|
||||
* @return The number of extensions successfully added
|
||||
* @throws IOException if an IO-related error occurred
|
||||
* @see <a href="https://doi.org/10.1109/STATIC66697.2025.00005">Heros in Action: Analyzing Objective-C Binaries through Decompilation and IFDS</a>
|
||||
* @see <a href="https://youtu.be/ojXI7Gio8Pg?si=zcAaZ2KGeBFcAabn">RE//verse 2025: Langs Beyond The C</a>
|
||||
*/
|
||||
public static int addExtensions(Program program, TaskMonitor monitor) throws IOException {
|
||||
Language language = program.getLanguageCompilerSpecPair().getLanguage();
|
||||
Processor processor = language.getProcessor();
|
||||
String spath = "extensions/" + OBJC_COMPILER;
|
||||
|
||||
int extensionCount = 0;
|
||||
|
||||
try {
|
||||
ResourceFile module =
|
||||
Application.getModuleDataSubDirectory(processor.toString(), spath);
|
||||
ResourceFile[] files = module.listFiles();
|
||||
if (files != null) {
|
||||
for (ResourceFile file : files) {
|
||||
InputStream stream = file.getInputStream();
|
||||
byte[] bytes = stream.readAllBytes();
|
||||
String xml = new String(bytes);
|
||||
try {
|
||||
SpecExtension extension = new SpecExtension(program);
|
||||
DocInfo docInfo = extension.testExtensionDocument(xml);
|
||||
if (SpecExtension.getCompilerSpecExtension(program, docInfo) == null) {
|
||||
extension.addReplaceCompilerSpecExtension(xml, monitor);
|
||||
extensionCount++;
|
||||
}
|
||||
}
|
||||
catch (SleighException | SAXException | XmlParseException | LockException e) {
|
||||
Msg.error(ObjcUtils.class,
|
||||
"Failed to load Objective-C cspec extension: " + file, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (FileNotFoundException e) {
|
||||
// fall thru
|
||||
}
|
||||
|
||||
return extensionCount;
|
||||
}
|
||||
}
|
||||
|
||||
+2
-1
@@ -248,7 +248,8 @@ public final class Objc1TypeEncodings {
|
||||
}
|
||||
case _C_SEL: {
|
||||
buffer.deleteCharAt(0);
|
||||
return createTypeDef("SEL");
|
||||
return new TypedefDataType("SEL",
|
||||
PointerDataType.getPointer(new CharDataType(), pointerSize));
|
||||
}
|
||||
case _C_CHR: {
|
||||
buffer.deleteCharAt(0);
|
||||
|
||||
+1
@@ -82,6 +82,7 @@ public class Objc2MessageReference extends ObjcTypeMetadataStructure {
|
||||
Structure struct = new StructureDataType(NAME, 0);
|
||||
struct.add(new PointerDataType(VOID), pointerSize, "imp", null);
|
||||
struct.add(new PointerDataType(ASCII), pointerSize, "sel", null);
|
||||
struct.setCategoryPath(Objc2Constants.CATEGORY_PATH);
|
||||
return struct;
|
||||
}
|
||||
}
|
||||
|
||||
+54
-49
@@ -40,6 +40,7 @@ import ghidra.app.util.bin.format.macho.dyld.DyldChainedPtr.DyldChainType;
|
||||
import ghidra.app.util.bin.format.macho.dyld.DyldFixup;
|
||||
import ghidra.app.util.bin.format.macho.relocation.*;
|
||||
import ghidra.app.util.bin.format.macho.threadcommand.ThreadCommand;
|
||||
import ghidra.app.util.bin.format.objc.ObjcUtils;
|
||||
import ghidra.app.util.bin.format.objc.objc1.Objc1Constants;
|
||||
import ghidra.app.util.importer.MessageLog;
|
||||
import ghidra.framework.options.Options;
|
||||
@@ -68,6 +69,13 @@ public class MachoProgramBuilder {
|
||||
|
||||
public static final String HEADER_SYMBOL = "MACH_HEADER";
|
||||
|
||||
/**
|
||||
* The spacing between symbols that get created in the
|
||||
* {@link MemoryBlock#EXTERNAL_BLOCK_NAME EXTERNAL block}. This will allow some room for
|
||||
* the recreation of external method thunks as they are discovered during analysis.
|
||||
*/
|
||||
public static final int UNDEFINED_SYMBOL_SPACING = 0x100;
|
||||
|
||||
protected MachHeader machoHeader;
|
||||
protected Program program;
|
||||
protected ByteProvider provider;
|
||||
@@ -168,7 +176,7 @@ public class MachoProgramBuilder {
|
||||
// Perform additional actions
|
||||
renameObjMsgSendRtpSymbol();
|
||||
fixupProgramTree(null); // should be done last to account for new memory blocks
|
||||
setCompiler();
|
||||
setCompiler(provider.getName());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -708,37 +716,37 @@ public class MachoProgramBuilder {
|
||||
if (monitor.isCancelled()) {
|
||||
return;
|
||||
}
|
||||
if (!(command instanceof SymbolTableCommand)) {
|
||||
if (!(command instanceof SymbolTableCommand symbolTableCommand)) {
|
||||
continue;
|
||||
}
|
||||
SymbolTableCommand symbolTableCommand = (SymbolTableCommand) command;
|
||||
List<NList> symbols = symbolTableCommand.getSymbols();
|
||||
monitor.initialize(symbols.size(), "Collectiing undefined symbols...");
|
||||
monitor.initialize(symbols.size(), "Collecting undefined symbols...");
|
||||
for (NList symbol : symbols) {
|
||||
monitor.increment();
|
||||
if (symbol.isSymbolicDebugging()) {
|
||||
continue;
|
||||
}
|
||||
if (symbol.isTypeUndefined()) {
|
||||
List<Symbol> globalSymbols = program.getSymbolTable()
|
||||
.getLabelOrFunctionSymbols(symbol.getString(), null);
|
||||
if (globalSymbols.isEmpty()) {//IF IT DOES NOT ALREADY EXIST...
|
||||
String name = symbol.getString();
|
||||
List<Symbol> globalSymbols =
|
||||
program.getSymbolTable().getLabelOrFunctionSymbols(name, null);
|
||||
if (globalSymbols.isEmpty()) { //IF IT DOES NOT ALREADY EXIST...
|
||||
undefinedSymbols.add(symbol);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (undefinedSymbols.size() == 0) {
|
||||
if (undefinedSymbols.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
Address addr = MemoryBlockUtils.addExternalBlock(program,
|
||||
undefinedSymbols.size() * machoHeader.getAddressSize(), log);
|
||||
long blockSize = undefinedSymbols.size() * UNDEFINED_SYMBOL_SPACING;
|
||||
Address addr = MemoryBlockUtils.addExternalBlock(program, blockSize, log);
|
||||
monitor.initialize(undefinedSymbols.size(), "Processing undefined symbols...");
|
||||
for (NList symbol : undefinedSymbols) {
|
||||
monitor.increment();
|
||||
String name = SymbolUtilities.replaceInvalidChars(symbol.getString(), true);
|
||||
try {
|
||||
String name = SymbolUtilities.replaceInvalidChars(symbol.getString(), true);
|
||||
if (name != null && name.length() > 0) {
|
||||
program.getSymbolTable().createLabel(addr, name, SourceType.IMPORTED);
|
||||
program.getExternalManager()
|
||||
@@ -748,7 +756,7 @@ public class MachoProgramBuilder {
|
||||
catch (Exception e) {
|
||||
log.appendMsg("Unable to create undefined symbol: " + e.getMessage());
|
||||
}
|
||||
addr = addr.add(machoHeader.getAddressSize());
|
||||
addr = addr.add(UNDEFINED_SYMBOL_SPACING);
|
||||
}
|
||||
}
|
||||
catch (Exception e) {
|
||||
@@ -761,10 +769,9 @@ public class MachoProgramBuilder {
|
||||
List<LoadCommand> commands = machoHeader.getLoadCommands();
|
||||
for (LoadCommand command : commands) {
|
||||
monitor.checkCancelled();
|
||||
if (!(command instanceof SymbolTableCommand)) {
|
||||
if (!(command instanceof SymbolTableCommand symbolTableCommand)) {
|
||||
continue;
|
||||
}
|
||||
SymbolTableCommand symbolTableCommand = (SymbolTableCommand) command;
|
||||
List<NList> symbols = symbolTableCommand.getSymbols();
|
||||
monitor.initialize(symbols.size(), "Collecting absolute symbols...");
|
||||
for (NList symbol : symbols) {
|
||||
@@ -995,8 +1002,7 @@ public class MachoProgramBuilder {
|
||||
listing.setComment(loadCommandAddr, CommentType.PRE,
|
||||
LoadCommandTypes.getLoadCommandName(loadCommand.getCommandType()));
|
||||
|
||||
if (loadCommand instanceof SegmentCommand) {
|
||||
SegmentCommand segmentCommand = (SegmentCommand) loadCommand;
|
||||
if (loadCommand instanceof SegmentCommand segmentCommand) {
|
||||
listing.setComment(loadCommandAddr, CommentType.EOL,
|
||||
segmentCommand.getSegmentName());
|
||||
|
||||
@@ -1011,51 +1017,43 @@ public class MachoProgramBuilder {
|
||||
sectionOffset += sectionDataType.getLength();
|
||||
}
|
||||
}
|
||||
else if (loadCommand instanceof DynamicLinkerCommand) {
|
||||
DynamicLinkerCommand dynamicLinkerCommand = (DynamicLinkerCommand) loadCommand;
|
||||
else if (loadCommand instanceof DynamicLinkerCommand dynamicLinkerCommand) {
|
||||
LoadCommandString name = dynamicLinkerCommand.getLoadCommandString();
|
||||
DataUtilities.createData(program, loadCommandAddr.add(name.getOffset()),
|
||||
StructConverter.STRING, loadCommand.getCommandSize() - name.getOffset(),
|
||||
DataUtilities.ClearDataMode.CHECK_FOR_SPACE);
|
||||
}
|
||||
else if (loadCommand instanceof DynamicLibraryCommand) {
|
||||
DynamicLibraryCommand dynamicLibraryCommand =
|
||||
(DynamicLibraryCommand) loadCommand;
|
||||
else if (loadCommand instanceof DynamicLibraryCommand dynamicLibraryCommand) {
|
||||
LoadCommandString name = dynamicLibraryCommand.getDynamicLibrary().getName();
|
||||
DataUtilities.createData(program, loadCommandAddr.add(name.getOffset()),
|
||||
StructConverter.STRING, loadCommand.getCommandSize() - name.getOffset(),
|
||||
DataUtilities.ClearDataMode.CHECK_FOR_SPACE);
|
||||
}
|
||||
else if (loadCommand instanceof RunPathCommand) {
|
||||
RunPathCommand runPathCommand = (RunPathCommand) loadCommand;
|
||||
else if (loadCommand instanceof RunPathCommand runPathCommand) {
|
||||
LoadCommandString path = runPathCommand.getPath();
|
||||
DataUtilities.createData(program, loadCommandAddr.add(path.getOffset()),
|
||||
StructConverter.STRING, loadCommand.getCommandSize() - path.getOffset(),
|
||||
DataUtilities.ClearDataMode.CHECK_FOR_SPACE);
|
||||
}
|
||||
else if (loadCommand instanceof SubFrameworkCommand) {
|
||||
SubFrameworkCommand subFrameworkCommand = (SubFrameworkCommand) loadCommand;
|
||||
else if (loadCommand instanceof SubFrameworkCommand subFrameworkCommand) {
|
||||
LoadCommandString name = subFrameworkCommand.getUmbrellaFrameworkName();
|
||||
DataUtilities.createData(program, loadCommandAddr.add(name.getOffset()),
|
||||
StructConverter.STRING, loadCommand.getCommandSize() - name.getOffset(),
|
||||
DataUtilities.ClearDataMode.CHECK_FOR_SPACE);
|
||||
}
|
||||
else if (loadCommand instanceof SubClientCommand) {
|
||||
SubClientCommand subClientCommand = (SubClientCommand) loadCommand;
|
||||
else if (loadCommand instanceof SubClientCommand subClientCommand) {
|
||||
LoadCommandString name = subClientCommand.getClientName();
|
||||
DataUtilities.createData(program, loadCommandAddr.add(name.getOffset()),
|
||||
StructConverter.STRING, loadCommand.getCommandSize() - name.getOffset(),
|
||||
DataUtilities.ClearDataMode.CHECK_FOR_SPACE);
|
||||
}
|
||||
else if (loadCommand instanceof SubLibraryCommand) {
|
||||
SubLibraryCommand subLibraryCommand = (SubLibraryCommand) loadCommand;
|
||||
else if (loadCommand instanceof SubLibraryCommand subLibraryCommand) {
|
||||
LoadCommandString name = subLibraryCommand.getSubLibraryName();
|
||||
DataUtilities.createData(program, loadCommandAddr.add(name.getOffset()),
|
||||
StructConverter.STRING, loadCommand.getCommandSize() - name.getOffset(),
|
||||
DataUtilities.ClearDataMode.CHECK_FOR_SPACE);
|
||||
}
|
||||
else if (loadCommand instanceof SubUmbrellaCommand) {
|
||||
SubUmbrellaCommand subUmbrellaCommand = (SubUmbrellaCommand) loadCommand;
|
||||
else if (loadCommand instanceof SubUmbrellaCommand subUmbrellaCommand) {
|
||||
LoadCommandString name = subUmbrellaCommand.getSubUmbrellaFrameworkName();
|
||||
DataUtilities.createData(program, loadCommandAddr.add(name.getOffset()),
|
||||
StructConverter.STRING, loadCommand.getCommandSize() - name.getOffset(),
|
||||
@@ -1064,15 +1062,13 @@ public class MachoProgramBuilder {
|
||||
StructConverter.STRING, loadCommand.getCommandSize() - name.getOffset(),
|
||||
DataUtilities.ClearDataMode.CHECK_FOR_SPACE);
|
||||
}
|
||||
else if (loadCommand instanceof FileSetEntryCommand) {
|
||||
FileSetEntryCommand fileSetEntryCommand = (FileSetEntryCommand) loadCommand;
|
||||
else if (loadCommand instanceof FileSetEntryCommand fileSetEntryCommand) {
|
||||
LoadCommandString name = fileSetEntryCommand.getFileSetEntryId();
|
||||
DataUtilities.createData(program, loadCommandAddr.add(name.getOffset()),
|
||||
StructConverter.STRING, loadCommand.getCommandSize() - name.getOffset(),
|
||||
DataUtilities.ClearDataMode.CHECK_FOR_SPACE);
|
||||
}
|
||||
else if (loadCommand instanceof LinkerOptionCommand) {
|
||||
LinkerOptionCommand linkerOptionCommand = (LinkerOptionCommand) loadCommand;
|
||||
else if (loadCommand instanceof LinkerOptionCommand linkerOptionCommand) {
|
||||
List<String> linkerOptions = linkerOptionCommand.getLinkerOptions();
|
||||
int offset = linkerOptionCommand.toDataType().getLength();
|
||||
for (int i = 0; i < linkerOptions.size(); i++) {
|
||||
@@ -1813,27 +1809,36 @@ public class MachoProgramBuilder {
|
||||
}
|
||||
}
|
||||
|
||||
protected void setCompiler() throws CancelledException {
|
||||
// Check for Rust
|
||||
protected void setCompiler(String source) throws CancelledException {
|
||||
if (ObjcUtils.isObjc(program)) {
|
||||
try {
|
||||
program.setCompiler(ObjcUtils.OBJC_COMPILER);
|
||||
int count = ObjcUtils.addExtensions(program, monitor);
|
||||
if (count > 0) {
|
||||
log.appendMsg("%s: installed %d objc SpecExtensions".formatted(source, count));
|
||||
}
|
||||
}
|
||||
catch (IOException e) {
|
||||
log.appendMsg("%s: objc error - %s".formatted(source, e.getMessage()));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
SegmentCommand segment = machoHeader.getSegment(SegmentNames.SEG_TEXT);
|
||||
if (segment == null) {
|
||||
return;
|
||||
}
|
||||
Section section = segment.getSectionByName(SectionNames.TEXT_CONST);
|
||||
if (section == null) {
|
||||
return;
|
||||
}
|
||||
if (RustUtilities.isRust(program,
|
||||
Section section =
|
||||
machoHeader.getSection(SegmentNames.SEG_TEXT, SectionNames.TEXT_CONST);
|
||||
if (section != null && RustUtilities.isRust(program,
|
||||
memory.getBlock(space.getAddress(section.getAddress())), monitor)) {
|
||||
program.setCompiler(RustConstants.RUST_COMPILER);
|
||||
int extensionCount = RustUtilities.addExtensions(program, monitor,
|
||||
int count = RustUtilities.addExtensions(program, monitor,
|
||||
RustConstants.RUST_EXTENSIONS_UNIX);
|
||||
log.appendMsg("Installed " + extensionCount + " Rust cspec extensions");
|
||||
if (count > 0) {
|
||||
log.appendMsg("%s: installed %d rust SpecExtensions".formatted(source, count));
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (IOException e) {
|
||||
log.appendMsg("Rust error: " + e.getMessage());
|
||||
log.appendMsg("%s: Rust error - %s".formatted(source, e.getMessage()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+494
-195
File diff suppressed because it is too large
Load Diff
+10
@@ -307,6 +307,16 @@ public class SpecExtension {
|
||||
return options.getString(optionName, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the raw string making up an extension, given its {@link DocInfo}
|
||||
* @param program is the program to extract the extension from
|
||||
* @param docInfo is extension's {@link DocInfo}
|
||||
* @return the extension string or null
|
||||
*/
|
||||
public static String getCompilerSpecExtension(Program program, DocInfo docInfo) {
|
||||
return getCompilerSpecExtension(program, docInfo.getType(), docInfo.getFormalName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Check the format version for spec extensions for a given program.
|
||||
* If the program reports a version that does not match the current
|
||||
|
||||
@@ -2,6 +2,14 @@
|
||||
Module.manifest||GHIDRA||||END|
|
||||
README.md||GHIDRA||||END|
|
||||
data/aarch64-pltThunks.xml||GHIDRA||||END|
|
||||
data/extensions/objc/chkstk_darwin_fixup.xml||GHIDRA||||END|
|
||||
data/extensions/objc/objc_getProperty_fixup.xml||GHIDRA||||END|
|
||||
data/extensions/objc/objc_load_fixup.xml||GHIDRA||||END|
|
||||
data/extensions/objc/objc_msgSend_stub.xml||GHIDRA||||END|
|
||||
data/extensions/objc/objc_release_fixup.xml||GHIDRA||||END|
|
||||
data/extensions/objc/objc_retain_fixup.xml||GHIDRA||||END|
|
||||
data/extensions/objc/objc_setProperty_fixup.xml||GHIDRA||||END|
|
||||
data/extensions/objc/objc_store_fixup.xml||GHIDRA||||END|
|
||||
data/languages/AARCH64.cspec||GHIDRA||||END|
|
||||
data/languages/AARCH64.dwarf||GHIDRA||||END|
|
||||
data/languages/AARCH64.ldefs||GHIDRA||||END|
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
<callfixup name="___chkstk_darwin_fixup">
|
||||
<target name="___chkstk_darwin"/>
|
||||
<pcode>
|
||||
<body><![CDATA[
|
||||
x0 = x0;
|
||||
]]></body>
|
||||
</pcode>
|
||||
</callfixup>
|
||||
@@ -0,0 +1,8 @@
|
||||
<callfixup name="_objc_getProperty_fixup">
|
||||
<target name="_objc_getProperty"/>
|
||||
<pcode>
|
||||
<body><![CDATA[
|
||||
x0 = *(x0 + x2);
|
||||
]]></body>
|
||||
</pcode>
|
||||
</callfixup>
|
||||
@@ -0,0 +1,9 @@
|
||||
<callfixup name="_objc_load_fixup">
|
||||
<target name="_objc_loadWeak"/>
|
||||
<target name="_objc_loadWeakRetained"/>
|
||||
<pcode>
|
||||
<body><![CDATA[
|
||||
x0 = *x0;
|
||||
]]></body>
|
||||
</pcode>
|
||||
</callfixup>
|
||||
@@ -0,0 +1,187 @@
|
||||
<prototype name="__objc_msgSend_stub" extrapop="0" stackshift="0">
|
||||
<!-- Used for "_objc_msgSend$methodName" stubs, which don't consider x1 a parameter register-->
|
||||
<input>
|
||||
<pentry minsize="8" maxsize="8" storage="hiddenret">
|
||||
<register name="x8"/>
|
||||
</pentry>
|
||||
<pentry minsize="1" maxsize="16" storage="float">
|
||||
<register name="q0"/>
|
||||
</pentry>
|
||||
<pentry minsize="1" maxsize="16" storage="float">
|
||||
<register name="q1"/>
|
||||
</pentry>
|
||||
<pentry minsize="1" maxsize="16" storage="float">
|
||||
<register name="q2"/>
|
||||
</pentry>
|
||||
<pentry minsize="1" maxsize="16" storage="float">
|
||||
<register name="q3"/>
|
||||
</pentry>
|
||||
<pentry minsize="1" maxsize="16" storage="float">
|
||||
<register name="q4"/>
|
||||
</pentry>
|
||||
<pentry minsize="1" maxsize="16" storage="float">
|
||||
<register name="q5"/>
|
||||
</pentry>
|
||||
<pentry minsize="1" maxsize="16" storage="float">
|
||||
<register name="q6"/>
|
||||
</pentry>
|
||||
<pentry minsize="1" maxsize="16" storage="float">
|
||||
<register name="q7"/>
|
||||
</pentry>
|
||||
<pentry minsize="1" maxsize="8" extension="zero">
|
||||
<register name="x0"/>
|
||||
</pentry>
|
||||
<pentry minsize="1" maxsize="8" extension="zero">
|
||||
<register name="x2"/>
|
||||
</pentry>
|
||||
<pentry minsize="1" maxsize="8" extension="zero">
|
||||
<register name="x3"/>
|
||||
</pentry>
|
||||
<pentry minsize="1" maxsize="8" extension="zero">
|
||||
<register name="x4"/>
|
||||
</pentry>
|
||||
<pentry minsize="1" maxsize="8" extension="zero">
|
||||
<register name="x5"/>
|
||||
</pentry>
|
||||
<pentry minsize="1" maxsize="8" extension="zero">
|
||||
<register name="x6"/>
|
||||
</pentry>
|
||||
<pentry minsize="1" maxsize="8" extension="zero">
|
||||
<register name="x7"/>
|
||||
</pentry>
|
||||
<pentry minsize="1" maxsize="500" align="8">
|
||||
<addr offset="0" space="stack"/>
|
||||
</pentry>
|
||||
<rule>
|
||||
<datatype name="homogeneous-float-aggregate"/>
|
||||
<join_per_primitive storage="float"/>
|
||||
</rule>
|
||||
<rule>
|
||||
<datatype name="homogeneous-float-aggregate"/>
|
||||
<goto_stack/> <!-- Don't consume general purpose registers -->
|
||||
<consume_extra storage="float"/> <!-- Once the stack has been used, don't go back to registers -->
|
||||
</rule>
|
||||
<rule>
|
||||
<datatype name="struct" minsize="17"/>
|
||||
<convert_to_ptr/>
|
||||
</rule>
|
||||
<rule>
|
||||
<datatype name="union" minsize="17"/>
|
||||
<convert_to_ptr/>
|
||||
</rule>
|
||||
<!-- Variadic arguments are passed differently than in the AARCH64 standard -->
|
||||
<!-- See "Writing ARM64 Code for Apple Platforms" -->
|
||||
<rule>
|
||||
<datatype name="any"/>
|
||||
<varargs first="0"/>
|
||||
<goto_stack/>
|
||||
</rule>
|
||||
<rule>
|
||||
<datatype name="float"/>
|
||||
<consume storage="float"/>
|
||||
</rule>
|
||||
<rule>
|
||||
<datatype name="float"/>
|
||||
<goto_stack/> <!-- Don't consume general purpose registers -->
|
||||
</rule>
|
||||
<rule>
|
||||
<datatype name="any"/>
|
||||
<join align="true"/> <!-- Chunk from general purpose registers -->
|
||||
</rule>
|
||||
</input>
|
||||
<output>
|
||||
<pentry minsize="1" maxsize="16" storage="float">
|
||||
<register name="q0"/>
|
||||
</pentry>
|
||||
<pentry minsize="1" maxsize="16" storage="float">
|
||||
<register name="q1"/>
|
||||
</pentry>
|
||||
<pentry minsize="1" maxsize="16" storage="float">
|
||||
<register name="q2"/>
|
||||
</pentry>
|
||||
<pentry minsize="1" maxsize="16" storage="float">
|
||||
<register name="q3"/>
|
||||
</pentry>
|
||||
<pentry minsize="1" maxsize="8" extension="zero">
|
||||
<register name="x0"/>
|
||||
</pentry>
|
||||
<pentry minsize="1" maxsize="8" extension="zero">
|
||||
<register name="x1"/>
|
||||
</pentry>
|
||||
<rule>
|
||||
<datatype name="homogeneous-float-aggregate"/>
|
||||
<join_per_primitive storage="float"/>
|
||||
</rule>
|
||||
<rule>
|
||||
<datatype name="float"/>
|
||||
<consume storage="float"/>
|
||||
</rule>
|
||||
<rule>
|
||||
<datatype name="any" minsize="17"/>
|
||||
<hidden_return voidlock="true"/>
|
||||
</rule>
|
||||
<rule>
|
||||
<datatype name="any"/>
|
||||
<join/>
|
||||
</rule>
|
||||
</output>
|
||||
<unaffected>
|
||||
<register name="x19"/>
|
||||
<register name="x20"/>
|
||||
<register name="x21"/>
|
||||
<register name="x22"/>
|
||||
<register name="x23"/>
|
||||
<register name="x24"/>
|
||||
<register name="x25"/>
|
||||
<register name="x26"/>
|
||||
<register name="x27"/>
|
||||
<register name="x28"/>
|
||||
<register name="x29"/>
|
||||
<register name="x30"/>
|
||||
<register name="sp"/>
|
||||
<!-- vectors -->
|
||||
<register name="d8"/>
|
||||
<register name="d9"/>
|
||||
<register name="d10"/>
|
||||
<register name="d11"/>
|
||||
<register name="d12"/>
|
||||
<register name="d13"/>
|
||||
<register name="d14"/>
|
||||
<register name="d15"/>
|
||||
</unaffected>
|
||||
<killedbycall>
|
||||
<register name="x0"/>
|
||||
<register name="x1"/>
|
||||
<register name="q0"/>
|
||||
<!-- x8: indirect result location register, which is not
|
||||
reflected in the pentry list -->
|
||||
<register name="x8"/>
|
||||
<register name="x9"/>
|
||||
<register name="x10"/>
|
||||
<register name="x11"/>
|
||||
<register name="x12"/>
|
||||
<register name="x13"/>
|
||||
<register name="x14"/>
|
||||
<register name="x15"/>
|
||||
<register name="x16"/>
|
||||
<register name="x17"/>
|
||||
<register name="x18"/>
|
||||
<!-- vectors -->
|
||||
<register name="d16"/>
|
||||
<register name="d17"/>
|
||||
<register name="d18"/>
|
||||
<register name="d19"/>
|
||||
<register name="d20"/>
|
||||
<register name="d21"/>
|
||||
<register name="d22"/>
|
||||
<register name="d23"/>
|
||||
<register name="d24"/>
|
||||
<register name="d25"/>
|
||||
<register name="d26"/>
|
||||
<register name="d27"/>
|
||||
<register name="d28"/>
|
||||
<register name="d29"/>
|
||||
<register name="d30"/>
|
||||
<register name="d31"/>
|
||||
</killedbycall>
|
||||
</prototype>
|
||||
@@ -0,0 +1,37 @@
|
||||
<callfixup name="_objc_release_fixup">
|
||||
<target name="_objc_release"/>
|
||||
<target name="_objc_release_x0"/>
|
||||
<target name="_objc_release_x1"/>
|
||||
<target name="_objc_release_x2"/>
|
||||
<target name="_objc_release_x3"/>
|
||||
<target name="_objc_release_x4"/>
|
||||
<target name="_objc_release_x5"/>
|
||||
<target name="_objc_release_x6"/>
|
||||
<target name="_objc_release_x7"/>
|
||||
<target name="_objc_release_x8"/>
|
||||
<target name="_objc_release_x9"/>
|
||||
<target name="_objc_release_x10"/>
|
||||
<target name="_objc_release_x11"/>
|
||||
<target name="_objc_release_x12"/>
|
||||
<target name="_objc_release_x13"/>
|
||||
<target name="_objc_release_x14"/>
|
||||
<target name="_objc_release_x15"/>
|
||||
<target name="_objc_release_x16"/>
|
||||
<target name="_objc_release_x17"/>
|
||||
<target name="_objc_release_x18"/>
|
||||
<target name="_objc_release_x19"/>
|
||||
<target name="_objc_release_x20"/>
|
||||
<target name="_objc_release_x21"/>
|
||||
<target name="_objc_release_x22"/>
|
||||
<target name="_objc_release_x23"/>
|
||||
<target name="_objc_release_x24"/>
|
||||
<target name="_objc_release_x25"/>
|
||||
<target name="_objc_release_x26"/>
|
||||
<target name="_objc_release_x27"/>
|
||||
<target name="_objc_release_x28"/>
|
||||
<pcode>
|
||||
<body><![CDATA[
|
||||
x0 = 0;
|
||||
]]></body>
|
||||
</pcode>
|
||||
</callfixup>
|
||||
@@ -0,0 +1,46 @@
|
||||
<callfixup name="_objc_retain_fixup">
|
||||
<target name="_objc_retain"/>
|
||||
<target name="_objc_retainAutoreleasedReturnValue"/>
|
||||
<target name="_objc_retainAutoreleaseReturnValue"/>
|
||||
<target name="_objc_autoreleaseReturnValue"/>
|
||||
<target name="_objc_retainAutoRelease"/>
|
||||
<target name="_objc_autorelease"/>
|
||||
<target name="_objc_claimAutoreleasedReturnValue"/>
|
||||
<target name="_objc_opt_self"/>
|
||||
<target name="_objc_unsafeClaimAutoreleasedReturnValue"/>
|
||||
<target name="_objc_retainBlock"/>
|
||||
<target name="_objc_retain_x0"/>
|
||||
<target name="_objc_retain_x1"/>
|
||||
<target name="_objc_retain_x2"/>
|
||||
<target name="_objc_retain_x3"/>
|
||||
<target name="_objc_retain_x4"/>
|
||||
<target name="_objc_retain_x5"/>
|
||||
<target name="_objc_retain_x6"/>
|
||||
<target name="_objc_retain_x7"/>
|
||||
<target name="_objc_retain_x8"/>
|
||||
<target name="_objc_retain_x9"/>
|
||||
<target name="_objc_retain_x10"/>
|
||||
<target name="_objc_retain_x11"/>
|
||||
<target name="_objc_retain_x12"/>
|
||||
<target name="_objc_retain_x13"/>
|
||||
<target name="_objc_retain_x14"/>
|
||||
<target name="_objc_retain_x15"/>
|
||||
<target name="_objc_retain_x16"/>
|
||||
<target name="_objc_retain_x17"/>
|
||||
<target name="_objc_retain_x18"/>
|
||||
<target name="_objc_retain_x19"/>
|
||||
<target name="_objc_retain_x20"/>
|
||||
<target name="_objc_retain_x21"/>
|
||||
<target name="_objc_retain_x22"/>
|
||||
<target name="_objc_retain_x23"/>
|
||||
<target name="_objc_retain_x24"/>
|
||||
<target name="_objc_retain_x25"/>
|
||||
<target name="_objc_retain_x26"/>
|
||||
<target name="_objc_retain_x27"/>
|
||||
<target name="_objc_retain_x28"/>
|
||||
<pcode>
|
||||
<body><![CDATA[
|
||||
x0 = x0;
|
||||
]]></body>
|
||||
</pcode>
|
||||
</callfixup>
|
||||
@@ -0,0 +1,12 @@
|
||||
<callfixup name="_objc_setProperty_fixup">
|
||||
<target name="_objc_setProperty"/>
|
||||
<target name="_objc_setProperty_atomic"/>
|
||||
<target name="_objc_setProperty_atomic_copy"/>
|
||||
<target name="_objc_setProperty_nonatomic"/>
|
||||
<target name="_objc_setProperty_nonatomic_copy"/>
|
||||
<pcode>
|
||||
<body><![CDATA[
|
||||
*(x0 + x3) = x2;
|
||||
]]></body>
|
||||
</pcode>
|
||||
</callfixup>
|
||||
@@ -0,0 +1,9 @@
|
||||
<callfixup name="_objc_store_fixup">
|
||||
<target name="_objc_storeStrong"/>
|
||||
<target name="_objc_storeWeak"/>
|
||||
<pcode>
|
||||
<body><![CDATA[
|
||||
*x0 = x1;
|
||||
]]></body>
|
||||
</pcode>
|
||||
</callfixup>
|
||||
Reference in New Issue
Block a user