Added support for multidex. Created APK loader to load all DEX files at one time and link method_lookup sections. APK loader uses the manifest file to determine Android version. Fixed a bug loading CDEX from Android 12.

This commit is contained in:
lazybinding-dev 2022-08-30 12:35:52 -04:00
parent cef30890ec
commit 15b59f82c3
13 changed files with 891 additions and 87 deletions

View File

@ -15,11 +15,10 @@
*/
package ghidra.app.util.opinion;
import java.util.*;
import java.util.stream.Collectors;
import java.io.File;
import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;
import org.apache.commons.io.FilenameUtils;
@ -306,7 +305,7 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
LibraryLookupTable.cleanup();
}
private boolean isCreateExportSymbolFiles(List<Option> options) {
protected boolean isCreateExportSymbolFiles(List<Option> options) {
boolean isCreateExportSymbolFiles = IS_CREATE_EXPORT_SYMBOL_FILES_DEFAULT;
if (options != null) {
for (Option option : options) {
@ -319,7 +318,7 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
return isCreateExportSymbolFiles;
}
private boolean isLoadLibraries(List<Option> options) {
protected boolean isLoadLibraries(List<Option> options) {
boolean isLoadLibraries = IS_LOAD_LIBRARIES_DEFAULT;
if (options != null) {
for (Option option : options) {
@ -342,8 +341,10 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
monitor.setMessage(provider.getName());
Address imageBaseAddr = language.getAddressFactory().getDefaultAddressSpace().getAddress(
loadSpec.getDesiredImageBase());
Address imageBaseAddr = language.getAddressFactory()
.getDefaultAddressSpace()
.getAddress(
loadSpec.getDesiredImageBase());
Program program = createProgram(provider, programName, imageBaseAddr, getName(), language,
compilerSpec, consumer);
@ -406,8 +407,10 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
boolean saveIfModified, MessageLog messageLog, TaskMonitor monitor)
throws CancelledException, IOException {
Map<String, Program> progsByName = programs.stream().filter(Objects::nonNull).collect(
Collectors.toMap((p) -> p.getDomainFile().getName(), (p) -> p));
Map<String, Program> progsByName = programs.stream()
.filter(Objects::nonNull)
.collect(
Collectors.toMap((p) -> p.getDomainFile().getName(), (p) -> p));
monitor.initialize(progsByName.size());
for (Program program : progsByName.values()) {
@ -818,8 +821,9 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader
program.getSymbolTable().getGlobalSymbol(les.getName(), ordSym.getAddress());
if (nameSym == null) {
String name = les.getName();
Symbol s = program.getSymbolTable().createLabel(ordSym.getAddress(), name,
program.getGlobalNamespace(), SourceType.IMPORTED);
Symbol s = program.getSymbolTable()
.createLabel(ordSym.getAddress(), name,
program.getGlobalNamespace(), SourceType.IMPORTED);
s.setPrimary();
}
}

View File

@ -27,8 +27,7 @@ import ghidra.app.util.OptionUtils;
import ghidra.app.util.bin.ByteProvider;
import ghidra.app.util.importer.MessageLog;
import ghidra.formats.gfilesystem.FSRL;
import ghidra.framework.model.DomainFolder;
import ghidra.framework.model.DomainObject;
import ghidra.framework.model.*;
import ghidra.framework.store.LockException;
import ghidra.plugin.importer.ProgramMappingService;
import ghidra.program.database.ProgramDB;
@ -106,6 +105,10 @@ public abstract class AbstractProgramLoader implements Loader {
TaskMonitor monitor) throws IOException, CancelledException, InvalidNameException,
DuplicateNameException, VersionException {
if (!isOverrideMainProgramName()) {
folder = ProjectDataUtils.createDomainFolderPath(folder, name);
}
List<DomainObject> results = new ArrayList<>();
if (!loadSpec.isComplete()) {
@ -133,10 +136,14 @@ public abstract class AbstractProgramLoader implements Loader {
continue;
}
// If this is the main imported program, use the given name, otherwise, use the
// internal program name. The first program in the list is the main imported program
String domainFileName =
loadedProgram == programs.get(0) ? name : loadedProgram.getName();
String domainFileName = loadedProgram.getName();
if (isOverrideMainProgramName()) {
// If this is the main imported program, use the given name, otherwise, use the
// internal program name. The first program in the list is the main imported program
if (loadedProgram == programs.get(0)) {
domainFileName = name;
}
}
if (createProgramFile(loadedProgram, folder, domainFileName, messageLog,
monitor)) {
@ -162,6 +169,16 @@ public abstract class AbstractProgramLoader implements Loader {
return results;
}
/**
* Some loaders can return more than one program.
* This method indicates whether the first (or main) program's name
* should be overridden and changed to the imported file name.
* @return true if first program name should be changed
*/
protected boolean isOverrideMainProgramName() {
return true;
}
@Override
public final boolean loadInto(ByteProvider provider, LoadSpec loadSpec, List<Option> options,
MessageLog messageLog, Program program, TaskMonitor monitor)

View File

@ -0,0 +1,296 @@
/* ###
* 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.opinion;
import java.io.IOException;
import java.util.*;
import org.jdom.*;
import org.jdom.input.SAXBuilder;
import ghidra.app.util.Option;
import ghidra.app.util.bin.BinaryReader;
import ghidra.app.util.bin.ByteProvider;
import ghidra.app.util.importer.MessageLog;
import ghidra.app.util.recognizer.PkzipRecognizer;
import ghidra.app.util.recognizer.Recognizer;
import ghidra.file.formats.android.dex.DexHeaderFactory;
import ghidra.file.formats.android.dex.format.DexConstants;
import ghidra.file.formats.android.dex.format.DexHeader;
import ghidra.file.formats.android.multidex.MultiDexLinker;
import ghidra.file.formats.android.xml.AndroidXmlFileSystem;
import ghidra.file.formats.zip.ZipFileSystem;
import ghidra.formats.gfilesystem.FileSystemService;
import ghidra.formats.gfilesystem.GFile;
import ghidra.framework.model.DomainFolder;
import ghidra.program.model.listing.Program;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor;
import ghidra.util.xml.XmlUtilities;
public class ApkLoader extends DexLoader {
@Override
public String getName() {
return "Android APK";
}
@Override
public Collection<LoadSpec> findSupportedLoadSpecs(ByteProvider provider) throws IOException {
if (isZip(provider)) {
try (ZipFileSystem zipFS = openAPK(provider, TaskMonitor.DUMMY)) {
return findLoadSpecs(zipFS, TaskMonitor.DUMMY);
}
catch (Exception e) {
//ignore
}
}
return Collections.emptyList();
}
@Override
protected List<Program> loadProgram(ByteProvider provider, String programName,
DomainFolder programFolder, LoadSpec loadSpec, List<Option> options, MessageLog log,
Object consumer, TaskMonitor monitor) throws CancelledException, IOException {
boolean success = false;
List<Program> programList = new ArrayList<>();
int dexIndex = 1;//DEX file numbering starts at 1
try (ZipFileSystem zipFS = openAPK(provider, monitor)) {
while (!monitor.isCancelled()) {
GFile classesDexFile =
zipFS.lookup("/" + "classes" + (dexIndex == 1 ? "" : dexIndex) + ".dex");
if (classesDexFile == null) {//done
break;
}
monitor.setMessage(
"Loading " + classesDexFile.getName() + " from " + programName + "...");
try (ByteProvider dexProvider =
zipFS.getByteProvider(classesDexFile, monitor)) {
// defer to the super class (DexLoader) to actually load the DEX file
List<Program> program =
super.loadProgram(dexProvider, classesDexFile.getName(), programFolder,
loadSpec, options, log, consumer, monitor);
programList.addAll(program);
}
++dexIndex;
}
success = true;
}
catch (IOException e) {
log.appendException(e);
}
finally {
if (!success) {
release(programList, consumer);
}
}
link(programList, log, monitor);
return programList;
}
@Override
protected boolean isOverrideMainProgramName() {
//preserve the classesX.dex file names...
return false;
}
/**
* Quickly check if the provider is a ZIP file.
* @param provider the byte provider
* @return TRUE if a valid ZIP file.
*/
private boolean isZip(ByteProvider provider) {
try {
Recognizer recog = new PkzipRecognizer();
byte[] bytes = provider.readBytes(0, recog.numberOfBytesRequired());
return recog.recognize(bytes) != null;
}
catch (Exception e) {
//ignore
}
return false;
}
/**
* Inspects the APK to locate the manifest and classes.dex files.
* If found use the manifest to determine the Android version.
* @param zipFS the ZIP file system of the APK file
* @return collection of load specs
* @throws IOException if exception occurs reading the ZIP file.
*/
private Collection<LoadSpec> findLoadSpecs(ZipFileSystem zipFS, TaskMonitor monitor)
throws IOException {
GFile manifestXmlFile = zipFS.lookup("/" + "AndroidManifest.xml");
GFile classesDexFile = zipFS.lookup("/" + "classes.dex");
if (manifestXmlFile != null) {
List<LoadSpec> xmlSpecs = processManifest(zipFS, manifestXmlFile, monitor);
if (!xmlSpecs.isEmpty()) {
//make sure APK contains classes.dex, some are empty
if (classesDexFile != null) {
return xmlSpecs;
}
}
}
//should NOT be else/if
if (classesDexFile != null) {
return processDEX(zipFS, classesDexFile, monitor);
}
return Collections.emptyList();
}
/**
* Reads the Android manifest file and returns the
* Android OS version string. For example, "N" or "S".
* Use this string to select the appropriate Sleigh module
* in the importer.
* @param zipFS the ZIP file system of the APK file
* @param manifestFile the manifest file from the ZIP
* @param monitor the task monitor
* @return Android OS version string. For example, "N" or "S".
* @throws IOException if exception occurs reading the manifest file.
*/
private List<LoadSpec> processManifest(ZipFileSystem zipFS, GFile manifestFile,
TaskMonitor monitor)
throws IOException {
List<LoadSpec> loadSpecs = new ArrayList<>();
monitor.setMessage("Reading Android Manifest ...");
try {
ByteProvider byteProvider =
zipFS.getByteProvider(manifestFile, monitor);
try (AndroidXmlFileSystem xmlFS =
openManifest(manifestFile.getName(), byteProvider, monitor)) {
GFile xmlFile = xmlFS.getPayloadFile();
ByteProvider xmlFileByteProvider = xmlFS.getByteProvider(xmlFile, monitor);
SAXBuilder sax = XmlUtilities.createSecureSAXBuilder(false, false);
Document document = sax.build(xmlFileByteProvider.getInputStream(0));
Element rootElement = document.getRootElement();
Attribute attribute = rootElement.getAttribute("platformBuildVersionName");
String platformBuildVersionName = attribute.getValue();
List<QueryResult> queries =
QueryOpinionService.query(getName(), DexConstants.MACHINE,
platformBuildVersionName);
for (QueryResult result : queries) {
loadSpecs.add(new LoadSpec(this, 0, result));
}
}
}
catch (Exception e) {
//ignore
}
return loadSpecs;
}
/**
* Loads the "classes.dex" file to determine the LoadSpec
* @param zipFS the Android APK file system
* @param file the classes.dex file from the file system
* @param monitor the task monitor
* @return the list of LoadSpec, could be empty list
* @throws IOException if exception occurs reading the classes.dex file.
*/
private List<LoadSpec> processDEX(ZipFileSystem zipFS, GFile file, TaskMonitor monitor)
throws IOException {
monitor.setMessage("Reading classes.dex ...");
List<LoadSpec> loadSpecs = new ArrayList<>();
try {
ByteProvider byteProvider =
zipFS.getByteProvider(file, monitor);
BinaryReader reader = new BinaryReader(byteProvider, true);
DexHeader header = DexHeaderFactory.getDexHeader(reader);
if (DexConstants.DEX_MAGIC_BASE.equals(new String(header.getMagic()))) {
List<QueryResult> queries =
QueryOpinionService.query(getName(), DexConstants.MACHINE, null);
for (QueryResult result : queries) {
loadSpecs.add(new LoadSpec(this, 0, result));
}
if (loadSpecs.isEmpty()) {
loadSpecs.add(new LoadSpec(this, 0, true));
}
}
}
catch (CancelledException e) {
//ignore
}
return loadSpecs;
}
/**
* Opens the Android APK as a ZIP file system.
* @param provider the byte provider
* @param monitor the task monitor
* @return the ZipFileSystem
*/
private ZipFileSystem openAPK(ByteProvider provider, TaskMonitor monitor)
throws IOException, CancelledException {
FileSystemService fsService = FileSystemService.getInstance();
return fsService.mountSpecificFileSystem(provider.getFSRL(), ZipFileSystem.class, monitor);
}
/**
* Opens the Android manifest XML file system.
* @param name the name of the file
* @param provider the byte provider
* @param monitor the task monitor
* @return the AndroidXmlFileSystem
*/
private AndroidXmlFileSystem openManifest(String name, ByteProvider provider,
TaskMonitor monitor) {
AndroidXmlFileSystem xmlFS = new AndroidXmlFileSystem(name, provider);
xmlFS.setFilesystemService(FileSystemService.getInstance());
xmlFS.setFSRL(provider.getFSRL().getFS());
try {
xmlFS.open(monitor);
}
catch (CancelledException | IOException e) {
//ignore
}
return xmlFS;
}
/**
* Links the DEX programs together.
* @param programList the list of DEX files loaded as programs
* @param log the message log
* @param monitor the task monitor
*/
private void link(List<Program> programList, MessageLog log, TaskMonitor monitor) {
MultiDexLinker linker = new MultiDexLinker(programList);
try {
linker.link(monitor);
linker.clear(monitor);
}
catch (Exception e) {
log.appendException(e);
}
}
}

View File

@ -13,14 +13,14 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.file.formats.android.cdex;
package ghidra.app.util.opinion;
import java.io.IOException;
import java.util.*;
import ghidra.app.util.bin.BinaryReader;
import ghidra.app.util.bin.ByteProvider;
import ghidra.app.util.opinion.*;
import ghidra.file.formats.android.cdex.CDexConstants;
import ghidra.file.formats.android.dex.DexHeaderFactory;
import ghidra.file.formats.android.dex.format.DexConstants;
import ghidra.file.formats.android.dex.format.DexHeader;

View File

@ -17,18 +17,35 @@ package ghidra.app.util.opinion;
import java.io.IOException;
import java.io.InputStream;
import java.util.*;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import ghidra.app.util.Option;
import ghidra.app.util.bin.BinaryReader;
import ghidra.app.util.bin.ByteProvider;
import ghidra.app.util.importer.MessageLog;
import ghidra.file.formats.android.dex.DexHeaderFactory;
import ghidra.file.formats.android.dex.format.*;
import ghidra.file.formats.android.dex.format.ClassDataItem;
import ghidra.file.formats.android.dex.format.ClassDefItem;
import ghidra.file.formats.android.dex.format.CodeItem;
import ghidra.file.formats.android.dex.format.DexConstants;
import ghidra.file.formats.android.dex.format.DexHeader;
import ghidra.file.formats.android.dex.format.EncodedMethod;
import ghidra.file.formats.android.dex.format.MethodIDItem;
import ghidra.file.formats.android.dex.util.DexUtil;
import ghidra.framework.model.DomainObject;
import ghidra.program.model.address.Address;
import ghidra.program.model.data.PointerDataType;
import ghidra.program.model.listing.CodeUnit;
import ghidra.program.model.listing.Program;
import ghidra.program.model.mem.MemoryBlock;
import ghidra.program.model.symbol.Namespace;
import ghidra.program.model.symbol.RefType;
import ghidra.program.model.symbol.SourceType;
import ghidra.program.model.symbol.Symbol;
import ghidra.util.exception.InvalidInputException;
import ghidra.util.task.TaskMonitor;
public class DexLoader extends AbstractLibrarySupportLoader {
@ -102,6 +119,8 @@ public class DexLoader extends AbstractLibrarySupportLoader {
createMethods(program, header, item, classDataItem.getVirtualMethods(), monitor,
log);
}
markupMethodLookup(program, header, monitor, log);
}
catch (Exception e) {
log.appendException(e);
@ -157,6 +176,72 @@ public class DexLoader extends AbstractLibrarySupportLoader {
}
}
protected void markupMethodLookup(Program program, DexHeader header, TaskMonitor monitor,
MessageLog log)
throws Exception {
monitor.setMessage("DEX: processing methods");
monitor.setMaximum(header.getMethodIdsSize());
monitor.setProgress(0);
int methodIndex = 0;
for (MethodIDItem item : header.getMethods()) {
monitor.checkCanceled();
monitor.incrementProgress(1);
StringBuilder builder = new StringBuilder();
builder.append("Method Index: 0x" + Integer.toHexString(methodIndex) + "\n");
builder.append(
"Class: " + DexUtil.convertTypeIndexToString(header, item.getClassIndex()) + "\n");
builder.append("Prototype: " +
DexUtil.convertPrototypeIndexToString(header, item.getProtoIndex()) + "\n");
builder.append("Name: " + DexUtil.convertToString(header, item.getNameIndex()) + "\n");
Address methodIndexAddress = DexUtil.toLookupAddress(program, methodIndex);
if (program.getMemory().getInt(methodIndexAddress) == -1) {
program.getListing()
.setComment(methodIndexAddress, CodeUnit.PLATE_COMMENT, builder.toString());
// Add placeholder symbol for external functions
String methodName = DexUtil.convertToString(header, item.getNameIndex());
String className = DexUtil.convertTypeIndexToString(header, item.getClassIndex());
Namespace classNameSpace =
DexUtil.createNameSpaceFromMangledClassName(program, className);
if (classNameSpace != null) {
Address externalAddress = DexUtil.toLookupAddress(program, methodIndex);
Symbol methodSymbol =
createMethodSymbol(program, externalAddress, methodName, classNameSpace,
log);
if (methodSymbol != null) {
String externalName = methodSymbol.getName(true);
program.getReferenceManager()
.addExternalReference(methodIndexAddress,
"EXTERNAL.dex-" + methodIndex, externalName, null,
SourceType.ANALYSIS, 0, RefType.DATA);
}
}
}
program.getListing().createData(methodIndexAddress, new PointerDataType());
++methodIndex;
}
}
private Symbol createMethodSymbol(Program program, Address methodAddress, String methodName,
Namespace classNameSpace, MessageLog log) {
program.getSymbolTable().addExternalEntryPoint(methodAddress);
try {
return program.getSymbolTable()
.createLabel(methodAddress, methodName, classNameSpace, SourceType.ANALYSIS);
}
catch (InvalidInputException e) {
log.appendException(e);
return null;
}
}
protected Address toAddr(Program program, long offset) {
return program.getAddressFactory().getDefaultAddressSpace().getAddress(offset);
}
@ -172,4 +257,27 @@ public class DexLoader extends AbstractLibrarySupportLoader {
protected String getMonitorMessageSecondary() {
return "DEX Loader: creating method byte code";
}
@Override
public List<Option> getDefaultOptions(ByteProvider provider, LoadSpec loadSpec,
DomainObject domainObject, boolean loadIntoProgram) {
return Collections.emptyList();
}
@Override
public String validateOptions(ByteProvider provider, LoadSpec loadSpec, List<Option> options,
Program program) {
return null;
}
@Override
protected boolean isCreateExportSymbolFiles(List<Option> options) {
return false;
}
@Override
protected boolean isLoadLibraries(List<Option> options) {
return false;
}
}

View File

@ -432,6 +432,9 @@ public class DexHeaderFormatMarkup {
if (item.getStaticValuesOffset() > 0) {
EncodedArrayItem staticValues = item.getStaticValues();
if (staticValues == null) {
return;
}
Address staticAddress =
baseAddress.add(DexUtil.adjustOffset(item.getStaticValuesOffset(), header));
DataType staticDataType = staticValues.toDataType();
@ -454,6 +457,9 @@ public class DexHeaderFormatMarkup {
if (item.getClassDataOffset() > 0) {
ClassDataItem classDataItem = item.getClassDataItem();
if (classDataItem == null) {
return;
}
Address classDataAddress =
baseAddress.add(DexUtil.adjustOffset(item.getClassDataOffset(), header));
DataType classDataDataType = classDataItem.toDataType();
@ -656,6 +662,9 @@ public class DexHeaderFormatMarkup {
if (item.getAnnotationsOffset() > 0) {
AnnotationsDirectoryItem annotationsDirectoryItem = item.getAnnotationsDirectoryItem();
if (annotationsDirectoryItem == null) {
return;
}
Address annotationsAddress =
baseAddress.add(DexUtil.adjustOffset(item.getAnnotationsOffset(), header));
DataType annotationsDataType = annotationsDirectoryItem.toDataType();
@ -728,6 +737,9 @@ public class DexHeaderFormatMarkup {
if (item.getInterfacesOffset() > 0) {
TypeList interfaces = item.getInterfaces();
if (interfaces == null) {
return;
}
Address interfaceAddress =
baseAddress.add(DexUtil.adjustOffset(item.getInterfacesOffset(), header));
DataType interfaceDataType = interfaces.toDataType();
@ -791,29 +803,6 @@ public class DexHeaderFormatMarkup {
api.setPlateComment(address, builder.toString());
Address methodIndexAddress = DexUtil.toLookupAddress(program, methodIndex);
if (program.getMemory().getInt(methodIndexAddress) == -1) {
// Add placeholder symbol for external functions
String methodName = DexUtil.convertToString(header, item.getNameIndex());
String className = DexUtil.convertTypeIndexToString(header, item.getClassIndex());
Namespace classNameSpace =
DexUtil.createNameSpaceFromMangledClassName(program, className);
if (classNameSpace != null) {
Address externalAddress = DexUtil.toLookupAddress(program, methodIndex);
Symbol methodSymbol =
createMethodSymbol(externalAddress, methodName, classNameSpace, log);
if (methodSymbol != null) {
String externalName = methodSymbol.getName(true);
program.getReferenceManager()
.addExternalReference(methodIndexAddress,
"EXTERNAL.dex-" + methodIndex, externalName, null,
SourceType.ANALYSIS, 0, RefType.DATA);
}
}
}
api.createData(methodIndexAddress, new PointerDataType());
++methodIndex;
address = address.add(dataType.getLength());
@ -876,24 +865,27 @@ public class DexHeaderFormatMarkup {
if (item.getParametersOffset() > 0) {
builder.append("Parameters: " + "\n");
TypeList parameters = item.getParameters();
for (TypeItem parameter : parameters.getItems()) {
monitor.checkCanceled();
builder.append(
DexUtil.convertTypeIndexToString(header, parameter.getType()) + " ");
if (parameters != null) {
for (TypeItem parameter : parameters.getItems()) {
monitor.checkCanceled();
builder.append(
DexUtil.convertTypeIndexToString(header, parameter.getType()) + " ");
}
DataType parametersDT = parameters.toDataType();
Address parametersAddress =
baseAddress.add(DexUtil.adjustOffset(item.getParametersOffset(), header));
api.createData(parametersAddress, parametersDT);
builder.append("\nParameters Address: " + parametersAddress);
builder.append("\n");
// add reference to the "parametersOffset" field
api.createMemoryReference(data.getComponent(2), parametersAddress,
RefType.DATA);
}
DataType parametersDT = parameters.toDataType();
Address parametersAddress =
baseAddress.add(DexUtil.adjustOffset(item.getParametersOffset(), header));
api.createData(parametersAddress, parametersDT);
builder.append("\nParameters Address: " + parametersAddress);
builder.append("\n");
// add reference to the "parametersOffset" field
api.createMemoryReference(data.getComponent(2), parametersAddress, RefType.DATA);
}
api.setPlateComment(address, builder.toString());
@ -970,6 +962,10 @@ public class DexHeaderFormatMarkup {
// markup string data items
Address stringDataAddress =
baseAddress.add(DexUtil.adjustOffset(item.getStringDataOffset(), header));
if (!program.getMemory().contains(stringDataAddress)) {
continue;
}
StringDataItem stringDataItem = item.getStringDataItem();
if (stringDataItem == null) {

View File

@ -129,11 +129,15 @@ class DexHeaderFragmentManager {
if (!isCreateFragments) {
return;
}
monitor.setMessage("DEX: creating fragments");
if (header.getDataSize() > 0) {
Address start = baseAddress.add(header.getDataOffset());
api.createFragment("data", start, header.getDataSize());
try {
api.createFragment("data", start, header.getDataSize());
}
catch (NotFoundException e) {
//ignore, case for incomplete CDEX
}
}
}

View File

@ -54,8 +54,11 @@ public class ClassDefItem implements StructConverter {
if (interfacesOffset > 0) {
long oldIndex = reader.getPointerIndex();
try {
reader.setPointerIndex(DexUtil.adjustOffset(interfacesOffset, dexHeader));
_interfaces = new TypeList(reader);
int adjustOffset = DexUtil.adjustOffset(interfacesOffset, dexHeader);
if (reader.isValidIndex(adjustOffset)) {
reader.setPointerIndex(adjustOffset);
_interfaces = new TypeList(reader);
}
}
finally {
reader.setPointerIndex(oldIndex);
@ -65,8 +68,11 @@ public class ClassDefItem implements StructConverter {
if (annotationsOffset > 0) {
long oldIndex = reader.getPointerIndex();
try {
reader.setPointerIndex(DexUtil.adjustOffset(annotationsOffset, dexHeader));
_annotationsDirectoryItem = new AnnotationsDirectoryItem(reader, dexHeader);
int adjustOffset = DexUtil.adjustOffset(annotationsOffset, dexHeader);
if (reader.isValidIndex(adjustOffset)) {
reader.setPointerIndex(adjustOffset);
_annotationsDirectoryItem = new AnnotationsDirectoryItem(reader, dexHeader);
}
}
finally {
reader.setPointerIndex(oldIndex);
@ -76,8 +82,11 @@ public class ClassDefItem implements StructConverter {
if (classDataOffset > 0) {
long oldIndex = reader.getPointerIndex();
try {
reader.setPointerIndex(DexUtil.adjustOffset(classDataOffset, dexHeader));
_classDataItem = new ClassDataItem(reader, dexHeader);
int adjustOffset = DexUtil.adjustOffset(classDataOffset, dexHeader);
if (reader.isValidIndex(adjustOffset)) {
reader.setPointerIndex(adjustOffset);
_classDataItem = new ClassDataItem(reader, dexHeader);
}
}
finally {
reader.setPointerIndex(oldIndex);
@ -87,8 +96,11 @@ public class ClassDefItem implements StructConverter {
if (staticValuesOffset > 0) {
long oldIndex = reader.getPointerIndex();
try {
reader.setPointerIndex(DexUtil.adjustOffset(staticValuesOffset, dexHeader));
_staticValues = new EncodedArrayItem(reader);
int adjustOffset = DexUtil.adjustOffset(staticValuesOffset, dexHeader);
if (reader.isValidIndex(adjustOffset)) {
reader.setPointerIndex(adjustOffset);
_staticValues = new EncodedArrayItem(reader);
}
}
finally {
reader.setPointerIndex(oldIndex);
@ -170,10 +182,12 @@ public class ClassDefItem implements StructConverter {
if (getInterfacesOffset() > 0) {
builder.append("Interfaces: " + "\n");
TypeList interfaces = getInterfaces();
for (TypeItem type : interfaces.getItems()) {
monitor.checkCanceled();
builder.append(
"\t" + DexUtil.convertTypeIndexToString(header, type.getType()) + "\n");
if (interfaces != null) {
for (TypeItem type : interfaces.getItems()) {
monitor.checkCanceled();
builder.append(
"\t" + DexUtil.convertTypeIndexToString(header, type.getType()) + "\n");
}
}
}

View File

@ -211,9 +211,9 @@ public class CodeItem implements StructConverter {
structure.add(DWORD, "debug_info_off", null);
structure.add(DWORD, "insns_size", null);
structure.add(new ArrayDataType(WORD, instructionSize, WORD.getLength()), "insns", null);
if (hasPadding()) {
structure.add(WORD, "padding", null);
}
// if (hasPadding()) {
// structure.add(WORD, "padding", null);
// }
structure.setCategoryPath(new CategoryPath("/dex/code_item"));
return structure;
}

View File

@ -124,8 +124,9 @@ public class DexHeader implements StructConverter {
}
parsed = true;
reader.setPointerIndex(DexUtil.adjustOffset(mapOffset, this));
if (mapOffset > 0) {
if (mapOffset > 0 && reader.isValidIndex(mapOffset)) {
int adjustedMapOffset = DexUtil.adjustOffset(mapOffset, this);
reader.setPointerIndex(adjustedMapOffset);
mapList = new MapList(reader);
}
@ -154,9 +155,11 @@ public class DexHeader implements StructConverter {
methods.add(new MethodIDItem(reader));
}
reader.setPointerIndex(classDefsIdsOffset);
for (int i = 0; i < classDefsIdsSize; ++i) {
classDefs.add(new ClassDefItem(reader, this));
if (reader.isValidIndex(classDefsIdsOffset)) {
reader.setPointerIndex(classDefsIdsOffset);
for (int i = 0; i < classDefsIdsSize; ++i) {
classDefs.add(new ClassDefItem(reader, this));
}
}
}

View File

@ -40,8 +40,12 @@ public class PrototypesIDItem implements StructConverter {
if (parametersOffset > 0) {
long oldIndex = reader.getPointerIndex();
try {
reader.setPointerIndex(DexUtil.adjustOffset(parametersOffset, dexHeader));
_parameters = new TypeList(reader);
//starting in Android 12, CDEX files are incomplete
int adjustedParametersOffset = DexUtil.adjustOffset(parametersOffset, dexHeader);
if (reader.isValidIndex(adjustedParametersOffset)) {
reader.setPointerIndex(adjustedParametersOffset);
_parameters = new TypeList(reader);
}
}
finally {
reader.setPointerIndex(oldIndex);

View File

@ -0,0 +1,301 @@
/* ###
* 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.file.formats.android.multidex;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import ghidra.file.formats.android.dex.DexHeaderFactory;
import ghidra.file.formats.android.dex.format.DexHeader;
import ghidra.file.formats.android.dex.format.MethodIDItem;
import ghidra.file.formats.android.dex.util.DexUtil;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressSet;
import ghidra.program.model.listing.Data;
import ghidra.program.model.listing.DataIterator;
import ghidra.program.model.listing.Program;
import ghidra.program.model.mem.MemoryAccessException;
import ghidra.program.model.mem.MemoryBlock;
import ghidra.program.model.symbol.ExternalLocation;
import ghidra.program.model.symbol.ExternalManager;
import ghidra.program.model.symbol.ExternalReference;
import ghidra.program.model.symbol.RefType;
import ghidra.program.model.symbol.Reference;
import ghidra.program.model.symbol.ReferenceManager;
import ghidra.program.model.symbol.SourceType;
import ghidra.util.exception.CancelledException;
import ghidra.util.exception.DuplicateNameException;
import ghidra.util.exception.InvalidInputException;
import ghidra.util.task.TaskMonitor;
/**
* Links multidex APKs together
* via the "method_lookup" section using
* external references.
*/
public final class MultiDexLinker {
private List<Program> programs;
private Map<Program, DexHeader> dexMap = new HashMap<>();
private Map<DexHeader, Map<ClassMethodPrototype, Integer>> cmpMap = new HashMap<>();
private Map<Program, List<Address>> changeMap = new HashMap<>();
private class ClassMethodPrototype {
private final String className;
private final String methodName;
private final String prototype;
ClassMethodPrototype(String className, String methodName, String prototype) {
this.className = className;
this.methodName = methodName;
this.prototype = prototype;
}
@Override
public boolean equals(Object obj) {
if (obj instanceof ClassMethodPrototype) {
ClassMethodPrototype cmp = (ClassMethodPrototype) obj;
return cmp.className.equals(className) &&
cmp.methodName.equals(methodName) &&
cmp.prototype.equals(prototype);
}
return super.equals(obj);
}
@Override
public int hashCode() {
return className.hashCode() + methodName.hashCode() + prototype.hashCode();
}
}
private class ProgramAddress {
private final Program program;
private final Address address;
ProgramAddress(Program program, Address address) {
this.program = program;
this.address = address;
}
@Override
public boolean equals(Object obj) {
if (obj instanceof ProgramAddress) {
ProgramAddress pa = (ProgramAddress) obj;
return this.program.equals(pa.program) && this.address.equals(pa.address);
}
return super.equals(obj);
}
@Override
public int hashCode() {
return program.hashCode() + address.hashCode();
}
}
public MultiDexLinker(List<Program> programs) {
this.programs = new ArrayList<>(programs);//create copy of list
}
public void link(TaskMonitor monitor) throws CancelledException, IOException,
MemoryAccessException, InvalidInputException, DuplicateNameException {
Objects.requireNonNull(monitor);
cacheHeaderInfo(monitor);
linkPrograms(monitor);
}
public void clear(TaskMonitor monitor) throws CancelledException {
Objects.requireNonNull(monitor);
programs.clear();
dexMap.clear();
for (DexHeader header : cmpMap.keySet()) {
monitor.checkCanceled();
cmpMap.get(header).clear();
}
cmpMap.clear();
changeMap.clear();
}
public List<Address> getChangeList(Program program) {
return Objects.requireNonNull(changeMap.get(program));
}
private void linkPrograms(TaskMonitor monitor) throws CancelledException, MemoryAccessException,
InvalidInputException, DuplicateNameException, IOException {
monitor.setMaximum(programs.size());
monitor.setProgress(1);
for (Program program : programs) {
monitor.checkCanceled();
monitor.incrementProgress(1);
monitor.setMessage(program.getName());
DexHeader dexHeader = dexMap.get(program);
List<Address> changeList = new ArrayList<>();
changeMap.put(program, changeList);
int transaction = program.startTransaction("multi-dex");
try {
ReferenceManager referenceManager = program.getReferenceManager();
ExternalManager externalManager = program.getExternalManager();
MemoryBlock methodLookupBlock = program.getMemory().getBlock("method_lookup");
AddressSet methodLookupRange =
new AddressSet(methodLookupBlock.getStart(), methodLookupBlock.getEnd());
DataIterator dataIterator =
program.getListing().getDefinedData(methodLookupRange, true);
while (dataIterator.hasNext()) {
Data data = dataIterator.next();
monitor.checkCanceled();
monitor.setMessage(program.getName() + " " + data.getMinAddress());
if (program.getMemory().getInt(data.getMinAddress()) != -1) {
continue;//internally linked, ignore
}
if (isExternalReferenceResolved(program, data, monitor)) {
continue;
}
int methodIndex =
(int) data.getMinAddress().subtract(methodLookupBlock.getStart()) / 4;
MethodIDItem methodIDItem = dexHeader.getMethods().get(methodIndex);
String className =
DexUtil.convertTypeIndexToString(dexHeader, methodIDItem.getClassIndex());
String prototype = DexUtil.convertPrototypeIndexToString(dexHeader,
methodIDItem.getProtoIndex());
String methodName =
DexUtil.convertToString(dexHeader, methodIDItem.getNameIndex());
ProgramAddress pa =
findInOtherProgram(program, className, prototype, methodName, monitor);
if (pa == null) {
continue;
}
if (externalManager.getExternalLibraryPath(pa.program.getName()) == null) {
externalManager.setExternalPath(pa.program.getName(),
pa.program.getDomainFile().getPathname(), true);
}
referenceManager.addExternalReference(data.getMinAddress(),
pa.program.getName(), null, pa.address, SourceType.ANALYSIS, 0,
RefType.EXTERNAL_REF);
changeList.add(data.getMinAddress());
}
}
finally {
program.endTransaction(transaction, true);
}
}
}
private ProgramAddress findInOtherProgram(Program program, String className,
String prototype, String methodName, TaskMonitor monitor)
throws CancelledException, IOException, MemoryAccessException {
ClassMethodPrototype cmp =
new ClassMethodPrototype(className, methodName, prototype);
for (Program otherProgram : programs) {
monitor.checkCanceled();
if (otherProgram.equals(program)) {
continue;
}
DexHeader otherDexHeader = dexMap.get(otherProgram);
Map<ClassMethodPrototype, Integer> map = cmpMap.get(otherDexHeader);
Integer otherMethodIndex = map.get(cmp);
if (otherMethodIndex != null) {
Address otherAddress = DexUtil.toLookupAddress(program, otherMethodIndex);
if (otherProgram.getMemory().getInt(otherAddress) != -1) {
return new ProgramAddress(otherProgram, otherAddress);
}
}
}
return null;
}
private void cacheHeaderInfo(TaskMonitor monitor) throws CancelledException, IOException {
monitor.setMaximum(programs.size());
monitor.setProgress(1);
for (Program program : programs) {
monitor.checkCanceled();
monitor.incrementProgress(1);
monitor.setMessage("Caching DEX header for " + program.getName() + "...");
DexHeader dexHeader = DexHeaderFactory.getDexHeader(program);
dexMap.put(program, dexHeader);
Map<ClassMethodPrototype, Integer> map = new HashMap<>();
cmpMap.put(dexHeader, map);
int index = 0;
for (MethodIDItem item : dexHeader.getMethods()) {
monitor.checkCanceled();
String className =
DexUtil.convertTypeIndexToString(dexHeader, item.getClassIndex());
String methodName = DexUtil.convertToString(dexHeader, item.getNameIndex());
String prototype =
DexUtil.convertPrototypeIndexToString(dexHeader, item.getProtoIndex());
ClassMethodPrototype cmp =
new ClassMethodPrototype(className, methodName, prototype);
map.put(cmp, index);
++index;
}
}
}
private boolean isExternalReferenceResolved(Program program, Data data, TaskMonitor monitor)
throws CancelledException {
ExternalManager externalManager = program.getExternalManager();
Reference[] referencesFrom = data.getReferencesFrom();
for (Reference reference : referencesFrom) {
monitor.checkCanceled();
if (reference instanceof ExternalReference) {
ExternalReference extref = (ExternalReference) reference;
ExternalLocation externalLocation = extref.getExternalLocation();
if (externalManager.getExternalLibraryPath(
externalLocation.getLibraryName()) != null) {
return true;//already resolved
}
}
}
return false;
}
}

View File

@ -1,8 +1,65 @@
<opinions>
<constraint loader="Dalvik Executable (DEX)" compilerSpecID="default">
<constraint primary="1" processor="Dalvik" endian="little" size="32" />
<constraint primary="1" processor="Dalvik" endian="little" size="32" />
</constraint>
<constraint loader="Compact Dalvik Executable (CDEX)" compilerSpecID="default">
<constraint primary="1" processor="Dalvik" endian="little" size="32" />
<constraint primary="1" processor="Dalvik" endian="little" size="32" />
</constraint>
<constraint loader="Android APK" compilerSpecID="default">
<constraint primary="1" processor="Dalvik" endian="little" size="32" />
</constraint>
<constraint loader="Android APK" compilerSpecID="default">
<constraint primary="1" secondary="K" processor="Dalvik" endian="little" size="32" variant="DEX KitKat" />
</constraint>
<constraint loader="Android APK" compilerSpecID="default">
<constraint primary="1" secondary="4" processor="Dalvik" endian="little" size="32" variant="DEX KitKat" />
</constraint>
<constraint loader="Android APK" compilerSpecID="default">
<constraint primary="1" secondary="L" processor="Dalvik" endian="little" size="32" variant="DEX Lollipop" />
</constraint>
<constraint loader="Android APK" compilerSpecID="default">
<constraint primary="1" secondary="5" processor="Dalvik" endian="little" size="32" variant="DEX Lollipop" />
</constraint>
<constraint loader="Android APK" compilerSpecID="default">
<constraint primary="1" secondary="M" processor="Dalvik" endian="little" size="32" variant="DEX Marshmallow" />
</constraint>
<constraint loader="Android APK" compilerSpecID="default">
<constraint primary="1" secondary="6" processor="Dalvik" endian="little" size="32" variant="DEX Marshmallow" />
</constraint>
<constraint loader="Android APK" compilerSpecID="default">
<constraint primary="1" secondary="N" processor="Dalvik" endian="little" size="32" variant="DEX Nougat" />
</constraint>
<constraint loader="Android APK" compilerSpecID="default">
<constraint primary="1" secondary="7" processor="Dalvik" endian="little" size="32" variant="DEX Nougat" />
</constraint>
<constraint loader="Android APK" compilerSpecID="default">
<constraint primary="1" secondary="O" processor="Dalvik" endian="little" size="32" variant="DEX Oreo" />
</constraint>
<constraint loader="Android APK" compilerSpecID="default">
<constraint primary="1" secondary="8" processor="Dalvik" endian="little" size="32" variant="DEX Oreo" />
</constraint>
<constraint loader="Android APK" compilerSpecID="default">
<constraint primary="1" secondary="P" processor="Dalvik" endian="little" size="32" variant="DEX Pie" />
</constraint>
<constraint loader="Android APK" compilerSpecID="default">
<constraint primary="1" secondary="9" processor="Dalvik" endian="little" size="32" variant="DEX Pie" />
</constraint>
<constraint loader="Android APK" compilerSpecID="default">
<constraint primary="1" secondary="Q" processor="Dalvik" endian="little" size="32" variant="DEX Android10" />
</constraint>
<constraint loader="Android APK" compilerSpecID="default">
<constraint primary="1" secondary="10" processor="Dalvik" endian="little" size="32" variant="DEX Android10" />
</constraint>
<constraint loader="Android APK" compilerSpecID="default">
<constraint primary="1" secondary="R" processor="Dalvik" endian="little" size="32" variant="DEX Android11" />
</constraint>
<constraint loader="Android APK" compilerSpecID="default">
<constraint primary="1" secondary="11" processor="Dalvik" endian="little" size="32" variant="DEX Android11" />
</constraint>
<constraint loader="Android APK" compilerSpecID="default">
<constraint primary="1" secondary="S" processor="Dalvik" endian="little" size="32" variant="DEX Android12" />
</constraint>
<constraint loader="Android APK" compilerSpecID="default">
<constraint primary="1" secondary="12" processor="Dalvik" endian="little" size="32" variant="DEX Android12" />
</constraint>
</opinions>