Candidate release of source code.

This commit is contained in:
Dan 2019-03-26 13:45:32 -04:00
parent db81e6b3b0
commit 79d8f164f8
12449 changed files with 2800756 additions and 16 deletions

View file

@ -0,0 +1,4 @@
<?xml version='1.0' encoding='ISO-8859-1' ?>
<tocroot>
</tocroot>

View file

@ -0,0 +1,51 @@
/* ###
* 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.
*/
/*
WARNING!
This file is copied to all help directories. If you change this file, you must copy it
to each src/main/help/help/shared directory.
Java Help Note: JavaHelp does not accept sizes (like in 'margin-top') in anything but
px (pixel) or with no type marking.
*/
body { margin-bottom: 50px; margin-left: 10px; margin-right: 10px;} /* some padding to improve readability */
li { font-family:times new roman; font-size:14pt; }
h1 { color:#000080; font-family:times new roman; font-size:36pt; font-style:italic; font-weight:bold; text-align:center; }
h2 { margin: 10px; margin-top: 20px; color:#984c4c; font-family:times new roman; font-size:18pt; font-weight:bold; }
h3 { margin-left: 10px; margin-top: 20px; color:#0000ff; font-family:times new roman; font-size:14pt; font-weight:bold; }
h4 { margin-left: 10px; font-family:times new roman; font-size:14pt; font-style:italic; }
/*
P tag code. Most of the help files nest P tags inside of blockquote tags (the was the
way it had been done in the beginning). The net effect is that the text is indented. In
modern HTML we would use CSS to do this. We need to support the Ghidra P tags, nested in
blockquote tags, as well as naked P tags. The following two lines accomplish this. Note
that the 'blockquote p' definition will inherit from the first 'p' definition.
*/
p { margin-left: 40px; font-family:times new roman; font-size:14pt; }
blockquote p { margin-left: 10px; }
p.providedbyplugin { color:#7f7f7f; margin-left: 10px; font-size:14pt; margin-top:100px }
p.ProvidedByPlugin { color:#7f7f7f; margin-left: 10px; font-size:14pt; margin-top:100px }
p.relatedtopic { color:#800080; margin-left: 10px; font-size:14pt; }
p.RelatedTopic { color:#800080; margin-left: 10px; font-size:14pt; }
td { font-family:times new roman; font-size:14pt; vertical-align: top; }
th { font-family:times new roman; font-size:14pt; font-weight:bold; background-color: #EDF3FE; }
code { color: black; font-family: courier new; font-size: 14pt; }

View file

@ -0,0 +1,74 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<HTML>
<HEAD>
<META name="generator" content=
"HTML Tidy for Java (vers. 2009-12-01), see jtidy.sourceforge.net">
<LINK rel="stylesheet" type="text/css" href="../../shared/Frontpage.css">
<TITLE>File Formats</TITLE>
</HEAD>
<BODY>
<H1>File Format Actions</H1>
<H2><A name="FileFormatsIntroduction"></A>Introduction</H2>
<BLOCKQUOTE>
<P>The file formats plugin adds the following actions to the
<A href="help/topics/FileSystemBrowserPlugin/FileSystemBrowserPlugin.html">File System
Browser</A></P>
</BLOCKQUOTE>
<H2>Right-click Context Menu Actions</H2>
<BLOCKQUOTE>
<H3><A name="FSB_Export_Eclipse_Project"></A>Export Eclipse Project</H3>
<BLOCKQUOTE>
<P>Given a selected Android application package (APK) file, this will create an Eclipse
project for that APK. It will convert the DEX file into a JAR file, then JAD the entire
file.</P>
</BLOCKQUOTE>
<H3><A name="FSB_Load_iOS_Kernel"></A>Load iOS Kernel</H3>
<BLOCKQUOTE>
<P>Given a selected iOS kernel cache file, this will load the entire kernel into the
project. After the kernel is opened, you should run the
<CODE>iOS_AnalyzeAllOpenKextsScript</CODE> script.</P>
</BLOCKQUOTE>
<H3><A name="FSB_Decompile_JAR"></A>Decompile JAR</H3>
<BLOCKQUOTE>
<P>Given a selected JAR file, this will JAD the entire contents and create the source.</P>
</BLOCKQUOTE>
<H3><A name="FSB_Create_Crypto_Key_Template"></A>Create Crypto Key Template File</H3>
<BLOCKQUOTE>
<P>Builds a template for the crypto keys.<BR>
<BR>
This is important when viewing iOS firmware. See the
<CODE>iPhoneWikiFirmwareKeyParserScript</CODE> to read KEYs and IVs directly from the
website pages into this template file.</P>
</BLOCKQUOTE>
<H2><A name="FileSystemBrowserKnownIssues"></A>Known issues</H2>
<BLOCKQUOTE>
<H3>Strong Crypto Support</H3>
<BLOCKQUOTE>
<P>Your Java JVM install may not have support for strong crypto currently installed.<BR>
<BR>
In order to fix this issue, you must install Oracle's "Java Cryptography Extension (JCE)
Unlimited Strength Jurisdiction Policy Files"<BR>
<BR>
</P>
</BLOCKQUOTE>
</BLOCKQUOTE>
</BODY>
</HTML>

View file

@ -0,0 +1,153 @@
/* ###
* 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.io.InputStream;
import java.util.*;
import ghidra.app.util.Option;
import ghidra.app.util.bin.BinaryReader;
import ghidra.app.util.bin.ByteProvider;
import ghidra.app.util.importer.MemoryConflictHandler;
import ghidra.app.util.importer.MessageLog;
import ghidra.file.formats.android.dex.format.*;
import ghidra.file.formats.android.dex.util.DexUtil;
import ghidra.program.model.address.Address;
import ghidra.program.model.listing.Program;
import ghidra.program.model.mem.MemoryBlock;
import ghidra.util.task.TaskMonitor;
public class DexLoader extends AbstractLibrarySupportLoader {
public DexLoader() {
}
@Override
public String getName( ) {
return "Dalvik Executable (DEX)";
}
@Override
public Collection<LoadSpec> findSupportedLoadSpecs(ByteProvider provider) throws IOException {
List<LoadSpec> loadSpecs = new ArrayList<>();
BinaryReader reader = new BinaryReader(provider, true);
try {
DexHeader header = new DexHeader(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 (Exception e) {
//ignore
}
return loadSpecs;
}
@Override
public void load(ByteProvider provider, LoadSpec loadSpec, List<Option> options,
Program program, MemoryConflictHandler handler, TaskMonitor monitor, MessageLog log)
throws IOException {
monitor.setMessage( "DEX Loader: creating dex memory" );
try {
Address start = program.getAddressFactory().getDefaultAddressSpace().getAddress( 0x0 );
long length = provider.length();
try (InputStream inputStream = provider.getInputStream(0)) {
program.getMemory().createInitializedBlock(".dex", start, inputStream, length,
monitor, false);
}
BinaryReader reader = new BinaryReader( provider, true );
DexHeader header = new DexHeader( reader );
monitor.setMessage( "DEX Loader: creating method byte code" );
createMethodLookupMemoryBlock( program, monitor );
createMethodByteCodeBlock( program, length, monitor);
for ( ClassDefItem item : header.getClassDefs( ) ) {
monitor.checkCanceled( );
ClassDataItem classDataItem = item.getClassDataItem( );
if ( classDataItem == null ) {
continue;
}
createMethods( program, header, item, classDataItem.getDirectMethods( ), monitor, log );
createMethods( program, header, item, classDataItem.getVirtualMethods( ), monitor, log );
}
}
catch ( Exception e) {
log.appendException( e );
}
}
private void createMethodByteCodeBlock(Program program, long length, TaskMonitor monitor) throws Exception {
Address address = toAddr( program, DexUtil.METHOD_ADDRESS );
MemoryBlock block = program.getMemory( ).createInitializedBlock( "method_bytecode", address, length, (byte) 0xff, monitor, false );
block.setRead( true );
block.setWrite( false );
block.setExecute( true );
}
private void createMethodLookupMemoryBlock(Program program, TaskMonitor monitor) throws Exception {
Address address = toAddr( program, DexUtil.LOOKUP_ADDRESS );
MemoryBlock block = program.getMemory( ).createInitializedBlock( "method_lookup", address, DexUtil.MAX_METHOD_LENGTH, (byte) 0xff, monitor, false );
block.setRead( true );
block.setWrite( false );
block.setExecute( false );
}
private void createMethods( Program program, DexHeader header, ClassDefItem item, List< EncodedMethod > methods, TaskMonitor monitor, MessageLog log ) throws Exception {
for ( int i = 0 ; i < methods.size( ) ; ++i ) {
monitor.checkCanceled( );
EncodedMethod encodedMethod = methods.get( i );
CodeItem codeItem = encodedMethod.getCodeItem( );
Address methodIndexAddress = DexUtil.toLookupAddress( program, encodedMethod.getMethodIndex( ) );
if ( codeItem == null ) {//external method
//TODO
}
else {
Address methodAddress = toAddr( program, DexUtil.METHOD_ADDRESS + encodedMethod.getCodeOffset( ) );
byte [] instructionBytes = codeItem.getInstructionBytes( );
program.getMemory( ).setBytes( methodAddress, instructionBytes );
program.getMemory( ).setInt( methodIndexAddress, (int) methodAddress.getOffset( ) );
}
}
}
private Address toAddr( Program program, long offset ) {
return program.getAddressFactory( ).getDefaultAddressSpace( ).getAddress( offset );
}
}

View file

@ -0,0 +1,231 @@
/* ###
* 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.analyzers;
import ghidra.app.cmd.comments.SetCommentCmd;
import ghidra.app.cmd.data.CreateDataCmd;
import ghidra.app.cmd.data.CreateStringCmd;
import ghidra.app.cmd.function.CreateFunctionCmd;
import ghidra.app.services.*;
import ghidra.app.util.importer.MessageLog;
import ghidra.docking.settings.*;
import ghidra.framework.options.Options;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressSetView;
import ghidra.program.model.data.DataType;
import ghidra.program.model.data.StringDataType;
import ghidra.program.model.listing.*;
import ghidra.program.model.mem.MemoryAccessException;
import ghidra.util.exception.CancelledException;
import ghidra.util.exception.NotEmptyException;
import ghidra.util.task.TaskMonitor;
public abstract class FileFormatAnalyzer implements Analyzer {
@Override
public AnalyzerType getAnalysisType() {
return AnalyzerType.BYTE_ANALYZER;
}
@Override
public AnalysisPriority getPriority() {
return AnalysisPriority.FORMAT_ANALYSIS;
}
@Override
final public boolean added(Program program, AddressSetView set, TaskMonitor monitor,
MessageLog log) throws CancelledException {
try {
return analyze(program, set, monitor, log);
}
catch (Exception e) {
log.appendException(e);
}
return false;
}
@Override
final public void analysisEnded(Program program) {
// do nothing
}
@Override
final public void registerOptions(Options options, Program program) {
// do nothing
}
@Override
final public void optionsChanged(Options options, Program program) {
// do nothing
}
@Override
final public boolean removed(Program program, AddressSetView set, TaskMonitor monitor,
MessageLog log) throws CancelledException {
return false;
}
@Override
final public boolean supportsOneTimeAnalysis() {
return false;
}
public abstract boolean analyze(Program program, AddressSetView set, TaskMonitor monitor,
MessageLog log) throws Exception;
protected void changeDataSettings(Program program, TaskMonitor monitor) {
monitor.setMessage("Changing data settings...");
Address address = program.getMinAddress();
while (!monitor.isCancelled()) {
Data data = getDataAt(program, address);
if (data == null) {
data = getDataAfter(program, address);
}
if (data == null) {
break;
}
int numComponents = data.getNumComponents();
for (int i = 0; i < numComponents; ++i) {
if (monitor.isCancelled()) {
break;
}
Data component = data.getComponent(i);
byte[] bytes = new byte[component.getLength()];
try {
program.getMemory().getBytes(component.getAddress(), bytes);
}
catch (MemoryAccessException e) {
// ignore
}
boolean isAscii = true;
for (byte b : bytes) {
if (b < ' ' || b > '~') {
isAscii = false;
}
}
if (isAscii && bytes.length > 1) {
changeFormatToString(component);
}
/*
if (component.getFieldName().equals("magic") ||
component.getFieldName().equals("identifier") ||
component.getFieldName().equals("compression") ||
component.getFieldName().equals("format") ||
component.getFieldName().equals("type")) {
changeFormatToString(component);
}
*/
}
address = address.add(data.getLength());
}
}
protected void removeEmptyFragments(Program program) throws NotEmptyException {
ProgramModule rootModule = program.getListing().getRootModule("Program Tree");
Group[] children = rootModule.getChildren();
for (Group child : children) {
if (child instanceof ProgramFragment) {
ProgramFragment fragment = (ProgramFragment) child;
if (fragment.isEmpty()) {
rootModule.removeChild(fragment.getName());
}
}
}
}
protected void changeFormatToString(Data data) {
SettingsImpl settings = new SettingsImpl(data);
settings.setDefaultSettings(settings);
SettingsDefinition[] settingsDefinitions = data.getDataType().getSettingsDefinitions();
for (SettingsDefinition settingsDefinition : settingsDefinitions) {
if (settingsDefinition instanceof FormatSettingsDefinition) {
FormatSettingsDefinition format = (FormatSettingsDefinition) settingsDefinition;
format.setChoice(data, FormatSettingsDefinition.CHAR);
}
}
}
protected ProgramFragment createFragment(Program program, String fragmentName, Address start,
Address end) throws Exception {
ProgramModule module = program.getListing().getDefaultRootModule();
ProgramFragment fragment = getFragment(module, fragmentName);
if (fragment == null) {
fragment = module.createFragment(fragmentName);
}
fragment.move(start, end.subtract(1));
return fragment;
}
protected ProgramFragment getFragment(ProgramModule module, String fragmentName) {
Group[] groups = module.getChildren();
for (Group group : groups) {
if (group.getName().equals(fragmentName)) {
return (ProgramFragment) group;
}
}
return null;
}
protected Data getDataAt(Program program, Address address) {
return program.getListing().getDefinedDataAt(address);
}
protected Data getDataAfter(Program program, Data data) {
return getDataAfter(program, data.getMaxAddress());
}
protected Data getDataAfter(Program program, Address address) {
return program.getListing().getDefinedDataAfter(address);
}
protected Address toAddr(Program program, long offset) {
return program.getAddressFactory().getDefaultAddressSpace().getAddress(offset);
}
protected Data createData(Program program, Address address, DataType datatype)
throws Exception {
if (datatype instanceof StringDataType) {
CreateStringCmd cmd = new CreateStringCmd(address);
if (!cmd.applyTo(program)) {
throw new RuntimeException(cmd.getStatusMsg());
}
}
else {
CreateDataCmd cmd = new CreateDataCmd(address, datatype);
if (!cmd.applyTo(program)) {
throw new RuntimeException(cmd.getStatusMsg());
}
}
return program.getListing().getDefinedDataAt(address);
}
protected boolean setPlateComment(Program program, Address address, String comment) {
SetCommentCmd cmd = new SetCommentCmd(address, CodeUnit.PLATE_COMMENT, comment);
return cmd.applyTo(program);
}
protected Function createFunction(Program program, Address entryPoint) {
CreateFunctionCmd cmd = new CreateFunctionCmd(entryPoint);
cmd.applyTo(program);
return program.getListing().getFunctionAt(entryPoint);
}
protected Address find(Program program, Address start, byte[] values, TaskMonitor monitor) {
return program.getMemory().findBytes(start, values, null, true, monitor);
}
}

View file

@ -0,0 +1,27 @@
/* ###
* 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.analyzers;
import ghidra.util.classfinder.ClassSearcher;
import java.util.Set;
public final class FileFormatAnalyzerFactory {
public final static Set<FileFormatAnalyzer> getAnalyzers() {
return ClassSearcher.getInstances(FileFormatAnalyzer.class);
}
}

View file

@ -0,0 +1,32 @@
/* ###
* 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.crypto;
public final class CryptoKey {
public final static CryptoKey NOT_ENCRYPTED_KEY = new CryptoKey(null,null);
public final byte [] key;
public final byte [] iv;
public CryptoKey(byte [] key, byte [] iv) {
this.key = key;
this.iv = iv;
}
public boolean isEmpty() {
return key == null || key.length == 0;
}
}

View file

@ -0,0 +1,155 @@
/* ###
* 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.crypto;
import java.io.*;
import java.util.*;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.input.SAXBuilder;
import generic.jar.ResourceFile;
import ghidra.framework.Application;
import ghidra.util.Msg;
import ghidra.util.NumericUtilities;
import ghidra.util.exception.CryptoException;
import ghidra.util.xml.XmlUtilities;
import util.CollectionUtils;
public final class CryptoKeyFactory {
private static Map<String, Map<String, CryptoKey>> cryptoMap =
new HashMap<String, Map<String, CryptoKey>>();
private static Map<String, Long> fileDatesMap = new HashMap<String, Long>();
public static void forceReload() {
cryptoMap.clear();
fileDatesMap.clear();
loadIfNeeded();
}
/**
* Loads the crypto key XML file if it is not currently loaded OR if it has
* changed since it was last loaded.
*/
private static void loadIfNeeded() {
ResourceFile cryptoDirectory = getCryptoDirectory();
ResourceFile[] files = cryptoDirectory.listFiles();
for (ResourceFile file : files) {
if (!file.getName().endsWith(".xml")) {
continue;
}
if (fileDatesMap.containsKey(file.getName())) {
if (fileDatesMap.get(file.getName()) == file.lastModified()) {
continue;
}
}
fileDatesMap.put(file.getName(), file.lastModified());
try {
InputStream is = file.getInputStream();
try {
SAXBuilder sax = XmlUtilities.createSecureSAXBuilder(false, false);
Document doc = sax.build(is);
Element root = doc.getRootElement();
String firmwareName = root.getAttributeValue("NAME");
if (!cryptoMap.containsKey(firmwareName)) {
cryptoMap.put(firmwareName, new HashMap<String, CryptoKey>());
}
List<Element> firmwareFileList =
CollectionUtils.asList(root.getChildren(), Element.class);
Iterator<Element> firmwareFileIter = firmwareFileList.iterator();
while (firmwareFileIter.hasNext()) {
Element firmwareFileElement = firmwareFileIter.next();
String path = firmwareFileElement.getAttributeValue("PATH");
if (firmwareFileElement.getAttribute("not_encrypted") != null) {
cryptoMap.get(firmwareName).put(path, CryptoKey.NOT_ENCRYPTED_KEY);
}
else {
Element keyElement = firmwareFileElement.getChild("KEY");
String keyString = keyElement.getText().trim();
if ((keyString.length() % 2) != 0) {
throw new CryptoException("Invalid key length in [" + firmwareName +
".xml] for [" + path + "]");
}
byte[] key = NumericUtilities.convertStringToBytes(keyString);
Element ivElement = firmwareFileElement.getChild("IV");
String ivString = ivElement.getText().trim();
if ((ivString.length() % 2) != 0) {
throw new CryptoException("Invalid iv length in [" + firmwareName +
".xml] for [" + path + "]");
}
byte[] iv = NumericUtilities.convertStringToBytes(ivString);
CryptoKey cryptoKey = new CryptoKey(key, iv);
cryptoMap.get(firmwareName).put(path, cryptoKey);
}
}
}
finally {
is.close();
}
}
catch (Exception e) {
Msg.showWarn(CryptoKeyFactory.class, null, "Error Parsing Crypto Keys File",
"Unable to process crypto keys files.", e);
}
}
}
public static ResourceFile getCryptoDirectory() {
try {
return Application.getModuleDataSubDirectory("crypto");
}
catch (IOException e) {
}
throw new RuntimeException("cannot find crypto directory");
}
public static CryptoKey getCryptoKey(String firmwareName, String firmwarePath)
throws CryptoException {
loadIfNeeded();
Map<String, CryptoKey> firmwareMap = cryptoMap.get(firmwareName);
if (firmwareMap == null) {
throw new CryptoException(
"Firmware may be encrypted, but XML key file does not exist: " + "[" +
firmwareName + ".xml]");
}
CryptoKey cryptoKey = firmwareMap.get(firmwarePath);//check for absolute path
if (cryptoKey == null) {//check for relative path
File file = new File(firmwarePath);
cryptoKey = firmwareMap.get(file.getName());
}
if (cryptoKey == null) {//okay it does not exist
throw new CryptoException("[" + firmwareName + ".xml]" +
" does not contain an entry for " + firmwarePath + ". File might be encrypted.");
}
if (cryptoKey == CryptoKey.NOT_ENCRYPTED_KEY) {
return cryptoKey;
}
if (cryptoKey.isEmpty()) {
throw new CryptoException(
"No key specified in [" + firmwareName + ".xml] file for [" + firmwarePath + "]");
}
return cryptoKey;
}
}

View file

@ -0,0 +1,80 @@
/* ###
* 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.crypto;
import generic.jar.ResourceFile;
import java.io.*;
/**
* Reads a file and creates a template
* XML file for storing the crypto keys and IVs.
*/
public final class CryptoKeyFileTemplateWriter {
private String fileName;
private PrintWriter writer;
/**
* Constructs a new template using the given file name.
* @param fileName the name of the firmware file
*/
public CryptoKeyFileTemplateWriter(String fileName) {
this.fileName = fileName;
}
/**
* Returns TRUE if the XML file already exists.
* @return TRUE if the XML file already exists
*/
public boolean exists() {
ResourceFile xmlFile =
new ResourceFile(CryptoKeyFactory.getCryptoDirectory(), fileName + ".xml");
return xmlFile.exists();
}
/**
* Opens the crypto key file.
* WARNING: If a file already exists, it will be overwritten.
* @throws IOException if an I/O error occurs
*/
public void open() throws IOException {
File xmlFile =
new ResourceFile(CryptoKeyFactory.getCryptoDirectory(), fileName + ".xml").getFile(false);
writer = new PrintWriter(xmlFile);
writer.println("<FIRMWARE NAME=\"" + fileName + "\">");
}
/**
* Closes the crypto key file.
* @throws IOException if an I/O error occurs
*/
public void close() throws IOException {
writer.println("</FIRMWARE>");
writer.close();
}
/**
* Write the entryName to the XML file.
* @param entryName the name of the entry
* @throws IOException if an I/O error occurs
*/
public void write(String entryName) throws IOException {
writer.println(" <FILE PATH=\"" + entryName + "\">");
writer.println(" <KEY></KEY>");
writer.println(" <IV></IV>");
writer.println(" </FILE>");
}
}

View file

@ -0,0 +1,35 @@
/* ###
* 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.crypto;
public final class CryptoUtil {
/**
* Translates an integer from host byte order to network byte order.
* @param value the integer value to translate
* @return the network byte order value
*/
public final static byte [] htonl(int value) {
byte [] bytes = new byte[4];
bytes[3] = (byte)((value >> 0) & 0xff);
bytes[2] = (byte)((value >> 8) & 0xff);
bytes[1] = (byte)((value >> 16) & 0xff);
bytes[0] = (byte)((value >> 24) & 0xff);
return bytes;
}
}

View file

@ -0,0 +1,44 @@
/* ###
* 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.crypto;
import java.io.*;
public class DecryptedPacket {
public final File decryptedFile;
public final InputStream decryptedStream;
public final int decryptedStreamLength;
public DecryptedPacket(File decryptedFile) {
this.decryptedFile = decryptedFile;
this.decryptedStream = null;
this.decryptedStreamLength = -1;
}
public DecryptedPacket(InputStream decryptedStream, int decryptedStreamLength) {
this.decryptedFile = null;
this.decryptedStream = decryptedStream;
this.decryptedStreamLength = decryptedStreamLength;
}
public void dispose() {
try {
decryptedStream.close();
}
catch (IOException e) {/* don't care */}
}
}

View file

@ -0,0 +1,54 @@
/* ###
* 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.crypto;
import ghidra.app.util.bin.ByteProvider;
import ghidra.util.classfinder.ExtensionPoint;
import ghidra.util.exception.CancelledException;
import ghidra.util.exception.CryptoException;
import ghidra.util.task.TaskMonitor;
import java.io.IOException;
/**
* NOTE: ALL DECRYPTOR CLASSES MUST END IN "Decryptor". If not,
* the ClassSearcher will not find them.
*/
public interface Decryptor extends ExtensionPoint {
/**
* Returns TRUE if this decryptor implementation
* can in fact decrypt the bytes contained in the byte provider.
* @param provider the byte provider.
* @return TRUE if this decryptor can decrypt
* @throws IOException if an I/O occurs
*/
boolean isValid(ByteProvider provider) throws IOException;
/**
* Actually decrypt the bytes in the byte provider.
* @param firmwareName
* @param firmwarePath
* @param provider
* @param monitor
* @return
* @throws IOException
* @throws CryptoException
* @throws CancelledException
*/
DecryptedPacket decrypt(String firmwareName, String firmwarePath, ByteProvider provider, TaskMonitor monitor)
throws IOException, CryptoException, CancelledException;
}

View file

@ -0,0 +1,49 @@
/* ###
* 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.crypto;
import ghidra.app.util.bin.ByteProvider;
import ghidra.util.classfinder.ClassSearcher;
import ghidra.util.exception.CancelledException;
import ghidra.util.exception.CryptoException;
import ghidra.util.task.TaskMonitor;
import java.io.IOException;
import java.util.Set;
/**
* Not used by any known code, not tested.
*/
@Deprecated
public final class DecryptorFactory {
public final static DecryptedPacket decrypt(String firmwareName, String firmwarePath,
ByteProvider provider, TaskMonitor monitor) throws IOException, CryptoException,
CancelledException {
Set<Decryptor> instances = ClassSearcher.getInstances(Decryptor.class);
for (Decryptor decryptor : instances) {
if (monitor.isCancelled()) {
throw new CancelledException();
}
if (decryptor.isValid(provider)) {
return decryptor.decrypt(firmwareName, firmwarePath, provider, monitor);
}
}
throw new CryptoException("Unable to decrypt " + provider.getName() +
" unable to locate decryption provider.");
}
}

View file

@ -0,0 +1,254 @@
/* ###
* 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.eclipse;
import java.io.*;
import java.util.List;
import org.jdom.*;
import generic.jar.ResourceFile;
import ghidra.app.util.importer.MessageLog;
import ghidra.file.formats.android.dex.DexToJarFileSystem;
import ghidra.file.formats.android.xml.AndroidXmlFileSystem;
import ghidra.file.formats.zip.ZipFileSystem;
import ghidra.file.jad.*;
import ghidra.formats.gfilesystem.*;
import ghidra.framework.Application;
import ghidra.util.Msg;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor;
import ghidra.util.xml.XmlUtilities;
import utilities.util.FileUtilities;
/**
* Creates an Eclipse project folder and contents based on the contents of an Android
* APK file.
*/
public class AndroidProjectCreator {
public static ResourceFile androidDirectory;
static {
ResourceFile directory = null;
try {
directory = Application.getModuleDataSubDirectory("android");
}
catch (IOException e) {
Msg.error(AndroidProjectCreator.class, "cannot find android directory");
}
androidDirectory = directory;
}
private GFile apkFile;
private File eclipseProjectDirectory;
private File srcDirectory;
private File genDirectory;
private File assetDirectory;
private ResourceFile projectTemplateFile =
new ResourceFile(androidDirectory, "eclipse-project");
private ResourceFile classpathTemplateFile =
new ResourceFile(androidDirectory, "eclipse-classpath");
private MessageLog log = new MessageLog();
public AndroidProjectCreator(GFile apkFile, File eclipseProjectDirectory) {
this.apkFile = apkFile;
this.eclipseProjectDirectory = eclipseProjectDirectory;
}
public void create(TaskMonitor monitor) throws IOException, CancelledException {
createEclipseProjectDirectories();
try (ZipFileSystem fs = FileSystemService.getInstance().mountSpecificFileSystem(
apkFile.getFSRL(), ZipFileSystem.class, monitor)) {
List<GFile> listing = fs.getListing(null);
processListing(eclipseProjectDirectory, listing, monitor);
}
File destProjectFile =
copyFile(projectTemplateFile, eclipseProjectDirectory, ".project", monitor);
copyFile(classpathTemplateFile, eclipseProjectDirectory, ".classpath", monitor);
fixupProjectFile(destProjectFile);
}
public MessageLog getLog() {
return log;
}
private void fixupProjectFile(File projectFile) throws IOException {
try {
Document projectDoc = XmlUtilities.readDocFromFile(projectFile);
Element nameElement = projectDoc.getRootElement().getChild("name");
if (nameElement != null) {
nameElement.setText(apkFile.getName());
XmlUtilities.writeDocToFile(projectDoc, projectFile);
}
}
catch (JDOMException e) {
throw new IOException("Error when processing xml", e);
}
}
private void createEclipseProjectDirectories() throws IOException {
FileUtilities.checkedMkdirs(eclipseProjectDirectory);
srcDirectory = FileUtilities.checkedMkdir(new File(eclipseProjectDirectory, "src"));
genDirectory = FileUtilities.checkedMkdirs(new File(eclipseProjectDirectory, "gen"));
assetDirectory = FileUtilities.checkedMkdirs(new File(eclipseProjectDirectory, "asset"));
}
private void processListing(File outputDirectory, List<GFile> listing, TaskMonitor monitor)
throws IOException, CancelledException {
for (GFile child : listing) {
String childName = child.getName();
if (monitor.isCancelled()) {
break;
}
monitor.setIndeterminate(true);
if (child.isDirectory()) {
if (childName.equals("META-INF")) {
continue;
}
File subDir = new File(outputDirectory, childName);
FileUtilities.checkedMkdir(subDir);
processListing(subDir, child.getListing(), monitor);
continue;
}
File cacheFile = FileSystemService.getInstance().getFile(child.getFSRL(), monitor);
try {
if (childName.endsWith(".xml") &&
AndroidXmlFileSystem.isAndroidXmlFile(cacheFile, monitor)) {
processXML(outputDirectory, child, monitor);
}
else if (childName.endsWith("classes.dex")) {
processDex(outputDirectory, child, monitor);
}
else if (childName.endsWith("resources.arsc")) {
//TODO convert resources file back into actual resources
copyFile(cacheFile, outputDirectory, child.getName(), monitor);
}
else if (childName.endsWith(".class")) {
processClass(outputDirectory, child, monitor);
}
else {
copyFile(cacheFile, outputDirectory, childName, monitor);
}
}
catch (Exception e) {
log.appendMsg("Unable to export child file: " + child.getFSRL());
log.appendMsg("\tISSUE WAS: " + e.getMessage());
Msg.error(this, "Unable to export child file", e);
}
}
}
private void processDex(File outputDirectory, GFile dexFile, TaskMonitor monitor)
throws IOException, CancelledException {
try (DexToJarFileSystem fs = FileSystemService.getInstance().mountSpecificFileSystem(
dexFile.getFSRL(), DexToJarFileSystem.class, monitor)) {
GFile jarFile = fs.getJarFile();
processJar(srcDirectory, jarFile.getFSRL(), monitor);
}
}
private void processJar(File outputDirectory, FSRL jarFile, TaskMonitor monitor)
throws IOException, CancelledException {
JarDecompiler decompiler = new JarDecompiler(jarFile, outputDirectory);
decompiler.decompile(monitor);
if (decompiler.getLog().getMsgCount() > 0) {
log.copyFrom(decompiler.getLog());
}
}
private void processClass(File outputDirectory, GFile classGFile, TaskMonitor monitor)
throws IOException, CancelledException {
String classFileName = classGFile.getName();
File destClassFile = new File(outputDirectory, classFileName);
//File destJavaFile = new File(outputDirectory, PathUtils.stripExt(classFileName) + ".java");
File classFile = FileSystemService.getInstance().getFile(classGFile.getFSRL(), monitor);
copyFile(classFile, outputDirectory, classFileName, monitor);
JadProcessWrapper wrapper = new JadProcessWrapper(destClassFile);
JadProcessController controller = new JadProcessController(wrapper, classGFile.getName());
controller.decompile(5, monitor);
}
private void processXML(File outputDirectory, GFile containerFile, TaskMonitor monitor)
throws CancelledException {
try (AndroidXmlFileSystem fs = FileSystemService.getInstance().mountSpecificFileSystem(
containerFile.getFSRL(), AndroidXmlFileSystem.class, monitor)) {
GFile xmlFile = fs.getPayloadFile();
copyStream(fs.getInputStream(xmlFile, monitor), outputDirectory,
containerFile.getName(), monitor);
}
catch (IOException ioe) {
Msg.info(this,
"XML file " + containerFile.getPath() + " is not AndriodXmlFileSystem compatible",
ioe);
}
}
private static File copyFile(ResourceFile inputFile, File outputDirectory, String outputName,
TaskMonitor monitor) throws IOException {
try (InputStream is = inputFile.getInputStream()) {
FileUtilities.checkedMkdirs(outputDirectory);
File destFile = new File(outputDirectory, outputName);
monitor.setMessage("Copying [" + inputFile.getName() + "] to Eclipse project...");
FileUtilities.copyStreamToFile(is, destFile, false, monitor);
return destFile;
}
}
private static File copyFile(File inputFile, File outputDirectory, String outputName,
TaskMonitor monitor) throws IOException {
FileUtilities.checkedMkdirs(outputDirectory);
File destFile = new File(outputDirectory, outputName);
monitor.setMessage("Copying [" + inputFile.getName() + "] to Eclipse project...");
FileUtilities.copyFile(inputFile, destFile, false, monitor);
return destFile;
}
private static File copyStream(InputStream streamToCopy, File outputDirectory,
String outputName, TaskMonitor monitor) throws IOException {
try (InputStream is = streamToCopy) {
FileUtilities.checkedMkdirs(outputDirectory);
File destFile = new File(outputDirectory, outputName);
monitor.setMessage("Copying [" + outputName + "] to Eclipse project...");
FileUtilities.copyStreamToFile(is, destFile, false, monitor);
return destFile;
}
}
}

View file

@ -0,0 +1,69 @@
/* ###
* 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.apk;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import ghidra.app.util.bin.ByteProvider;
import ghidra.formats.gfilesystem.GFile;
import ghidra.formats.gfilesystem.GFileSystemBase;
import ghidra.formats.gfilesystem.annotations.FileSystemInfo;
import ghidra.formats.gfilesystem.factory.GFileSystemBaseFactory;
import ghidra.util.exception.CancelledException;
import ghidra.util.exception.CryptoException;
import ghidra.util.task.TaskMonitor;
/**
*
* APK is really just a ZIP file.
*
*/
@FileSystemInfo(type = "apk", description = "Android APK", factory = GFileSystemBaseFactory.class)
public class ApkFileSystem extends GFileSystemBase {
public ApkFileSystem(String fileSystemName, ByteProvider provider) {
super(fileSystemName, provider);
}
@Override
public boolean isValid(TaskMonitor monitor) throws IOException {
return false;
}
@Override
public void open(TaskMonitor monitor) throws IOException, CryptoException, CancelledException {
}
@Override
public List<GFile> getListing(GFile directory) throws IOException {
return new ArrayList<>();
}
@Override
public String getInfo(GFile file, TaskMonitor monitor) throws IOException {
return null;
}
@Override
protected InputStream getData(GFile file, TaskMonitor monitor)
throws IOException, CancelledException, CryptoException {
return null;
}
}

View file

@ -0,0 +1,157 @@
/* ###
* 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.bootimg;
import ghidra.app.util.bin.*;
import ghidra.program.model.data.*;
import ghidra.util.exception.DuplicateNameException;
import java.io.IOException;
public class BootImage implements StructConverter {
private String magic;
private int kernelSize;
private int kernelAddress;
private int ramDiskSize;
private int ramDiskAddress;
private int secondStageSize;
private int secondStageAddress;
private int tagsAddress;
private int pageSize;
private int [] unused;
private String name;
private String commandLine;
private int [] id;
public BootImage(ByteProvider provider) throws IOException {
this( new BinaryReader( provider, true ) );
}
public BootImage(BinaryReader reader) throws IOException {
magic = reader.readNextAsciiString( BootImageConstants.BOOT_IMAGE_MAGIC_SIZE );
kernelSize = reader.readNextInt();
kernelAddress = reader.readNextInt();
ramDiskSize = reader.readNextInt();
ramDiskAddress = reader.readNextInt();
secondStageSize = reader.readNextInt();
secondStageAddress = reader.readNextInt();
tagsAddress = reader.readNextInt();
pageSize = reader.readNextInt();
unused = reader.readNextIntArray( 2 );
name = reader.readNextAsciiString( BootImageConstants.BOOT_NAME_SIZE );
commandLine = reader.readNextAsciiString( BootImageConstants.BOOT_ARGS_SIZE );
id = reader.readNextIntArray( 8 );
}
public String getMagic() {
return magic;
}
public int getKernelSize() {
return kernelSize;
}
public int getKernelAddress() {
return kernelAddress;
}
public int getKernelOffset() {
return getPageSize();
}
public int getKernelSizePageAligned() {
int remainder = getPageSize() - ( getKernelSize() % getPageSize() );
return getKernelSize() + remainder;
}
public int getRamDiskSize() {
return ramDiskSize;
}
public int getRamDiskAddress() {
return ramDiskAddress;
}
public int getRamDiskOffset() {
return getKernelOffset() + getKernelSizePageAligned();
}
public int getRamDiskSizePageAligned() {
int remainder = getPageSize() - ( getRamDiskSize() % getPageSize() );
return getRamDiskSize() + remainder;
}
public int getSecondStageSize() {
return secondStageSize;
}
public int getSecondStageAddress() {
return secondStageAddress;
}
public int getSecondStageOffset() {
return getRamDiskOffset() + ( getRamDiskSizePageAligned() * getPageSize() );
}
public int getSecondStageSizePageAligned() {
int remainder = getPageSize() - ( getSecondStageSize() % getPageSize() );
return getSecondStageSize() + remainder;
}
public int getTagsAddress() {
return tagsAddress;
}
public int getPageSize() {
return pageSize;
}
public int [] getUnused() {
return unused;
}
public String getName() {
return name;
}
public String getCommandLine() {
return commandLine;
}
public int [] getId() {
return id;
}
@Override
public DataType toDataType() throws DuplicateNameException, IOException {
Structure structure = new StructureDataType( "boot_img_hdr", 0 );
structure.add( UTF8, BootImageConstants.BOOT_IMAGE_MAGIC_SIZE, "magic", null );
structure.add( DWORD, "kernelSize", null );
structure.add( DWORD, "kernelAddress", null );
structure.add( DWORD, "ramDiskSize", null );
structure.add( DWORD, "ramDiskAddress", null );
structure.add( DWORD, "secondStageSize", null );
structure.add( DWORD, "secondStageAddress", null );
structure.add( DWORD, "tagsAddress", null );
structure.add( DWORD, "pageSize", null );
structure.add(new ArrayDataType(DWORD, 2, DWORD.getLength()), "unused", null);
structure.add( UTF8, BootImageConstants.BOOT_NAME_SIZE, "name", null );
structure.add( UTF8, BootImageConstants.BOOT_ARGS_SIZE, "commandLine", null );
structure.add(new ArrayDataType(DWORD, 8, DWORD.getLength()), "id", null);
return structure;
}
}

View file

@ -0,0 +1,130 @@
/* ###
* 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.bootimg;
import ghidra.app.plugin.core.analysis.AnalysisWorker;
import ghidra.app.plugin.core.analysis.AutoAnalysisManager;
import ghidra.app.util.bin.*;
import ghidra.app.util.importer.MessageLog;
import ghidra.file.analyzers.FileFormatAnalyzer;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressSetView;
import ghidra.program.model.data.DataType;
import ghidra.program.model.listing.Data;
import ghidra.program.model.listing.Program;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor;
public class BootImageAnalyzer extends FileFormatAnalyzer implements AnalysisWorker {
@Override
public String getName() {
return "Android Boot or Recovery Image Annotation";
}
@Override
public boolean getDefaultEnablement(Program program) {
return false;
}
@Override
public String getDescription() {
return "Annotates Android Boot and Recovery Image files.";
}
@Override
public boolean canAnalyze(Program program) {
try {
return BootImageUtil.isBootImage(program);
}
catch (Exception e) {
// not a boot image
}
return false;
}
@Override
public boolean isPrototype() {
return true;
}
private MessageLog log;
@Override
public boolean analyze(Program program, AddressSetView set, TaskMonitor monitor, MessageLog log)
throws Exception {
this.log = log;
AutoAnalysisManager manager = AutoAnalysisManager.getAnalysisManager(program);
return manager.scheduleWorker(this, null, false, monitor);
}
@Override
public boolean analysisWorkerCallback(Program program, Object workerContext, TaskMonitor monitor)
throws Exception, CancelledException {
Address address = program.getMinAddress();
ByteProvider provider = new MemoryByteProvider(program.getMemory(), address);
BinaryReader reader = new BinaryReader(provider, true);
BootImage header = new BootImage(reader);
if (!header.getMagic().equals(BootImageConstants.BOOT_IMAGE_MAGIC)) {
return false;
}
DataType headerDataType = header.toDataType();
Data headerData = createData(program, address, headerDataType);
if (headerData == null) {
log.appendMsg("Unable to create header data.");
}
createFragment(program, headerDataType.getName(), toAddr(program, 0),
toAddr(program, header.getPageSize()));
if (header.getKernelSize() > 0) {
Address start = toAddr(program, header.getKernelOffset());
Address end = toAddr(program, header.getKernelOffset() + header.getKernelSize());
createFragment(program, BootImageConstants.KERNEL, start, end);
}
if (header.getRamDiskSize() > 0) {
Address start = toAddr(program, header.getRamDiskOffset());
Address end = toAddr(program, header.getRamDiskOffset() + header.getRamDiskSize());
createFragment(program, BootImageConstants.RAMDISK, start, end);
}
if (header.getSecondStageSize() > 0) {
Address start = toAddr(program, header.getSecondStageOffset());
Address end =
toAddr(program, header.getSecondStageOffset() + header.getSecondStageSize());
createFragment(program, BootImageConstants.SECOND_STAGE, start, end);
}
changeDataSettings(program, monitor);
removeEmptyFragments(program);
return true;
}
@Override
public String getWorkerName() {
return "BootImageAnalyzer";
}
}

View file

@ -0,0 +1,36 @@
/* ###
* 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.bootimg;
public final class BootImageConstants {
public final static String BOOT_IMAGE_MAGIC = "ANDROID!";
public final static byte [] BOOT_IMAGE_MAGIC_BYTES = BOOT_IMAGE_MAGIC.getBytes();
public final static int BOOT_IMAGE_MAGIC_SIZE = BOOT_IMAGE_MAGIC.length();
public final static int BOOT_NAME_SIZE = 16;
public final static int BOOT_ARGS_SIZE = 512;
public final static String SECOND_STAGE = "second stage";
public final static String RAMDISK = "ramdisk";
public final static String KERNEL = "kernel";
}

View file

@ -0,0 +1,123 @@
/* ###
* 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.bootimg;
import java.io.*;
import java.util.*;
import ghidra.app.util.bin.ByteProvider;
import ghidra.formats.gfilesystem.*;
import ghidra.formats.gfilesystem.annotations.FileSystemInfo;
import ghidra.formats.gfilesystem.factory.GFileSystemBaseFactory;
import ghidra.util.exception.CancelledException;
import ghidra.util.exception.CryptoException;
import ghidra.util.task.TaskMonitor;
@FileSystemInfo(type = "androidbootimg", description = "Android Boot and Recovery Images", factory = GFileSystemBaseFactory.class)
public class BootImageFileSystem extends GFileSystemBase {
private BootImage header;
private GFileImpl kernelFile;
private GFileImpl ramdiskFile;
private GFileImpl secondStageFile;
private List<GFileImpl> fileList = new ArrayList<>();
public BootImageFileSystem(String fileSystemName, ByteProvider provider) {
super(fileSystemName, provider);
}
@Override
public boolean isValid(TaskMonitor monitor) throws IOException {
byte[] bytes = provider.readBytes(0, BootImageConstants.BOOT_IMAGE_MAGIC_SIZE);
return Arrays.equals(bytes, BootImageConstants.BOOT_IMAGE_MAGIC_BYTES);
}
@Override
public void open(TaskMonitor monitor) throws IOException, CryptoException, CancelledException {
this.header = new BootImage(provider);
if (!header.getMagic().equals(BootImageConstants.BOOT_IMAGE_MAGIC)) {
throw new IOException("Invalid Android boot image file!");
}
if (header.getKernelSize() > 0) {
kernelFile = GFileImpl.fromFilename(this, root, BootImageConstants.KERNEL, false,
header.getKernelSize(), null);
fileList.add(kernelFile);
}
if (header.getRamDiskSize() > 0) {
ramdiskFile = GFileImpl.fromFilename(this, root, BootImageConstants.RAMDISK, false,
header.getKernelSize(), null);
fileList.add(ramdiskFile);
}
if (header.getSecondStageSize() > 0) {
secondStageFile = GFileImpl.fromFilename(this, root, BootImageConstants.SECOND_STAGE,
false, header.getKernelSize(), null);
fileList.add(secondStageFile);
}
}
@Override
public void close() throws IOException {
kernelFile = null;
ramdiskFile = null;
secondStageFile = null;
header = null;
super.close();
}
@Override
public List<GFile> getListing(GFile directory) throws IOException {
return (directory == null || directory.equals(root)) ? new ArrayList<>(fileList)
: Collections.emptyList();
}
@Override
public String getInfo(GFile file, TaskMonitor monitor) throws IOException {
if (file == kernelFile) {
return "This is the actual KERNEL for the android device. You can analyze this file.";
}
else if (file == ramdiskFile) {
return "This is a ramdisk, it is a GZIP file containing a CPIO archive.";
}
else if (file == secondStageFile) {
return "This is a second stage loader file. It appears unused at this time.";
}
return null;
}
@Override
protected InputStream getData(GFile file, TaskMonitor monitor)
throws IOException, CancelledException, CryptoException {
if (file == kernelFile) {
byte[] kernelBytes =
provider.readBytes(header.getKernelOffset(), header.getKernelSize());
return new ByteArrayInputStream(kernelBytes);
}
else if (file == ramdiskFile) {
byte[] ramDiskBytes =
provider.readBytes(header.getRamDiskOffset(), header.getRamDiskSize());
return new ByteArrayInputStream(ramDiskBytes);
}
else if (file == secondStageFile) {
byte[] secondStageBytes =
provider.readBytes(header.getSecondStageOffset(), header.getSecondStageSize());
return new ByteArrayInputStream(secondStageBytes);
}
return null;
}
}

View file

@ -0,0 +1,35 @@
/* ###
* 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.bootimg;
import ghidra.program.model.address.Address;
import ghidra.program.model.listing.Program;
import java.util.Arrays;
public class BootImageUtil {
public final static boolean isBootImage( Program program ) {
byte [] bytes = new byte[ 8 ];
try {
Address address = program.getMinAddress();
program.getMemory().getBytes( address, bytes );
}
catch (Exception e) {}
return Arrays.equals( bytes, BootImageConstants.BOOT_IMAGE_MAGIC_BYTES );
}
}

View file

@ -0,0 +1,53 @@
/* ###
* 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.bootimg;
import ghidra.app.util.bin.BinaryReader;
import ghidra.app.util.bin.ByteProvider;
import java.io.IOException;
public class PartitionTableEntry {
private byte [] name;
private int start;
private int length;
private int flags;
public PartitionTableEntry(ByteProvider provider) throws IOException {
this( new BinaryReader( provider, true ) );
}
public PartitionTableEntry(BinaryReader reader) throws IOException {
name = reader.readNextByteArray( 16 );
start = reader.readNextInt();
length = reader.readNextInt();
flags = reader.readNextInt();
}
public String getName() {
return new String( name );
}
public int getStart() {
return start;
}
public int getLength() {
return length;
}
public int getFlags() {
return flags;
}
}

View file

@ -0,0 +1,41 @@
/* ###
* 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.dex;
import org.objectweb.asm.MethodVisitor;
import com.googlecode.d2j.Method;
import com.googlecode.d2j.dex.DexExceptionHandler;
import com.googlecode.d2j.node.DexMethodNode;
class DexToJarExceptionHandler implements DexExceptionHandler {
private Exception e;
@Override
public void handleMethodTranslateException(Method method, DexMethodNode node, MethodVisitor visitor, Exception e) {
this.e = e;
}
@Override
public void handleFileException(Exception e) {
this.e = e;
}
Exception getFileException() {
return e;
}
}

View file

@ -0,0 +1,183 @@
/* ###
* 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.dex;
import java.io.*;
import java.util.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
import org.apache.commons.io.FilenameUtils;
import org.objectweb.asm.*;
import com.googlecode.d2j.dex.ClassVisitorFactory;
import com.googlecode.d2j.dex.ExDex2Asm;
import com.googlecode.d2j.node.DexFileNode;
import com.googlecode.d2j.reader.DexFileReader;
import com.googlecode.d2j.visitors.DexFileVisitor;
import ghidra.app.util.bin.ByteProvider;
import ghidra.file.formats.android.dex.format.DexConstants;
import ghidra.formats.gfilesystem.*;
import ghidra.formats.gfilesystem.annotations.FileSystemInfo;
import ghidra.formats.gfilesystem.factory.GFileSystemBaseFactory;
import ghidra.util.Msg;
import ghidra.util.exception.CancelledException;
import ghidra.util.exception.CryptoException;
import ghidra.util.task.TaskMonitor;
import ghidra.util.task.UnknownProgressWrappingTaskMonitor;
import utilities.util.FileUtilities;
/**
* {@link GFileSystem} that converts a DEX file into a JAR file.
*/
@FileSystemInfo(type = "dex2jar", description = "Android DEX to JAR", factory = GFileSystemBaseFactory.class)
public class DexToJarFileSystem extends GFileSystemBase {
private GFileImpl jarFile;
public DexToJarFileSystem(String fileSystemName, ByteProvider provider) {
super(fileSystemName, provider);
}
public GFile getJarFile() {
return jarFile;
}
@Override
protected InputStream getData(GFile file, TaskMonitor monitor)
throws IOException, CancelledException, CryptoException {
if (file.equals(jarFile)) {
FileCacheEntry jarFileInfo = getJarFile(monitor);
return new FileInputStream(jarFileInfo.file);
}
return null;
}
@Override
public List<GFile> getListing(GFile directory) throws IOException {
return (directory == null || directory.equals(root)) ? Arrays.asList(jarFile)
: Collections.emptyList();
}
@Override
public boolean isValid(TaskMonitor monitor) throws IOException {
return DexConstants.isDexFile(provider);
}
private FileCacheEntry getJarFile(TaskMonitor monitor) throws CancelledException, IOException {
TaskMonitor upwtm = new UnknownProgressWrappingTaskMonitor(monitor, 1);
upwtm.setMessage("Converting DEX to JAR...");
FSRLRoot targetFSRL = getFSRL();
FSRL containerFSRL = targetFSRL.getContainer();
File containerFile = fsService.getFile(containerFSRL, monitor);
FileCacheEntry derivedFileInfo =
fsService.getDerivedFilePush(containerFSRL, "dex2jar", (os) -> {
try (ZipOutputStream outputStream = new ZipOutputStream(os)) {
DexToJarExceptionHandler exceptionHandler = new DexToJarExceptionHandler();
DexFileReader reader =
new DexFileReader(FileUtilities.getBytesFromFile(containerFile));
DexFileNode fileNode = new DexFileNode();
try {
reader.accept(fileNode, DexFileReader.IGNORE_READ_EXCEPTION);
}
catch (Exception ex) {
exceptionHandler.handleFileException(ex);
}
DexFileVisitor visitor = new DexFileVisitor();
reader.accept(visitor);
ClassVisitorFactory classVisitorFactory = name -> new ClassVisitor(Opcodes.ASM4,
new ClassWriter(ClassWriter.COMPUTE_MAXS)) {
//NOTE: EXTRACTED FROM Dex2jar.java
@Override
public void visitEnd() {
super.visitEnd();
ClassWriter cw = (ClassWriter) super.cv;
byte[] data;
try {
// FIXME handle 'java.lang.RuntimeException: Method code too large!'
data = cw.toByteArray();
}
catch (Exception ex) {
//System.err.println(String.format("ASM fail to generate .class file: %s", name));
Msg.warn(this,
String.format("ASM fail to generate .class file: %s", name));
exceptionHandler.handleFileException(ex);
return;
}
try {
ZipEntry entry = new ZipEntry(name + ".class");
outputStream.putNextEntry(entry);
outputStream.write(data);
outputStream.closeEntry();
upwtm.incrementProgress(1);
}
catch (IOException e) {
//e.printStackTrace(System.err);
Msg.warn(this, e);
}
}
};
ExDex2Asm exDex2Asm = new ExDex2Asm(exceptionHandler);
exDex2Asm.convertDex(fileNode, classVisitorFactory);
if (exceptionHandler.getFileException() != null) {
throw new IOException(exceptionHandler.getFileException());
}
outputStream.finish();
}
}, monitor);
return derivedFileInfo;
}
@Override
public void open(TaskMonitor monitor) throws CancelledException, IOException {
FileCacheEntry jarFileInfo = getJarFile(monitor);
FSRLRoot targetFSRL = getFSRL();
FSRL containerFSRL = targetFSRL.getContainer();
String baseName = FilenameUtils.removeExtension(containerFSRL.getName());
String jarName = baseName + ".jar";
FSRL jarFSRL = targetFSRL.withPathMD5(jarName, jarFileInfo.md5);
this.jarFile = GFileImpl.fromFilename(this, root, baseName + ".jar", false,
jarFileInfo.file.length(), jarFSRL);
}
@Override
public void close() throws IOException {
super.close();
}
@Override
public String getInfo(GFile file, TaskMonitor monitor) throws IOException {
return null;
}
}

View file

@ -0,0 +1,214 @@
/* ###
* 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.dex;
import java.io.*;
import java.util.*;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.filefilter.TrueFileFilter;
import org.jf.baksmali.baksmali;
import org.jf.dexlib.DexFile;
import ghidra.app.util.bin.ByteProvider;
import ghidra.file.formats.android.dex.format.DexConstants;
import ghidra.formats.gfilesystem.*;
import ghidra.formats.gfilesystem.annotations.FileSystemInfo;
import ghidra.formats.gfilesystem.factory.GFileSystemBaseFactory;
import ghidra.util.exception.CancelledException;
import ghidra.util.exception.CryptoException;
import ghidra.util.task.TaskMonitor;
@FileSystemInfo(type = "dex2smali", description = "Android DEX to SMALI", factory = GFileSystemBaseFactory.class)
public class DexToSmaliFileSystem extends GFileSystemBase {
private Map<GFile, File> map = new HashMap<>();
public DexToSmaliFileSystem(String fileSystemName, ByteProvider provider) {
super(fileSystemName, provider);
}
@Override
protected InputStream getData(GFile file, TaskMonitor monitor)
throws IOException, CancelledException, CryptoException {
File entry = map.get(file);
return new FileInputStream(entry);
}
@Override
public List<GFile> getListing(GFile directory) throws IOException {
if (directory == null || directory.equals(root)) {
List<GFile> roots = new ArrayList<>();
for (GFile file : map.keySet()) {
if (file.getParentFile() == root || file.getParentFile().equals(root)) {
roots.add(file);
}
}
return roots;
}
List<GFile> tmp = new ArrayList<>();
for (GFile file : map.keySet()) {
if (file.getParentFile() == null) {
continue;
}
if (file.getParentFile().equals(directory)) {
tmp.add(file);
}
}
return tmp;
}
@Override
public boolean isValid(TaskMonitor monitor) throws IOException {
return DexConstants.isDexFile(provider);
}
//Baksmali
@Override
public void open(TaskMonitor monitor) throws IOException, CryptoException, CancelledException {
monitor.setMessage("Converting DEX to SMALI...");
File dexFileFile = provider.getFile();
boolean isFixRegisters = false;
boolean isNoParameterRegisters = false;
boolean isUseLocalsDirective = false;
boolean isUseSequentialLabels = false;
boolean isOutputDebugInfo = true;
boolean isAddCodeOffsets = false;
boolean isDeOdex = false;//TODO DexConstants.isODEX( provider );
boolean isVerify = false;
boolean isIgnoreErrors = false;
int registerInfo = 0;
boolean isNoAccessorComments = false;//TODO
String inlineTable = null;//TODO
boolean isCheckPackagePrivateAccess = false;
final String baseTempPath = System.getProperty("java.io.tmpdir");
int rand = new Random().nextInt() & 0xffff;
File tempOutputDirectory =
new File(baseTempPath + File.separator + "ghidra_file_system_" + rand);
String bootClassPath = "core.jar:ext.jar:framework.jar:android.policy.jar:services.jar";
StringBuffer extraBootClassPathEntries = new StringBuffer();
List<String> bootClassPathDirs = new ArrayList<>();
bootClassPathDirs.add(".");
//TODO bootClassPathDirs.add( "~/Android/smali/required-libs/" );
DexFile dexFile = new DexFile(dexFileFile, !isFixRegisters, false);
String[] bootClassPathDirsArray = new String[bootClassPathDirs.size()];
for (int i = 0; i < bootClassPathDirsArray.length; i++) {
bootClassPathDirsArray[i] = bootClassPathDirs.get(i);
}
baksmali.disassembleDexFile(dexFileFile.getPath(), dexFile, isDeOdex,
tempOutputDirectory.getPath(), bootClassPathDirsArray, bootClassPath,
extraBootClassPathEntries.toString(), isNoParameterRegisters, isUseLocalsDirective,
isUseSequentialLabels, isOutputDebugInfo, isAddCodeOffsets, isNoAccessorComments,
registerInfo, isVerify, isIgnoreErrors, inlineTable, isCheckPackagePrivateAccess);
getFileListing(tempOutputDirectory, root, monitor);
}
private void getFileListing(File startingDirectory, GFileImpl currentRoot,
TaskMonitor monitor) {
Iterator<File> iterator = FileUtils.iterateFiles(startingDirectory, TrueFileFilter.INSTANCE,
TrueFileFilter.INSTANCE);
while (iterator.hasNext()) {
File f = iterator.next();
if (monitor.isCancelled()) {
break;
}
monitor.setMessage(f.getName());
GFileImpl gfile = GFileImpl.fromFilename(this, currentRoot, f.getName(),
f.isDirectory(), f.length(), null);
storeFile(gfile, f);
}
}
private void storeFile(GFile file, File entry) {
if (file == null) {
return;
}
if (file.equals(root)) {
return;
}
if (!map.containsKey(file) || map.get(file) == null) {
map.put(file, entry);
}
GFile parentFile = file.getParentFile();
storeFile(parentFile, null);
}
@Override
public void close() throws IOException {
map.clear();
super.close();
}
@Override
public String getInfo(GFile file, TaskMonitor monitor) throws IOException {
return null;
}
}
/* DexHeader header = new DexHeader(provider);
if (!header.getMagic().equals(DexConstants.MAGIC)) {
throw new IOException("Unable to open file: invalid DEX file!");
}
BinaryReader reader = new BinaryReader(provider, true);
int stringTableOffset = header.getStringTableOffset();
String classNameArr[] = new String[header.getClassTableSize()];
long last = 0;
int stringAddress;
GFile file;
DexClass myClass;
for(int i = 0; i < header.getClassTableSize(); i++){
if(i == 0){
//Get offset of Class Name in String Table
last = header.getClassTableOffset();
//Reads from String Table to get address of class Name
stringAddress = reader.readInt((reader.readInt(last+16))*4 + stringTableOffset);
//Reads String at String Address and stores in Array
classNameArr[i] = reader.readAsciiString(stringAddress+1);
monitor.setMessage(classNameArr[i]);
file = new GFile(this, root, classNameArr[i], false, header.getClassTableSize());
myClass = new DexClass(reader.readInt(last), reader.readInt(last+4), reader.readInt(last+8), reader.readInt(last+12), reader.readInt(last+16), reader.readInt(last+20), reader.readInt(last+24), reader.readInt(last+28));
storeFile(file, myClass);
}else{
last = last + 32;
stringAddress = reader.readInt((reader.readInt(last+16))*4 + stringTableOffset);
classNameArr[i] = reader.readAsciiString(stringAddress+1);
monitor.setMessage(classNameArr[i]);
file = new GFile(this, root, classNameArr[i], false, header.getClassTableSize());
myClass = new DexClass(reader.readInt(last), reader.readInt(last+4), reader.readInt(last+8), reader.readInt(last+12), reader.readInt(last+16), reader.readInt(last+20), reader.readInt(last+24), reader.readInt(last+28));
storeFile(file, myClass);
}
}
/*
int typeTableOffset = header.getTypeTableOffset();
int classTypeOffset = 0x2d * 4 + typeTableOffset;
int classNameStringID = 0;
*/

View file

@ -0,0 +1,116 @@
/* ###
* 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.dex.analyzer;
import java.io.IOException;
import java.util.List;
import java.util.TreeMap;
import ghidra.app.plugin.core.analysis.AnalysisState;
import ghidra.app.plugin.core.analysis.AnalysisStateInfo;
import ghidra.app.util.bin.*;
import ghidra.file.formats.android.dex.format.*;
import ghidra.file.formats.android.dex.util.DexUtil;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressSpace;
import ghidra.program.model.listing.Program;
/**
* This class is used to cache the {@link DexHeader} which holds constant pool and method information.
* These do not change for a given .dex Program and can be shared (by this {@link AnalysisState})
* between plug-ins that need to do analysis.
*/
final public class DexAnalysisState implements AnalysisState {
private Program program;
private DexHeader header; // Collection of raw records parsed from .dex file
private TreeMap<Address, EncodedMethod> methodMap; // Cached map from Address -> EncodedMethod
public DexAnalysisState(Program program, DexHeader header) {
this.program = program;
this.header = header;
}
/**
* Calculate the Address for every method in the list and add an entry to -methodMap-
* @param defaultAddressSpace is the AddressSpace all encoded offsets are relative to
* @param methodList is the list of encoded methods
*/
private void installMethodList(AddressSpace defaultAddressSpace,
List<EncodedMethod> methodList) {
for (EncodedMethod encodedMethod : methodList) {
Address methodAddress = defaultAddressSpace.getAddress(
DexUtil.METHOD_ADDRESS + encodedMethod.getCodeOffset());
methodMap.put(methodAddress, encodedMethod);
}
}
/**
* Calculate and cache a map from Address to the corresponding EncodedMethod object
* for all methods in the Program
*/
private void buildMethodMap() {
methodMap = new TreeMap<>();
AddressSpace defaultAddressSpace = program.getAddressFactory().getDefaultAddressSpace();
for (ClassDefItem item : header.getClassDefs()) {
ClassDataItem classDataItem = item.getClassDataItem();
if (classDataItem == null) {
continue;
}
installMethodList(defaultAddressSpace, classDataItem.getDirectMethods());
installMethodList(defaultAddressSpace, classDataItem.getVirtualMethods());
}
}
/**
* @return the {@link DexHeader} containing raw constant pool and method records
*/
public DexHeader getHeader() {
return header;
}
/**
* Retrieve the EncodedMethod object, given its address
* @param addr is the Address of the method
* @return the EncodedMethod
*/
public EncodedMethod getEncodedMethod(Address addr) {
if (methodMap == null) {
buildMethodMap();
}
return methodMap.get(addr);
}
/**
* Return persistent <code>DexAnalysisState</code> which corresponds to the specified program instance.
* @param program
* @return <code>DexAnalysisState</code> for specified program instance
*/
public static DexAnalysisState getState(Program program) throws IOException {
DexAnalysisState analysisState =
AnalysisStateInfo.getAnalysisState(program, DexAnalysisState.class);
if (analysisState == null) {
ByteProvider provider =
new MemoryByteProvider(program.getMemory(), program.getMinAddress());
BinaryReader reader = new BinaryReader(provider, true);
DexHeader dexHeader = new DexHeader(reader);
analysisState = new DexAnalysisState(program, dexHeader);
AnalysisStateInfo.putAnalysisState(program, analysisState);
}
return analysisState;
}
}

View file

@ -0,0 +1,123 @@
/* ###
* 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.dex.analyzer;
import ghidra.app.services.AnalysisPriority;
import ghidra.app.services.AnalyzerType;
import ghidra.app.util.bin.ByteProvider;
import ghidra.app.util.bin.MemoryByteProvider;
import ghidra.app.util.importer.MessageLog;
import ghidra.file.analyzers.FileFormatAnalyzer;
import ghidra.file.formats.android.dex.format.DexConstants;
import ghidra.file.formats.android.dex.util.DexUtil;
import ghidra.program.model.address.*;
import ghidra.program.model.data.AlignmentDataType;
import ghidra.program.model.listing.Program;
import ghidra.program.model.mem.MemoryBlock;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor;
public class DexCondenseFillerBytesAnalyzer extends FileFormatAnalyzer {
@Override
public boolean analyze( Program program, AddressSetView set, TaskMonitor monitor, MessageLog log ) throws Exception {
AlignmentDataType alignmentDataType = new AlignmentDataType( );
Address address = toAddr( program, DexUtil.METHOD_ADDRESS );
MemoryBlock block = program.getMemory().getBlock( address );
if ( block == null ) {
log.appendMsg( "Can't locate block with method byte code!" );
return false;
}
AddressSet blockSet = new AddressSet( block.getStart( ), block.getEnd( ) );
AddressSetView undefinedSet = program.getListing().getUndefinedRanges( blockSet, true, monitor );
monitor.setMaximum( undefinedSet.getNumAddressRanges() );
monitor.setProgress( 0 );
monitor.setMessage( "DEX: condensing filler bytes" );
AddressRangeIterator addressRanges = undefinedSet.getAddressRanges();
while ( addressRanges.hasNext( ) ) {
monitor.checkCanceled( );
monitor.incrementProgress( 1 );
AddressRange addressRange = addressRanges.next();
if ( isRangeAllSameBytes( program, addressRange, (byte) 0xff, monitor ) ) {
program.getListing().createData( addressRange.getMinAddress(), alignmentDataType, (int)addressRange.getLength() );
}
}
//collapseFillerBytes( program, monitor );
return true;
}
@Override
public boolean canAnalyze( Program program ) {
ByteProvider provider = new MemoryByteProvider( program.getMemory( ), program.getMinAddress( ) );
return DexConstants.isDexFile( provider );
}
@Override
public AnalyzerType getAnalysisType( ) {
return AnalyzerType.BYTE_ANALYZER;
}
@Override
public boolean getDefaultEnablement( Program program ) {
return true;
}
@Override
public String getDescription( ) {
return "Condenses all filler bytes in a DEX file";
}
@Override
public String getName( ) {
return "Android DEX Condense Filler Bytes";
}
@Override
public AnalysisPriority getPriority( ) {
return new AnalysisPriority( Integer.MAX_VALUE );
}
@Override
public boolean isPrototype( ) {
return false;
}
private boolean isRangeAllSameBytes( Program program, AddressRange addressRange, byte value, TaskMonitor monitor ) throws CancelledException {
byte [] bytes = new byte[ (int) addressRange.getLength() ];
try {
program.getMemory().getBytes( addressRange.getMinAddress(), bytes ) ;
}
catch ( Exception e ) {
return false;
//ignore
}
for ( byte b : bytes ) {
monitor.checkCanceled( );
if ( b != value ) {
return false;
}
}
return true;
}
}

View file

@ -0,0 +1,187 @@
/* ###
* 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.dex.analyzer;
import java.util.List;
import ghidra.app.cmd.disassemble.DisassembleCommand;
import ghidra.app.services.AnalysisPriority;
import ghidra.app.services.AnalyzerType;
import ghidra.app.util.bin.ByteProvider;
import ghidra.app.util.bin.MemoryByteProvider;
import ghidra.app.util.importer.MessageLog;
import ghidra.file.analyzers.FileFormatAnalyzer;
import ghidra.file.formats.android.dex.format.*;
import ghidra.file.formats.android.dex.util.DexUtil;
import ghidra.program.model.address.*;
import ghidra.program.model.listing.Program;
import ghidra.program.model.symbol.Namespace;
import ghidra.program.model.symbol.SourceType;
import ghidra.util.Msg;
import ghidra.util.task.TaskMonitor;
public class DexExceptionHandlersAnalyzer extends FileFormatAnalyzer {
@Override
public boolean analyze(Program program, AddressSetView set, TaskMonitor monitor, MessageLog log)
throws Exception {
monitor.setMessage("DEX: exception handler markup");
AddressSet disassembleSet = new AddressSet();
disassembleSet.add(computeExceptionSet(program, monitor));
DisassembleCommand dCommand = new DisassembleCommand(disassembleSet, null, true);
dCommand.applyTo(program, monitor);
return true;
}
@Override
public boolean canAnalyze(Program program) {
ByteProvider provider =
new MemoryByteProvider(program.getMemory(), program.getMinAddress());
return DexConstants.isDexFile(provider);
}
@Override
public AnalyzerType getAnalysisType() {
return AnalyzerType.BYTE_ANALYZER;
}
@Override
public boolean getDefaultEnablement(Program program) {
return true;
}
@Override
public String getDescription() {
return "Disassembles the exception handlers in a DEX file";
}
@Override
public String getName() {
return "Android DEX Exception Handlers";
}
@Override
public AnalysisPriority getPriority() {
return new AnalysisPriority(Integer.MAX_VALUE - 1);
}
@Override
public boolean isPrototype() {
return false;
}
private AddressSetView computeExceptionSet(Program program, TaskMonitor monitor)
throws Exception {
AddressSet set = new AddressSet();
DexHeader header = null;
DexAnalysisState analysisState = DexAnalysisState.getState(program);
header = analysisState.getHeader();
Address address = toAddr(program, DexUtil.METHOD_ADDRESS);
for (ClassDefItem item : header.getClassDefs()) {
monitor.checkCanceled();
monitor.incrementProgress(1);
ClassDataItem classDataItem = item.getClassDataItem();
if (classDataItem == null) {
continue;
}
set.add(processMethods(program, address, header, item, classDataItem.getDirectMethods(),
monitor));
set.add(processMethods(program, address, header, item,
classDataItem.getVirtualMethods(), monitor));
}
return set;
}
private AddressSetView processMethods(Program program, Address baseAddress, DexHeader header,
ClassDefItem item, List<EncodedMethod> methods, TaskMonitor monitor) throws Exception {
AddressSet set = new AddressSet();
monitor.setMaximum(methods.size());
monitor.setProgress(0);
for (int i = 0; i < methods.size(); ++i) {
monitor.checkCanceled();
monitor.incrementProgress(1);
EncodedMethod method = methods.get(i);
Address codeAddress = baseAddress.add(method.getCodeOffset());
CodeItem codeItem = method.getCodeItem();
if (codeItem == null) {
continue;
}
// for ( TryItem tryItem : codeItem.getTries( ) ) {
// monitor.checkCanceled( );
//
// Address tryAddress = codeAddress.add( tryItem.getStartAddress( ) );
// set.add( tryAddress );
// }
EncodedCatchHandlerList handlerList = codeItem.getHandlerList();
if (handlerList == null) {
continue;
}
for (EncodedCatchHandler handler : handlerList.getHandlers()) {
monitor.checkCanceled();
List<EncodedTypeAddressPair> pairs = handler.getPairs();
for (EncodedTypeAddressPair pair : pairs) {
monitor.checkCanceled();
int catchTypeIndex = pair.getTypeIndex();
TypeIDItem catchTypeIDItem = header.getTypes().get(catchTypeIndex);
StringIDItem catchStringItem =
header.getStrings().get(catchTypeIDItem.getDescriptorIndex());
String catchString = catchStringItem.getStringDataItem().getString();
Address catchAddress = codeAddress.add(pair.getAddress() * 2);
createCatchSymbol(program, catchString, catchAddress);
set.add(catchAddress);
}
if (handler.getSize() <= 0) {
Address catchAllAddress = codeAddress.add(handler.getCatchAllAddress() * 2);
createCatchSymbol(program, "CatchAll", catchAllAddress);
set.add(catchAllAddress);
}
}
}
return set;
}
private void createCatchSymbol(Program program, String catchName, Address catchAddress) {
Namespace catchNameSpace = DexUtil.getOrCreateNameSpace(program, "CatchHandlers");
try {
program.getSymbolTable().createLabel(catchAddress, catchName, catchNameSpace,
SourceType.ANALYSIS);
}
catch (Exception e) {
Msg.error(this, "Error creating label", e);
}
}
}

View file

@ -0,0 +1,987 @@
/* ###
* 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.dex.analyzer;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import ghidra.app.cmd.disassemble.DisassembleCommand;
import ghidra.app.services.AnalysisPriority;
import ghidra.app.services.AnalyzerType;
import ghidra.app.util.bin.ByteProvider;
import ghidra.app.util.bin.MemoryByteProvider;
import ghidra.app.util.importer.MessageLog;
import ghidra.file.analyzers.FileFormatAnalyzer;
import ghidra.file.formats.android.dex.format.*;
import ghidra.file.formats.android.dex.util.DexUtil;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressSetView;
import ghidra.program.model.data.*;
import ghidra.program.model.lang.*;
import ghidra.program.model.listing.*;
import ghidra.program.model.listing.Function.FunctionUpdateType;
import ghidra.program.model.mem.Memory;
import ghidra.program.model.mem.MemoryBlock;
import ghidra.program.model.symbol.*;
import ghidra.util.exception.*;
import ghidra.util.task.TaskMonitor;
public class DexHeaderFormatAnalyzer extends FileFormatAnalyzer {
@Override
public boolean analyze(Program program, AddressSetView set, TaskMonitor monitor, MessageLog log)
throws Exception {
Address startAddress = toAddr(program, 0x0);
if (getDataAt(program, startAddress) != null) {
log.appendMsg("data already exists.");
return true;
}
Memory memory = program.getMemory();
MemoryBlock block = memory.getBlock(startAddress);
block.setRead(true);
block.setWrite(false);
block.setExecute(false);
DexAnalysisState analysisState = DexAnalysisState.getState(program);
DexHeader header = analysisState.getHeader();
processHeader(program, header);
createInitialFragments(program, header, monitor);
BasicCompilerSpec.enableJavaLanguageDecompilation(program);
createNamespaces(program, header, monitor, log);
processMap(program, header, monitor, log);
processStrings(program, header, monitor, log);
processTypes(program, header, monitor, log);
processPrototypes(program, header, monitor, log);
processFields(program, header, monitor, log);
processMethods(program, header, monitor, log);
processClassDefs(program, header, monitor, log);
createProgramDataTypes(program, header, monitor, log);
createMethods(program, header, monitor, log);
monitor.setMessage("DEX: cleaning up tree");
removeEmptyFragments(program);
return true;
}
@Override
public boolean canAnalyze(Program program) {
ByteProvider provider =
new MemoryByteProvider(program.getMemory(), program.getMinAddress());
return DexConstants.isDexFile(provider);
}
@Override
public AnalyzerType getAnalysisType() {
return AnalyzerType.BYTE_ANALYZER;
}
@Override
public boolean getDefaultEnablement(Program program) {
return true;
}
@Override
public String getDescription() {
return "Android DEX Header Format";
}
@Override
public String getName() {
return "Android DEX Header Format";
}
@Override
public AnalysisPriority getPriority() {
return new AnalysisPriority(0);
}
@Override
public boolean isPrototype() {
return false;
}
private void createNamespaces(Program program, DexHeader header, TaskMonitor monitor,
MessageLog log) throws Exception {
monitor.setMessage("DEX: creating namespaces");
monitor.setMaximum(header.getClassDefsIdsSize());
monitor.setProgress(0);
// NOTE:
// MUST CREATE ALL OF THE CLASSES AND NAMESPACES FIRST
// OTHERWISE GHIDRA CANNOT HANDLE OBFUSCATED PACKAGES NAMES
// FOR EXAMPLE, "a.a.a.a" and "a.a.a" WHERE THE LAST A IS A METHOD
for (ClassDefItem item : header.getClassDefs()) {
monitor.checkCanceled();
monitor.incrementProgress(1);
String className = DexUtil.convertTypeIndexToString(header, item.getClassIndex());
Namespace classNameSpace =
DexUtil.createNameSpaceFromMangledClassName(program, className);
if (classNameSpace == null) {
log.appendMsg("Failed to create namespace: " + className);
}
}
}
private void createMethods(Program program, DexHeader header, TaskMonitor monitor,
MessageLog log) throws Exception {
monitor.setMessage("DEX: creating methods");
monitor.setMaximum(header.getClassDefsIdsSize());
monitor.setProgress(0);
for (ClassDefItem item : header.getClassDefs()) {
monitor.checkCanceled();
monitor.incrementProgress(1);
ClassDataItem classDataItem = item.getClassDataItem();
if (classDataItem == null) {
continue;
}
createMethods(program, header, item, classDataItem.getDirectMethods(), monitor, log);
createMethods(program, header, item, classDataItem.getVirtualMethods(), monitor, log);
}
}
private void createMethods(Program program, DexHeader header, ClassDefItem item,
List<EncodedMethod> methods, TaskMonitor monitor, MessageLog log) throws Exception {
String className = DexUtil.convertTypeIndexToString(header, item.getClassIndex());
Namespace classNameSpace = DexUtil.createNameSpaceFromMangledClassName(program, className);
if (classNameSpace == null) {
log.appendMsg("No namespace: Skipping methods for " + className);
return;
}
for (int i = 0; i < methods.size(); ++i) {
monitor.checkCanceled();
EncodedMethod encodedMethod = methods.get(i);
MethodIDItem methodID = header.getMethods().get(encodedMethod.getMethodIndex());
String methodName = DexUtil.convertToString(header, methodID.getNameIndex());
if ((AccessFlags.ACC_CONSTRUCTOR & encodedMethod.getAccessFlags()) != 0) {
methodName = classNameSpace.getName();
}
CodeItem codeItem = encodedMethod.getCodeItem();
if (codeItem == null) {//external
// Address externalAddress = toAddr( program, DexUtil.EXTERNAL_ADDRESS + ( 4 * methodIndex ) );
// createMethodSymbol( program, externalAddress, methodName, classNameSpace );
// createMethodComment( program, externalAddress, header, item, methodID, encodedMethod, codeItem, monitor );
// createData( program, externalAddress, new PointerDataType( ) );
// Function method = createFunction( program, externalAddress );
// method.setCustomVariableStorage( true );
//
// Address methodIndexAddress = toAddr( program, DexUtil.LOOKUP_ADDRESS + ( methodIndex * 4 ) );
// Symbol primarySymbol = program.getSymbolTable().getPrimarySymbol( methodIndexAddress );
// program.getReferenceManager().addExternalReference( methodIndexAddress, (Namespace) null, primarySymbol.getName( ), null, SourceType.ANALYSIS, 0, RefType.EXTERNAL_REF );
}
else {
Address methodAddress =
toAddr(program, DexUtil.METHOD_ADDRESS + encodedMethod.getCodeOffset());
createMethodSymbol(program, methodAddress, methodName, classNameSpace, log);
createMethodComment(program, methodAddress, header, item, methodID, encodedMethod,
codeItem, monitor);
disassembleMethod(program, header, className, encodedMethod.isStatic(),
methodAddress, methodID, codeItem, monitor, log);
}
}
}
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;
}
}
private void createMethodComment(Program program, Address methodAddress, DexHeader header,
ClassDefItem item, MethodIDItem methodID, EncodedMethod encodedMethod,
CodeItem codeItem, TaskMonitor monitor) throws CancelledException {
String methodSignature =
DexUtil.convertPrototypeIndexToString(header, methodID.getProtoIndex());
StringBuilder commentBuilder = new StringBuilder();
commentBuilder.append(item.toString(header, -1, monitor) + "\n");
commentBuilder.append("Method Signature: " + methodSignature + "\n");
commentBuilder.append("Method Access Flags:\n");
commentBuilder.append(AccessFlags.toString(encodedMethod.getAccessFlags()) + "\n");
if (codeItem != null) {
commentBuilder.append("Method Register Size: " + codeItem.getRegistersSize() + "\n");
commentBuilder.append("Method Incoming Size: " + codeItem.getIncomingSize() + "\n");
commentBuilder.append("Method Outgoing Size: " + codeItem.getOutgoingSize() + "\n");
commentBuilder.append("Method Debug Info Offset: 0x" +
Integer.toHexString(codeItem.getDebugInfoOffset()) + "\n");
}
commentBuilder.append(
"Method ID Offset: 0x" + Long.toHexString(methodID.getFileOffset()) + "\n");
setPlateComment(program, methodAddress, commentBuilder.toString());
}
private void disassembleMethod(Program program, DexHeader header, String className,
boolean isStatic, Address methodAddress, MethodIDItem methodID, CodeItem codeItem,
TaskMonitor monitor, MessageLog log) throws CancelledException {
Language language = program.getLanguage();
DisassembleCommand dCommand = new DisassembleCommand(methodAddress, null, true);
dCommand.applyTo(program);
Function method = createFunction(program, methodAddress);
if (method == null) {
log.appendMsg("Failed to create method at " + methodAddress);
return;
}
int registerIndex = codeItem.getRegistersSize() - codeItem.getIncomingSize();
//TODO create local variables in between
for (int i = 0; i < registerIndex; ++i) {
DataType localDataType = null;//TODO
Register localRegister = language.getRegister("v" + i);
try {
LocalVariableImpl local =
new LocalVariableImpl("local_" + i, 0, localDataType, localRegister, program);
method.addLocalVariable(local, SourceType.ANALYSIS);
}
catch (Exception e) {
log.appendException(e);
}
}
Variable returnVar = null;
ArrayList<Variable> paramList = new ArrayList<>();
int prototypeIndex = methodID.getProtoIndex() & 0xffff;
PrototypesIDItem prototype = header.getPrototypes().get(prototypeIndex);
try {
String returnTypeString =
DexUtil.convertTypeIndexToString(header, prototype.getReturnTypeIndex());
DataType returnDataType =
DexUtil.toDataType(program.getDataTypeManager(), returnTypeString);
returnVar = new ReturnParameterImpl(returnDataType, program);
if (!isStatic) {
String classString =
DexUtil.convertTypeIndexToString(header, methodID.getClassIndex());
DataType thisDataType =
DexUtil.toDataType(program.getDataTypeManager(), classString);
String parameterName = "this";
Variable param = new ParameterImpl(parameterName, thisDataType, program);
paramList.add(param);
}
TypeList parameters = prototype.getParameters();
if (parameters != null) {
for (TypeItem parameterTypeItem : parameters.getItems()) {
monitor.checkCanceled();
String parameterTypeString =
DexUtil.convertTypeIndexToString(header, parameterTypeItem.getType());
DataType parameterDataType =
DexUtil.toDataType(program.getDataTypeManager(), parameterTypeString);
String parameterName =
getParameterName(header, codeItem, paramList.size() - (isStatic ? 0 : 1));
if (parameterName == null) {
parameterName = "p" + paramList.size();
}
Variable param = new ParameterImpl(parameterName, parameterDataType, program);
paramList.add(param);
}
}
method.updateFunction("__stdcall", returnVar, paramList,
FunctionUpdateType.DYNAMIC_STORAGE_ALL_PARAMS, true, SourceType.ANALYSIS);
}
catch (InvalidInputException ex) {
log.appendException(ex);
}
catch (DuplicateNameException ex) {
log.appendException(ex);
}
}
private String getParameterName(DexHeader header, CodeItem codeItem, int parameterOrdinal) {
try {
DebugInfoItem debugInfo = codeItem.getDebugInfo();
int[] debugParameterNames = debugInfo.getParameterNames();
List<StringIDItem> strings = header.getStrings();
StringIDItem stringIDItem = strings.get(debugParameterNames[parameterOrdinal]);
StringDataItem stringDataItem = stringIDItem.getStringDataItem();
return stringDataItem.getString();
}
catch (Exception e) {
// IndexOutOfBoundsException
}
return null;
}
private void processHeader(Program program, DexHeader header) throws Exception {
Address headerAddress = toAddr(program, 0x0);
DataType headerDataType = header.toDataType();
createData(program, headerAddress, headerDataType);
createFragment(program, "header", headerAddress,
headerAddress.add(headerDataType.getLength()));
}
private void processClassDefs(Program program, DexHeader header, TaskMonitor monitor,
MessageLog log) throws Exception {
monitor.setMessage("DEX: processing class definitions");
monitor.setMaximum(header.getClassDefsIdsSize());
monitor.setProgress(0);
Address address = toAddr(program, header.getClassDefsIdsOffset());
int index = 0;
for (ClassDefItem item : header.getClassDefs()) {
monitor.checkCanceled();
monitor.incrementProgress(1);
DataType dataType = item.toDataType();
createData(program, address, dataType);
createFragment(program, "classes", address, address.add(dataType.getLength()));
createClassDefSymbol(program, header, item, address);
processClassInterfaces(program, header, item, monitor);
processClassAnnotations(program, item, monitor, log);
processClassDataItem(program, header, item, monitor);
processClassStaticValues(program, header, item, monitor);
setPlateComment(program, address, item.toString(header, index, monitor));
address = address.add(dataType.getLength());
++index;
}
}
private void createProgramDataTypes(Program program, DexHeader header, TaskMonitor monitor,
MessageLog log) throws CancelledException {
monitor.setMessage("DEX: creating program datatypes");
monitor.setMaximum(header.getTypeIdsSize());
monitor.setProgress(0);
DataTypeManager dtm = program.getDataTypeManager();
int curGroup = -1;
CategoryPath handlePath = null;
List<TypeIDItem> types = header.getTypes();
for (int typeID = 0; typeID < header.getTypeIdsSize(); ++typeID) {
TypeIDItem item = types.get(typeID);
monitor.checkCanceled();
monitor.incrementProgress(1);
String name = DexUtil.convertToString(header, item.getDescriptorIndex());
String[] path = DexUtil.convertClassStringToPathArray(DexUtil.CATEGORY_PATH, name);
if (path == null) {
continue;
}
StringBuilder builder = new StringBuilder();
for (int i = 0; i < path.length - 1; ++i) {
builder.append(CategoryPath.DELIMITER_CHAR);
builder.append(path[i]);
}
CategoryPath catPath = new CategoryPath(builder.toString());
DataType dataType =
new TypedefDataType(catPath, path[path.length - 1], DWordDataType.dataType);
dataType = dtm.resolve(dataType, DataTypeConflictHandler.DEFAULT_HANDLER);
// Create unchanging typedef to each class based on typeID, so we can find class type even if name changes
if (typeID / 100 != curGroup) {
curGroup = typeID / 100;
builder = new StringBuilder();
builder.append(DexUtil.HANDLE_PATH);
builder.append("group").append(curGroup);
handlePath = new CategoryPath(builder.toString());
}
DataType handleType = new TypedefDataType(handlePath, "type" + typeID, dataType);
dtm.resolve(handleType, DataTypeConflictHandler.DEFAULT_HANDLER);
}
}
private void createClassDefSymbol(Program program, DexHeader header, ClassDefItem item,
Address address) {
String className = DexUtil.convertTypeIndexToString(header, item.getClassIndex());
SymbolTable symbolTable = program.getSymbolTable();
try {
Namespace nameSpace = DexUtil.createNameSpaceFromMangledClassName(program, className);
if (nameSpace != null) {
symbolTable.createLabel(address, DexUtil.CLASSDEF_NAME, nameSpace,
SourceType.ANALYSIS);
}
}
catch (Exception ex) {
// Don't worry if we can't laydown this symbol
}
}
private void processClassStaticValues(Program program, DexHeader header, ClassDefItem item,
TaskMonitor monitor) throws DuplicateNameException, IOException, Exception {
if (item.getStaticValuesOffset() > 0) {
EncodedArrayItem staticValues = item.getStaticValues();
Address staticAddress = toAddr(program, item.getStaticValuesOffset());
DataType staticDataType = staticValues.toDataType();
createData(program, staticAddress, staticDataType);
createFragment(program, "class_static_values", staticAddress,
staticAddress.add(staticDataType.getLength()));
StringBuilder builder = new StringBuilder();
builder.append("Class: " +
DexUtil.convertTypeIndexToString(header, item.getClassIndex()) + "\n\n");
builder.append("Static Values:" + "\n");
for (byte b : staticValues.getArray().getValues()) {
builder.append(Integer.toHexString(b & 0xff) + " ");
}
setPlateComment(program, staticAddress, builder.toString());
}
}
private void processClassDataItem(Program program, DexHeader header, ClassDefItem item,
TaskMonitor monitor) throws DuplicateNameException, IOException, Exception {
if (item.getClassDataOffset() > 0) {
ClassDataItem classDataItem = item.getClassDataItem();
Address classDataAddress = toAddr(program, item.getClassDataOffset());
DataType classDataDataType = classDataItem.toDataType();
createData(program, classDataAddress, classDataDataType);
createFragment(program, "class_data", classDataAddress,
classDataAddress.add(classDataDataType.getLength()));
StringBuilder builder = new StringBuilder();
builder.append("Class: " +
DexUtil.convertTypeIndexToString(header, item.getClassIndex()) + "\n\n");
builder.append("Static Fields: " + classDataItem.getStaticFieldsSize() + "\n");
builder.append("Instance Fields: " + classDataItem.getInstanceFieldsSize() + "\n");
builder.append("Direct Methods: " + classDataItem.getDirectMethodsSize() + "\n");
builder.append("Virtual Methods: " + classDataItem.getVirtualMethodsSize() + "\n");
processEncodedFields(program, header, classDataItem.getStaticFields(), monitor);
processEncodedFields(program, header, classDataItem.getInstancesFields(), monitor);
processEncodedMethods(program, header, item, classDataItem.getDirectMethods(), monitor);
processEncodedMethods(program, header, item, classDataItem.getVirtualMethods(),
monitor);
setPlateComment(program, classDataAddress, builder.toString());
}
}
private void processEncodedFields(Program program, DexHeader header,
List<EncodedField> instanceFields, TaskMonitor monitor) throws Exception {
int index = 0;
for (int i = 0; i < instanceFields.size(); ++i) {
monitor.checkCanceled();
EncodedField field = instanceFields.get(i);
int diff = field.getFieldIndexDifference();
if (i == 0) {
index = diff;
}
else {
index += diff;
}
FieldIDItem fieldID = header.getFields().get(index);
StringBuilder builder = new StringBuilder();
builder.append(DexUtil.convertToString(header, fieldID.getNameIndex()) + "\n");
builder.append(AccessFlags.toString(field.getAccessFlags()) + "\n");
builder.append("\n");
Address address = toAddr(program, field.getFileOffset());
DataType dataType = field.toDataType();
createData(program, address, dataType);
setPlateComment(program, address, builder.toString());
createFragment(program, "encoded_fields", address, address.add(dataType.getLength()));
}
}
private void processEncodedMethods(Program program, DexHeader header, ClassDefItem item,
List<EncodedMethod> methods, TaskMonitor monitor) throws Exception {
for (int i = 0; i < methods.size(); ++i) {
monitor.checkCanceled();
EncodedMethod method = methods.get(i);
MethodIDItem methodID = header.getMethods().get(method.getMethodIndex());
StringBuilder builder = new StringBuilder();
builder.append(
"Method Name: " + DexUtil.convertToString(header, methodID.getNameIndex()) + "\n");
builder.append("Method Offset: 0x" + Long.toHexString(methodID.getFileOffset()) + "\n");
builder.append("Method Flags:\n");
builder.append(AccessFlags.toString(method.getAccessFlags()) + "\n");
builder.append("Code Offset: 0x" + Integer.toHexString(method.getCodeOffset()) + "\n");
builder.append("\n");
Address address = toAddr(program, method.getFileOffset());
DataType dataType = method.toDataType();
createData(program, address, dataType);
setPlateComment(program, address, builder.toString());
createFragment(program, "encoded_methods", address, address.add(dataType.getLength()));
processCodeItem(program, header, item, method, methodID);
}
}
private void processCodeItem(Program program, DexHeader header, ClassDefItem item,
EncodedMethod method, MethodIDItem methodID)
throws DuplicateNameException, IOException, Exception {
if (method.getCodeOffset() > 0) {
Address codeAddress = toAddr(program, method.getCodeOffset());
StringBuilder builder = new StringBuilder();
builder.append(DexUtil.convertTypeIndexToString(header, item.getClassIndex()) + " " +
DexUtil.convertToString(header, methodID.getNameIndex()) + "\n");
setPlateComment(program, codeAddress, builder.toString());
CodeItem codeItem = method.getCodeItem();
DataType codeItemDataType = codeItem.toDataType();
try {
createData(program, codeAddress, codeItemDataType);
int codeItemDataTypeLength = codeItemDataType.getLength();
createFragment(program, "code_item", codeAddress,
codeAddress.add(codeItemDataTypeLength));
Address tempAddress = codeAddress.add(codeItemDataTypeLength);
tempAddress = processCodeItemTrys(program, tempAddress, codeItem);
processCodeItemHandlers(program, codeItem, tempAddress);
}
catch (Exception e) {
//happens when "padding" member has been removed, so struct won't fit
//just ignore it
}
if (codeItem.getDebugInfoOffset() > 0) {
Address debugAddress = toAddr(program, codeItem.getDebugInfoOffset());
DebugInfoItem debug = codeItem.getDebugInfo();
DataType debugDataType = debug.toDataType();
createData(program, debugAddress, debugDataType);
createFragment(program, "debug_info", debugAddress,
debugAddress.add(debugDataType.getLength()));
}
}
}
private void processCodeItemHandlers(Program program, CodeItem codeItem, Address tempAddress)
throws DuplicateNameException, IOException, Exception {
EncodedCatchHandlerList handlerList = codeItem.getHandlerList();
if (handlerList == null) {
return;
}
DataType handlerDataType = handlerList.toDataType();
createData(program, tempAddress, handlerDataType);
createFragment(program, "handlers", tempAddress,
tempAddress.add(handlerDataType.getLength()));
tempAddress = tempAddress.add(handlerDataType.getLength());
for (EncodedCatchHandler handler : handlerList.getHandlers()) {
DataType dataType = handler.toDataType();
createData(program, tempAddress, dataType);
createFragment(program, "handlers", tempAddress, tempAddress.add(dataType.getLength()));
tempAddress = tempAddress.add(dataType.getLength());
}
}
private Address processCodeItemTrys(Program program, Address codeAddress, CodeItem codeItem)
throws DuplicateNameException, IOException, Exception {
Address tempAddress = codeAddress;
for (TryItem tryItem : codeItem.getTries()) {
DataType dataType = tryItem.toDataType();
createData(program, tempAddress, dataType);
createFragment(program, "try", tempAddress, tempAddress.add(dataType.getLength()));
tempAddress = tempAddress.add(dataType.getLength());
}
return tempAddress;
}
private void processClassAnnotations(Program program, ClassDefItem item, TaskMonitor monitor,
MessageLog log)
throws DuplicateNameException, IOException, Exception, CancelledException {
if (item.getAnnotationsOffset() > 0) {
AnnotationsDirectoryItem annotationsDirectoryItem = item.getAnnotationsDirectoryItem();
Address annotationsAddress = toAddr(program, item.getAnnotationsOffset());
DataType annotationsDataType = annotationsDirectoryItem.toDataType();
createData(program, annotationsAddress, annotationsDataType);
createFragment(program, "annotations", annotationsAddress,
annotationsAddress.add(annotationsDataType.getLength()));
if (annotationsDirectoryItem.getClassAnnotationsOffset() > 0) {
Address classAddress =
toAddr(program, annotationsDirectoryItem.getClassAnnotationsOffset());
AnnotationSetItem setItem = annotationsDirectoryItem.getClassAnnotations();
DataType setItemDataType = setItem.toDataType();
createData(program, classAddress, setItemDataType);
createFragment(program, "class_annotations", classAddress,
classAddress.add(setItemDataType.getLength()));
processAnnotationSetItem(program, setItem, monitor, log);
}
for (FieldAnnotation field : annotationsDirectoryItem.getFieldAnnotations()) {
monitor.checkCanceled();
Address fieldAddress = toAddr(program, field.getAnnotationsOffset());
AnnotationSetItem setItem = field.getAnnotationSetItem();
DataType setItemDataType = setItem.toDataType();
createData(program, fieldAddress, setItemDataType);
createFragment(program, "annotation_fields", fieldAddress,
fieldAddress.add(setItemDataType.getLength()));
processAnnotationSetItem(program, setItem, monitor, log);
}
for (MethodAnnotation method : annotationsDirectoryItem.getMethodAnnotations()) {
monitor.checkCanceled();
Address methodAddress = toAddr(program, method.getAnnotationsOffset());
AnnotationSetItem setItem = method.getAnnotationSetItem();
DataType setItemDataType = setItem.toDataType();
createData(program, methodAddress, setItemDataType);
createFragment(program, "annotation_methods", methodAddress,
methodAddress.add(setItemDataType.getLength()));
processAnnotationSetItem(program, setItem, monitor, log);
}
for (ParameterAnnotation parameter : annotationsDirectoryItem.getParameterAnnotations()) {
monitor.checkCanceled();
Address parameterAddress = toAddr(program, parameter.getAnnotationsOffset());
AnnotationSetReferenceList annotationSetReferenceList =
parameter.getAnnotationSetReferenceList();
DataType listDataType = annotationSetReferenceList.toDataType();
createData(program, parameterAddress, listDataType);
createFragment(program, "annotation_parameters", parameterAddress,
parameterAddress.add(listDataType.getLength()));
for (AnnotationSetReferenceItem refItem : annotationSetReferenceList.getItems()) {
AnnotationItem annotationItem = refItem.getItem();
if (annotationItem != null) {
int annotationsItemOffset = refItem.getAnnotationsOffset();
Address annotationItemAddress = toAddr(program, annotationsItemOffset);
DataType annotationItemDataType = annotationItem.toDataType();
createData(program, annotationItemAddress, annotationItemDataType);
createFragment(program, "annotation_item", annotationItemAddress,
annotationItemAddress.add(annotationItemDataType.getLength()));
}
}
}
}
}
private void processClassInterfaces(Program program, DexHeader header, ClassDefItem item,
TaskMonitor monitor) throws Exception {
if (item.getInterfacesOffset() > 0) {
TypeList interfaces = item.getInterfaces();
Address interfaceAddress = toAddr(program, item.getInterfacesOffset());
DataType interfaceDataType = interfaces.toDataType();
createData(program, interfaceAddress, interfaceDataType);
createFragment(program, "interfaces", interfaceAddress,
interfaceAddress.add(interfaceDataType.getLength()));
StringBuilder builder = new StringBuilder();
builder.append("Class: " +
DexUtil.convertTypeIndexToString(header, item.getClassIndex()) + "\n\n");
builder.append("Implements:" + "\n");
for (TypeItem interfaceItem : interfaces.getItems()) {
monitor.checkCanceled();
builder.append("\t" +
DexUtil.convertTypeIndexToString(header, interfaceItem.getType()) + "\n");
}
setPlateComment(program, interfaceAddress, builder.toString());
}
}
private void processAnnotationSetItem(Program program, AnnotationSetItem setItem,
TaskMonitor monitor, MessageLog log) {
try {
for (AnnotationOffsetItem offsetItem : setItem.getItems()) {
monitor.checkCanceled();
Address aAddress = toAddr(program, offsetItem.getAnnotationsOffset());
AnnotationItem aItem = offsetItem.getItem();
DataType aDataType = aItem.toDataType();
createData(program, aAddress, aDataType);
createFragment(program, "annotation_items", aAddress,
aAddress.add(aDataType.getLength()));
}
}
catch (Exception e) {
log.appendException(e);
}
}
private void processMethods(Program program, DexHeader header, TaskMonitor monitor,
MessageLog log) throws Exception {
monitor.setMessage("DEX: processing methods");
monitor.setMaximum(header.getMethodIdsSize());
monitor.setProgress(0);
Address address = toAddr(program, header.getMethodIdsOffset());
int methodIndex = 0;
for (MethodIDItem item : header.getMethods()) {
monitor.checkCanceled();
monitor.incrementProgress(1);
DataType dataType = item.toDataType();
createData(program, address, dataType);
createFragment(program, "methods", address, address.add(dataType.getLength()));
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");
setPlateComment(program, 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(program, externalAddress, methodName,
classNameSpace, log);
if (methodSymbol != null) {
String externalName = methodSymbol.getName(true);
program.getReferenceManager().addExternalReference(methodIndexAddress,
"EXTERNAL.dex", externalName, null, SourceType.ANALYSIS, 0,
RefType.DATA);
}
}
}
createData(program, methodIndexAddress, new PointerDataType());
++methodIndex;
address = address.add(dataType.getLength());
}
}
private void processFields(Program program, DexHeader header, TaskMonitor monitor,
MessageLog log) throws Exception {
monitor.setMessage("DEX: processing fields");
monitor.setMaximum(header.getFieldIdsSize());
monitor.setProgress(0);
Address address = toAddr(program, header.getFieldIdsOffset());
int index = 0;
for (FieldIDItem item : header.getFields()) {
monitor.checkCanceled();
monitor.incrementProgress(1);
DataType dataType = item.toDataType();
createData(program, address, dataType);
createFragment(program, "fields", address, address.add(dataType.getLength()));
StringBuilder builder = new StringBuilder();
builder.append("Field Index: 0x" + Integer.toHexString(index) + "\n");
builder.append(
"Class: " + DexUtil.convertTypeIndexToString(header, item.getClassIndex()) + "\n");
builder.append(
"Type: " + DexUtil.convertTypeIndexToString(header, item.getTypeIndex()) + "\n");
builder.append("Name: " + DexUtil.convertToString(header, item.getNameIndex()) + "\n");
setPlateComment(program, address, builder.toString());
++index;
address = address.add(dataType.getLength());
}
}
private void processPrototypes(Program program, DexHeader header, TaskMonitor monitor,
MessageLog log) throws Exception {
monitor.setMessage("DEX: processing prototypes");
monitor.setMaximum(header.getProtoIdsSize());
monitor.setProgress(0);
Address address = toAddr(program, header.getProtoIdsOffset());
int index = 0;
for (PrototypesIDItem item : header.getPrototypes()) {
monitor.checkCanceled();
monitor.incrementProgress(1);
DataType dataType = item.toDataType();
createData(program, address, dataType);
createFragment(program, "prototypes", address, address.add(dataType.getLength()));
StringBuilder builder = new StringBuilder();
builder.append("Prototype Index: 0x" + Integer.toHexString(index) + "\n");
builder.append(
"Shorty: " + DexUtil.convertToString(header, item.getShortyIndex()) + "\n");
builder.append("Return Type: " +
DexUtil.convertTypeIndexToString(header, item.getReturnTypeIndex()) + "\n");
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()) + " ");
}
DataType parametersDT = parameters.toDataType();
Address parametersAddress = toAddr(program, item.getParametersOffset());
createData(program, parametersAddress, parametersDT);
}
setPlateComment(program, address, builder.toString());
++index;
address = address.add(dataType.getLength());
}
}
private void processTypes(Program program, DexHeader header, TaskMonitor monitor,
MessageLog log) throws Exception {
monitor.setMessage("DEX: processing types");
monitor.setMaximum(header.getTypeIdsSize());
monitor.setProgress(0);
Address address = toAddr(program, header.getTypeIdsOffset());
int index = 0;
for (TypeIDItem item : header.getTypes()) {
monitor.checkCanceled();
monitor.incrementProgress(1);
DataType dataType = item.toDataType();
createData(program, address, dataType);
createFragment(program, "types", address, address.add(dataType.getLength()));
StringBuilder builder = new StringBuilder();
builder.append("Type Index: 0x" + Integer.toHexString(index) + "\n");
builder.append(
"\t" + "->" + DexUtil.convertToString(header, item.getDescriptorIndex()));
setPlateComment(program, address, builder.toString());
++index;
address = address.add(dataType.getLength());
}
}
private void processMap(Program program, DexHeader header, TaskMonitor monitor, MessageLog log)
throws Exception {
MapList mapList = header.getMapList();
if (mapList == null) {
return;
}
monitor.setMessage("DEX: processing map");
monitor.setMaximum(mapList.getSize());
monitor.setProgress(0);
Address mapListAddress = toAddr(program, header.getMapOffset());
DataType mapListDataType = mapList.toDataType();
createData(program, mapListAddress, mapListDataType);
createFragment(program, "map", mapListAddress,
mapListAddress.add(mapListDataType.getLength()));
StringBuilder builder = new StringBuilder();
for (MapItem item : header.getMapList().getItems()) {
monitor.checkCanceled();
builder.append(MapItemTypeCodes.toString(item.getType()) + "\n");
}
setPlateComment(program, mapListAddress, builder.toString());
}
private void createInitialFragments(Program program, DexHeader header, TaskMonitor monitor)
throws Exception {
monitor.setMessage("DEX: creating fragments");
if (header.getDataSize() > 0) {
Address start = toAddr(program, header.getDataOffset());
Address end = start.add(header.getDataSize());
createFragment(program, "data", start, end);
}
}
private void processStrings(Program program, DexHeader header, TaskMonitor monitor,
MessageLog log) throws Exception {
monitor.setMessage("DEX: processing strings");
monitor.setMaximum(header.getStringIdsSize());
monitor.setProgress(0);
Address address = toAddr(program, header.getStringIdsOffset());
int index = 0;
for (StringIDItem item : header.getStrings()) {
monitor.checkCanceled();
monitor.incrementProgress(1);
// markup string data items
Address stringDataAddress = toAddr(program, item.getStringDataOffset());
StringDataItem stringDataItem = item.getStringDataItem();
String string = stringDataItem.getString();
try {
DataType stringDataType = stringDataItem.toDataType();
createData(program, stringDataAddress, stringDataType);
setPlateComment(program, stringDataAddress,
Integer.toHexString(index) + "\n\n" + string);
createFragment(program, "string_data", stringDataAddress,
stringDataAddress.add(stringDataType.getLength()));
createStringSymbol(program, stringDataAddress, string, "strings");
}
catch (DuplicateNameException e) {
log.appendException(e); // Report the exception but keep going
}
catch (InvalidInputException e) {
log.appendException(e);
}
// markup string Id items
DataType dataType = item.toDataType();
try {
createData(program, address, dataType);
createFragment(program, "strings", address, address.add(dataType.getLength()));
setPlateComment(program, address,
"String Index: 0x" + Integer.toHexString(index) + "\n\n" + string);
createStringSymbol(program, address, string, "string_data");
}
catch (DuplicateNameException e) {
log.appendException(e); // Report the exception but keep going
}
catch (InvalidInputException e) {
log.appendException(e);
}
++index;
address = address.add(dataType.getLength());
}
}
private void createStringSymbol(Program program, Address address, String string,
String namespace) {
SymbolTable symbolTable = program.getSymbolTable();
if (string.length() > 0) {
Namespace nameSpace = DexUtil.getOrCreateNameSpace(program, namespace);
String symbolName = SymbolUtilities.replaceInvalidChars(string, true);
if (symbolName.length() > SymbolUtilities.MAX_SYMBOL_NAME_LENGTH) {
symbolName = symbolName.substring(0, SymbolUtilities.MAX_SYMBOL_NAME_LENGTH - 20);
}
try {
symbolTable.createLabel(address, symbolName, nameSpace, SourceType.ANALYSIS);
}
catch (InvalidInputException e) {
// TODO Symbol name matches possible default symbol name: BYTE_0
}
}
}
}

View file

@ -0,0 +1,126 @@
/* ###
* 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.dex.analyzer;
import ghidra.app.services.AnalysisPriority;
import ghidra.app.services.AnalyzerType;
import ghidra.app.util.bin.ByteProvider;
import ghidra.app.util.bin.MemoryByteProvider;
import ghidra.app.util.importer.MessageLog;
import ghidra.file.analyzers.FileFormatAnalyzer;
import ghidra.file.formats.android.dex.format.DexConstants;
import ghidra.file.formats.android.dex.format.DexHeader;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressSetView;
import ghidra.program.model.listing.*;
import ghidra.program.model.scalar.Scalar;
import ghidra.program.model.symbol.*;
import ghidra.util.task.TaskMonitor;
public class DexMarkupDataAnalyzer extends FileFormatAnalyzer {
@Override
public boolean analyze( Program program, AddressSetView set, TaskMonitor monitor, MessageLog log ) throws Exception {
monitor.setMaximum( set == null ? program.getMemory( ).getSize( ) : set.getNumAddresses( ) );
monitor.setProgress( 0 );
DexAnalysisState analysisState = DexAnalysisState.getState(program);
DexHeader header = analysisState.getHeader();
int headerLength = header.toDataType( ).getLength( );
Listing listing = program.getListing( );
DataIterator dataIterator = listing.getDefinedData( set, true );
while ( dataIterator.hasNext( ) ) {
monitor.checkCanceled( );
monitor.incrementProgress( 1 );
Data data = dataIterator.next( );
if ( data.getMinAddress( ).getOffset( ) == 0x0 ) {
continue;// skip the main dex header..
}
monitor.setMessage( "DEX: Data markup ... " + data.getMinAddress( ) );
if ( data.isStructure( ) ) {
processData( data, headerLength, monitor );
}
}
return true;
}
@Override
public boolean canAnalyze( Program program ) {
ByteProvider provider = new MemoryByteProvider( program.getMemory( ), program.getMinAddress( ) );
return DexConstants.isDexFile( provider );
}
@Override
public AnalyzerType getAnalysisType( ) {
return AnalyzerType.DATA_ANALYZER;
}
@Override
public boolean getDefaultEnablement( Program program ) {
return true;
}
@Override
public String getDescription( ) {
return "Android DEX Data Markup";
}
@Override
public String getName( ) {
return "Android DEX Data Markup";
}
@Override
public AnalysisPriority getPriority( ) {
return new AnalysisPriority( 5 );
}
@Override
public boolean isPrototype( ) {
return false;
}
private void processData( Data data, int headerLength, TaskMonitor monitor ) throws Exception {
for ( int i = 0 ; i < data.getNumComponents( ) ; ++i ) {
monitor.checkCanceled( );
Data component = data.getComponent( i );
if ( component.getNumComponents( ) > 0 ) {
processData( component, headerLength, monitor );
}
if ( component.getReferencesFrom( ).length > 0 ) {
continue;
}
if ( component.getFieldName( ).toLowerCase( ).indexOf( "offset" ) != -1 ) {
Scalar scalar = component.getScalar( 0 );
if ( scalar.getUnsignedValue( ) < headerLength ) {// skip low number points into dex header
continue;
}
Address destination = component.getMinAddress( ).getNewAddress( scalar.getUnsignedValue( ) );
Program program = component.getProgram( );
ReferenceManager referenceManager = program.getReferenceManager( );
referenceManager.addMemoryReference( component.getMinAddress( ), destination, RefType.DATA, SourceType.ANALYSIS, 0 );
}
}
}
}

View file

@ -0,0 +1,283 @@
/* ###
* 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.dex.analyzer;
import java.util.List;
import java.util.StringTokenizer;
import ghidra.app.services.AnalysisPriority;
import ghidra.app.services.AnalyzerType;
import ghidra.app.util.bin.*;
import ghidra.app.util.importer.MessageLog;
import ghidra.file.analyzers.FileFormatAnalyzer;
import ghidra.file.formats.android.dex.format.*;
import ghidra.program.model.address.*;
import ghidra.program.model.data.DataType;
import ghidra.program.model.listing.*;
import ghidra.program.model.scalar.Scalar;
import ghidra.program.model.symbol.*;
import ghidra.util.task.TaskMonitor;
public class DexMarkupInstructionsAnalyzer extends FileFormatAnalyzer {
@Override
public boolean analyze( Program program, AddressSetView set, TaskMonitor monitor, MessageLog log ) throws Exception {
monitor.setMaximum( set == null ? program.getMemory( ).getSize( ) : set.getNumAddresses( ) );
monitor.setProgress( 0 );
DexAnalysisState analysisState = DexAnalysisState.getState(program);
DexHeader header = analysisState.getHeader();
// Set-up reader for fill_array_data
ByteProvider provider = new MemoryByteProvider( program.getMemory( ), program.getMinAddress( ) );
BinaryReader reader = new BinaryReader( provider, true );
Listing listing = program.getListing( );
InstructionIterator instructionIterator = listing.getInstructions( set, true );
while ( instructionIterator.hasNext( ) ) {
Instruction instruction = instructionIterator.next( );
monitor.checkCanceled( );
monitor.incrementProgress( 1 );
monitor.setMessage( "DEX: Instruction markup ... " + instruction.getMinAddress( ) );
String mnemonicString = instruction.getMnemonicString( );
if ( mnemonicString.startsWith( "invoke_super_quick" ) ) {
//ignore...
}
else if ( mnemonicString.startsWith( "invoke_virtual_quick" ) ) {
//ignore...
}
else if ( mnemonicString.startsWith( "invoke_object_init_range" ) ) {
//ignore...
}
else if ( mnemonicString.indexOf( "quick" ) > 0 ) {
//ignore...
}
else if ( mnemonicString.startsWith( "const_string" ) ) {
Scalar scalar = instruction.getScalar( 1 );
processString( program, instruction, 1, header, ( int ) scalar.getUnsignedValue( ), log );
}
else if ( mnemonicString.equals( "const_class" ) ) {
Scalar scalar = instruction.getScalar( 1 );
processClass( program, instruction, 1, header, ( int ) scalar.getUnsignedValue( ), log );
}
else if ( mnemonicString.equals( "instance_of" ) ) {
Scalar scalar = instruction.getScalar( 2 );
processClass( program, instruction, 2, header, ( int ) scalar.getUnsignedValue( ), log );
}
else if ( mnemonicString.equals( "check_cast" ) ) {
Scalar scalar = instruction.getScalar( 1 );
processClass( program, instruction, 1, header, ( int ) scalar.getUnsignedValue( ), log );
}
else if ( mnemonicString.startsWith( "invoke" ) ) {
Scalar scalar = instruction.getScalar( 0 );//method id
processMethod( program, instruction, 0, header, ( int ) scalar.getUnsignedValue( ), log );
}
else if ( mnemonicString.equals( "new_instance" ) ) {
Scalar scalar = instruction.getScalar( 1 );
processClass( program, instruction, 1, header, ( int ) scalar.getUnsignedValue( ), log );
}
else if ( mnemonicString.equals( "new_array" ) ) {
Scalar scalar = instruction.getScalar( 2 );
processClass( program, instruction, 2, header, ( int ) scalar.getUnsignedValue( ), log );
}
else if ( mnemonicString.startsWith( "iget" ) ) {
Scalar scalar = instruction.getScalar( 2 );
processField( program, instruction, 2, header, ( int ) scalar.getUnsignedValue( ), log );
}
else if ( mnemonicString.startsWith( "iput" ) ) {
Scalar scalar = instruction.getScalar( 2 );
processField( program, instruction, 2, header, ( int ) scalar.getUnsignedValue( ), log );
}
else if ( mnemonicString.startsWith( "sget" ) ) {
Scalar scalar = instruction.getScalar( 1 );
processField( program, instruction, 1, header, ( int ) scalar.getUnsignedValue( ), log );
}
else if ( mnemonicString.startsWith( "sput" ) ) {
Scalar scalar = instruction.getScalar( 1 );
processField( program, instruction, 1, header, ( int ) scalar.getUnsignedValue( ), log );
}
else if ( mnemonicString.startsWith( "filled_new_array" ) ) {
Scalar scalar = instruction.getScalar( 0 );
processClass( program, instruction, 0, header, ( int ) scalar.getUnsignedValue( ), log );
}
else if ( mnemonicString.startsWith( "fill_array_data" ) ) {
Scalar scalar = instruction.getScalar( 1 );
Address address = instruction.getMinAddress( ).add( scalar.getUnsignedValue( ) * 2 );
if ( program.getMemory( ).getShort( address ) != FilledArrayDataPayload.MAGIC ) {
log.appendMsg( "invalid filled array at " + address );
}
else {
reader.setPointerIndex( address.getOffset( ) );
FilledArrayDataPayload payload = new FilledArrayDataPayload( reader );
DataType dataType = payload.toDataType( );
createData( program, address, dataType );
program.getReferenceManager( ).addMemoryReference( instruction.getMinAddress( ), address, RefType.DATA, SourceType.ANALYSIS, 1 );
}
}
}
return true;
}
@Override
public boolean canAnalyze( Program program ) {
ByteProvider provider = new MemoryByteProvider( program.getMemory( ), program.getMinAddress( ) );
return DexConstants.isDexFile( provider );
}
@Override
public AnalyzerType getAnalysisType( ) {
return AnalyzerType.INSTRUCTION_ANALYZER;
}
@Override
public boolean getDefaultEnablement( Program program ) {
return true;
}
@Override
public String getDescription( ) {
return "Android DEX Instruction Markup";
}
@Override
public String getName( ) {
return "Android DEX Instruction Markup";
}
@Override
public AnalysisPriority getPriority( ) {
return new AnalysisPriority( 4 );
}
@Override
public boolean isPrototype( ) {
return false;
}
private String getClassName( Program program, DexHeader header, int classTypeIndex, MessageLog log ) {
TypeIDItem typeItem = header.getTypes( ).get( classTypeIndex );
StringIDItem stringItem = header.getStrings( ).get( typeItem.getDescriptorIndex( ) );
return stringItem.getStringDataItem( ).getString( );
}
private String format( String className, String methodName ) {
StringBuilder builder = new StringBuilder( );
if ( className.startsWith( "L" ) && className.endsWith( ";" ) ) {
String str = className.substring( 1, className.length( ) - 1 );
StringTokenizer tokenizer = new StringTokenizer( str, "/" );
while ( tokenizer.hasMoreTokens( ) ) {
String token = tokenizer.nextToken( );
builder.append( token + "::" );
}
}
builder.append( methodName );
return builder.toString( );
}
private void setEquate( Program program, Address address, int operand, String equateName, int equateValue ) {
EquateTable equateTable = program.getEquateTable( );
Equate equate = equateTable.getEquate( equateName );
if ( equate == null ) {
try {
equate = equateTable.createEquate( equateName, equateValue );
}
catch ( Exception e ) {
// ignore
}
}
if ( equate == null ) {// happens when equate name is invalid
return;
}
if ( equate.getValue( ) != equateValue ) {// verify value is same
setEquate( program, address, operand, equateName + "_" + equateValue, equateValue );
return;
}
equate.addReference( address, operand );
}
private void processMethod( Program program, Instruction instruction, int operand, DexHeader header, int methodIndex, MessageLog log ) {
if ( methodIndex < 0 || methodIndex > header.getMethodIdsSize() ) {
log.appendMsg( "method index not found: " + methodIndex );
return;
}
//MethodIDItem methodIDItem = methods.get( methodIndex );
//StringIDItem stringItem = header.getStrings( ).get( methodIDItem.getNameIndex( ) );
//String methodName = stringItem.getStringDataItem( ).getString( );
//String className = getClassName( program, header, methodIDItem.getClassIndex( ), log );
//String valueName = format( className, methodName );
Address methodIndexAddress = header.getMethodAddress( program, methodIndex );
if (methodIndexAddress != Address.NO_ADDRESS)
program.getReferenceManager().addMemoryReference( instruction.getMinAddress(), methodIndexAddress, RefType.UNCONDITIONAL_CALL, SourceType.ANALYSIS, operand );
}
private void processClass( Program program, Instruction instruction, int operand, DexHeader header, int classTypeIndex, MessageLog log ) {
TypeIDItem typeItem = header.getTypes( ).get( classTypeIndex );
StringIDItem stringItem = header.getStrings( ).get( typeItem.getDescriptorIndex( ) );
String className = stringItem.getStringDataItem( ).getString( );
setEquate( program, instruction.getMinAddress( ), operand, className, classTypeIndex );
program.getListing( ).setComment( instruction.getMinAddress( ), CodeUnit.EOL_COMMENT, className );
}
private void processString( Program program, Instruction instruction, int operand, DexHeader header, int stringIndex, MessageLog log ) {
List< StringIDItem > strings = header.getStrings( );
if ( stringIndex < 0 || stringIndex > strings.size( ) ) {
log.appendMsg( "string index not found: " + stringIndex );
return;
}
StringIDItem stringIDItem = strings.get( stringIndex );
StringDataItem stringDataItem = stringIDItem.getStringDataItem( );
if ( stringDataItem == null ) {
log.appendMsg( "string data item is null: " + stringIndex );
return;
}
AddressSpace defaultAddressSpace = program.getAddressFactory().getDefaultAddressSpace();
Address stringAddr = defaultAddressSpace.getAddress(stringIDItem.getStringDataOffset());
program.getReferenceManager().addMemoryReference(instruction.getMinAddress(), stringAddr,
RefType.DATA, SourceType.ANALYSIS, operand);
// setEquate( program, instruction.getMinAddress( ), operand, stringDataItem.getString( ), stringIndex );
// program.getListing( ).setComment( instruction.getMinAddress( ), CodeUnit.EOL_COMMENT, stringDataItem.getString( ) );
}
private void processField( Program program, Instruction instruction, int operand, DexHeader header, int fieldIndex, MessageLog log ) {
List< FieldIDItem > fields = header.getFields( );
if ( fieldIndex < 0 || fieldIndex > fields.size( ) ) {
log.appendMsg( "field index not found: " + fieldIndex );
return;
}
FieldIDItem fieldIDItem = fields.get( fieldIndex );
StringIDItem stringItem = header.getStrings( ).get( fieldIDItem.getNameIndex( ) );
String fieldName = stringItem.getStringDataItem( ).getString( );
String className = getClassName( program, header, fieldIDItem.getClassIndex( ), log );
String valueName = format( className, fieldName );
setEquate( program, instruction.getMinAddress( ), operand, fieldName, fieldIndex );
program.getListing( ).setComment( instruction.getMinAddress( ), CodeUnit.EOL_COMMENT, valueName );
}
}

View file

@ -0,0 +1,188 @@
/* ###
* 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.dex.analyzer;
import ghidra.app.cmd.disassemble.DisassembleCommand;
import ghidra.app.services.AnalysisPriority;
import ghidra.app.services.AnalyzerType;
import ghidra.app.util.bin.*;
import ghidra.app.util.importer.MessageLog;
import ghidra.file.analyzers.FileFormatAnalyzer;
import ghidra.file.formats.android.dex.format.*;
import ghidra.file.formats.android.dex.util.DexUtil;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressSetView;
import ghidra.program.model.data.DataType;
import ghidra.program.model.listing.*;
import ghidra.program.model.scalar.Scalar;
import ghidra.program.model.symbol.*;
import ghidra.util.task.TaskMonitor;
public class DexMarkupSwitchTableAnalyzer extends FileFormatAnalyzer {
@Override
public boolean analyze(Program program, AddressSetView set, TaskMonitor monitor, MessageLog log)
throws Exception {
monitor.setMaximum(set == null ? program.getMemory().getSize() : set.getNumAddresses());
monitor.setProgress(0);
ByteProvider provider =
new MemoryByteProvider(program.getMemory(), program.getMinAddress());
BinaryReader reader = new BinaryReader(provider, true);
Listing listing = program.getListing();
InstructionIterator instructionIterator = listing.getInstructions(set, true);
while (instructionIterator.hasNext()) {
Instruction instruction = instructionIterator.next();
monitor.checkCanceled();
monitor.incrementProgress(1);
monitor.setMessage("DEX: Instruction markup ... " + instruction.getMinAddress());
if (instruction.getMnemonicString().startsWith("packed_switch")) {
if (instruction.getMnemonicReferences().length > 0) {// already done
continue;
}
Scalar scalar = instruction.getScalar(1);
Address address = instruction.getMinAddress().add(scalar.getUnsignedValue() * 2);
if (program.getMemory().getShort(address) != PackedSwitchPayload.MAGIC) {
log.appendMsg("invalid packed switch at " + address);
}
else {
program.getReferenceManager().addMemoryReference(instruction.getMinAddress(),
address, RefType.DATA, SourceType.ANALYSIS, 1);
reader.setPointerIndex(address.getOffset());
PackedSwitchPayload payload = new PackedSwitchPayload(reader);
DataType dataType = payload.toDataType();
createData(program, address, dataType);
processPacked(program, instruction, payload, monitor);
//TODO setFallThrough( program, instruction );
}
}
else if (instruction.getMnemonicString().startsWith("sparse_switch")) {
if (instruction.getMnemonicReferences().length > 0) {// already done
continue;
}
Scalar scalar = instruction.getScalar(1);
Address address = instruction.getMinAddress().add(scalar.getUnsignedValue() * 2);
if (program.getMemory().getShort(address) != SparseSwitchPayload.MAGIC) {
log.appendMsg("invalid sparse switch at " + address);
}
else {
program.getReferenceManager().addMemoryReference(instruction.getMinAddress(),
address, RefType.DATA, SourceType.ANALYSIS, 1);
reader.setPointerIndex(address.getOffset());
SparseSwitchPayload payload = new SparseSwitchPayload(reader);
DataType dataType = payload.toDataType();
createData(program, address, dataType);
processSparse(program, instruction, payload, monitor);
//TODO setFallThrough( program, instruction );
}
}
}
return true;
}
@Override
public boolean canAnalyze(Program program) {
ByteProvider provider =
new MemoryByteProvider(program.getMemory(), program.getMinAddress());
return DexConstants.isDexFile(provider);
}
@Override
public AnalyzerType getAnalysisType() {
return AnalyzerType.INSTRUCTION_ANALYZER;
}
@Override
public boolean getDefaultEnablement(Program program) {
return true;
}
@Override
public String getDescription() {
return "Android DEX Switch Table Markup";
}
@Override
public String getName() {
return "Android DEX Switch Table Markup";
}
@Override
public AnalysisPriority getPriority() {
return new AnalysisPriority(3);
}
@Override
public boolean isPrototype() {
return false;
}
// private void setFallThrough( Program program, Instruction instruction ) {
// Address fallThroughAddress = instruction.getMaxAddress( ).add( 1 );
// instruction.setFallThrough( fallThroughAddress );
// DisassembleCommand dCommand = new DisassembleCommand( fallThroughAddress, null, true );
// dCommand.applyTo( program );
// }
private void processPacked(Program program, Instruction instruction,
PackedSwitchPayload payload, TaskMonitor monitor) throws Exception {
String namespaceName = "pswitch_" + instruction.getMinAddress();
Namespace nameSpace = DexUtil.getOrCreateNameSpace(program, namespaceName);
int key = payload.getFirstKey();
for (int target : payload.getTargets()) {
monitor.checkCanceled();
String caseName = "case_0x" + Integer.toHexString(key);
Address caseAddress = instruction.getMinAddress().add(target * 2);
program.getSymbolTable().createLabel(caseAddress, caseName, nameSpace,
SourceType.ANALYSIS);
program.getReferenceManager().addMemoryReference(instruction.getMinAddress(),
caseAddress, RefType.COMPUTED_JUMP, SourceType.ANALYSIS, CodeUnit.MNEMONIC);
DisassembleCommand dCommand = new DisassembleCommand(caseAddress, null, true);
dCommand.applyTo(program);
++key;
}
}
private void processSparse(Program program, Instruction instruction,
SparseSwitchPayload payload, TaskMonitor monitor) throws Exception {
String namespaceName = "sswitch_" + instruction.getMinAddress();
Namespace nameSpace = DexUtil.getOrCreateNameSpace(program, namespaceName);
for (int i = 0; i < payload.getSize(); ++i) {
monitor.checkCanceled();
String caseName = "case_0x" + Integer.toHexString(payload.getKeys()[i]);
Address caseAddress = instruction.getMinAddress().add(payload.getTargets()[i] * 2);
program.getSymbolTable().createLabel(caseAddress, caseName, nameSpace,
SourceType.ANALYSIS);
program.getReferenceManager().addMemoryReference(instruction.getMinAddress(),
caseAddress, RefType.COMPUTED_JUMP, SourceType.ANALYSIS, CodeUnit.MNEMONIC);
DisassembleCommand dCommand = new DisassembleCommand(caseAddress, null, true);
dCommand.applyTo(program);
}
}
}

View file

@ -0,0 +1,58 @@
/* ###
* 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.dex.format;
import java.lang.reflect.Field;
public final class AccessFlags {
public final static int ACC_PUBLIC = 0x1;// public: visible everywhere public: visible everywhere public: visible everywhere
public final static int ACC_PRIVATE = 0x2;// * private: only visible to defining class private: only visible to defining class private: only visible to defining class
public final static int ACC_PROTECTED = 0x4;// * protected: visible to package and subclasses protected: visible to package and subclasses protected: visible to package and subclasses public final
public final static int ACC_STATIC = 0x8;// * static: is not constructed with an outer this reference static: global to defining class static: does not take a this argument
public final static int ACC_FINAL = 0x10;// final: not subclassable final: immutable after construction final: not overridable
public final static int ACC_SYNCHRONIZED = 0x20;// synchronized: associated lock automatically acquired around call to this method. Note: This is only valid to set when ACC_NATIVE is also set.
public final static int ACC_VOLATILE = 0x40;// volatile: special access rules to help with thread safety
public final static int ACC_BRIDGE = 0x40;// bridge method, added automatically by compiler as a type-safe bridge
public final static int ACC_TRANSIENT = 0x80;// transient: not to be saved by default serialization
public final static int ACC_VARARGS = 0x80;// last argument should be treated as a "rest" argument by compiler
public final static int ACC_NATIVE = 0x100;// native: implemented in native code
public final static int ACC_INTERFACE = 0x200;// interface: multiply-implementable abstract class
public final static int ACC_ABSTRACT = 0x400;// abstract: not directly instantiable abstract: unimplemented by this class
public final static int ACC_STRICT = 0x800;// strictfp: strict rules for floating-point arithmetic
public final static int ACC_SYNTHETIC = 0x1000;// not directly defined in source code not directly defined in source code not directly defined in source code
public final static int ACC_ANNOTATION = 0x2000;// declared as an annotation class
public final static int ACC_ENUM = 0x4000;// declared as an enumerated type declared as an enumerated value
// (unused) 0x8000
public final static int ACC_CONSTRUCTOR = 0x10000;// constructor method (class or instance initializer)
public final static int ACC_DECLARED_SYNCHRONIZED = 0x20000;// declared synchronized. Note: This has no effect on execution (other than in reflection of this flag, per se).
public final static String toString( int value ) {
StringBuilder builder = new StringBuilder( );
try {
Field [] fields = AccessFlags.class.getDeclaredFields( );
for ( Field field : fields ) {
if ( ( field.getInt( null ) & value ) != 0 ) {
builder.append( "\t" + field.getName( ) + "\n" );
}
}
}
catch ( Exception e ) {
// ignore
}
return builder.toString( );
}
}

View file

@ -0,0 +1,74 @@
/* ###
* 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.dex.format;
import ghidra.app.util.bin.BinaryReader;
import ghidra.app.util.bin.StructConverter;
import ghidra.file.formats.android.dex.util.Leb128;
import ghidra.program.model.data.ArrayDataType;
import ghidra.program.model.data.CategoryPath;
import ghidra.program.model.data.DataType;
import ghidra.program.model.data.Structure;
import ghidra.program.model.data.StructureDataType;
import ghidra.util.exception.DuplicateNameException;
import java.io.IOException;
public class AnnotationElement implements StructConverter {
private int nameIndex;
private int nameIndexLength;// in bytes
private EncodedValue value;
public AnnotationElement( BinaryReader reader ) throws IOException {
nameIndex = Leb128.readUnsignedLeb128( reader.readByteArray( reader.getPointerIndex( ), 5 ) );
nameIndexLength = Leb128.unsignedLeb128Size( nameIndex );
reader.setPointerIndex( reader.getPointerIndex( ) + nameIndexLength );
value = new EncodedValue( reader );
}
public int getNameIndex( ) {
return nameIndex;
}
public EncodedValue getValue( ) {
return value;
}
@Override
public DataType toDataType( ) throws DuplicateNameException, IOException {
DataType encodeValueDataType = value.toDataType( );
String name = "annotation_element" + "_" + nameIndexLength + "_" + encodeValueDataType.getName( );
Structure structure = new StructureDataType( name, 0 );
structure.add( new ArrayDataType( BYTE, nameIndexLength, BYTE.getLength( ) ), "nameIndex", null );
structure.add( encodeValueDataType, "value", null );
structure.setCategoryPath( new CategoryPath( "/dex/annotation_element" ) );
// try {
// structure.setName( name + "_" + structure.getLength( ) );
// }
// catch ( Exception e ) {
// // ignore
// }
return structure;
}
}

View file

@ -0,0 +1,67 @@
/* ###
* 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.dex.format;
import ghidra.app.util.bin.*;
import ghidra.program.model.data.*;
import ghidra.util.exception.DuplicateNameException;
import java.io.IOException;
public class AnnotationItem implements StructConverter {
private byte visibility;
private EncodedAnnotation annotation;
public AnnotationItem( BinaryReader reader ) throws IOException {
visibility = reader.readNextByte( );
annotation = new EncodedAnnotation( reader );
}
public byte getVisibility( ) {
return visibility;
}
public EncodedAnnotation getAnnotation( ) {
return annotation;
}
@Override
public DataType toDataType( ) throws DuplicateNameException, IOException {
DataType annotationDataType = annotation.toDataType( );
StringBuilder builder = new StringBuilder( );
builder.append( "annotation_item" + "_" );
builder.append( visibility + "_" );
builder.append( annotationDataType.getName( ) );
Structure structure = new StructureDataType( builder.toString( ), 0 );
structure.add( BYTE, "visibility", null );
structure.add( annotationDataType, "annotation", null );
builder.append( structure.getLength( ) + "_" );
structure.setCategoryPath( new CategoryPath( "/dex/annotation_item" ) );
try {
structure.setName( builder.toString( ) );
}
catch ( Exception e ) {
// ignore
}
return structure;
}
}

View file

@ -0,0 +1,60 @@
/* ###
* 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.dex.format;
import ghidra.app.util.bin.*;
import ghidra.program.model.data.*;
import ghidra.util.exception.DuplicateNameException;
import java.io.IOException;
public class AnnotationOffsetItem implements StructConverter {
private int annotationsOffset;
private AnnotationItem _item;
public AnnotationOffsetItem( BinaryReader reader ) throws IOException {
annotationsOffset = reader.readNextInt( );
if ( annotationsOffset > 0 ) {
long oldIndex = reader.getPointerIndex( );
try {
reader.setPointerIndex( annotationsOffset );
_item = new AnnotationItem( reader );
}
finally {
reader.setPointerIndex( oldIndex );
}
}
}
public int getAnnotationsOffset( ) {
return annotationsOffset;
}
public AnnotationItem getItem( ) {
return _item;
}
@Override
public DataType toDataType( ) throws DuplicateNameException, IOException {
DataType dataType = StructConverterUtil.toDataType( AnnotationOffsetItem.class );
dataType.setCategoryPath( new CategoryPath( "/dex" ) );
return dataType;
}
}

View file

@ -0,0 +1,68 @@
/* ###
* 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.dex.format;
import ghidra.app.util.bin.*;
import ghidra.program.model.data.*;
import ghidra.util.exception.DuplicateNameException;
import java.io.IOException;
import java.util.*;
/**
* annotation_set_item
*
* referenced from annotations_directory_item, field_annotations_item, method_annotations_item, and annotation_set_ref_item
*
* appears in the data section
*
* alignment: 4 bytes
*/
public class AnnotationSetItem implements StructConverter {
private int size;
private List<AnnotationOffsetItem> items = new ArrayList<AnnotationOffsetItem>();
public AnnotationSetItem( BinaryReader reader ) throws IOException {
size = reader.readNextInt( );
for ( int i = 0 ; i < size ; ++i ) {
items.add( new AnnotationOffsetItem( reader ) );
}
}
public int getSize( ) {
return size;
}
public List< AnnotationOffsetItem > getItems( ) {
return Collections.unmodifiableList( items );
}
@Override
public DataType toDataType( ) throws DuplicateNameException, IOException {
Structure structure = new StructureDataType( "annotation_set_item_" + size, 0 );
structure.add( DWORD, "size", null );
int index = 0;
for ( AnnotationOffsetItem item : items ) {
structure.add( item.toDataType( ), "item" + index, null );
++index;
}
structure.setCategoryPath( new CategoryPath( "/dex/annotation_set_item" ) );
return structure;
}
}

View file

@ -0,0 +1,65 @@
/* ###
* 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.dex.format;
import ghidra.app.util.bin.*;
import ghidra.program.model.data.*;
import ghidra.util.exception.DuplicateNameException;
import java.io.IOException;
/**
*
* annotation_set_ref_item format
*
*/
public class AnnotationSetReferenceItem implements StructConverter {
private int annotationsOffset;
private AnnotationItem _item;
public AnnotationSetReferenceItem( BinaryReader reader ) throws IOException {
annotationsOffset = reader.readNextInt( );
if ( annotationsOffset > 0 ) {
long oldIndex = reader.getPointerIndex( );
try {
reader.setPointerIndex( annotationsOffset );
_item = new AnnotationItem( reader );
}
finally {
reader.setPointerIndex( oldIndex );
}
}
}
public int getAnnotationsOffset( ) {
return annotationsOffset;
}
public AnnotationItem getItem( ) {
return _item;
}
@Override
public DataType toDataType( ) throws DuplicateNameException, IOException {
DataType dataType = StructConverterUtil.toDataType( AnnotationSetReferenceItem.class );
dataType.setCategoryPath( new CategoryPath( "/dex" ) );
return dataType;
}
}

View file

@ -0,0 +1,66 @@
/* ###
* 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.dex.format;
import ghidra.app.util.bin.BinaryReader;
import ghidra.app.util.bin.StructConverter;
import ghidra.program.model.data.*;
import ghidra.util.exception.DuplicateNameException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
/**
* annotation_set_ref_list
*
* referenced from parameter_annotations_item
*
* appears in the data section
*
* alignment: 4 bytes
*/
public class AnnotationSetReferenceList implements StructConverter {
private int size;
private List< AnnotationSetReferenceItem > items = new ArrayList< AnnotationSetReferenceItem >( );
public AnnotationSetReferenceList( BinaryReader reader ) throws IOException {
size = reader.readNextInt( );
for ( int i = 0 ; i < size ; ++i ) {
items.add( new AnnotationSetReferenceItem( reader ) );
}
}
@Override
public DataType toDataType( ) throws DuplicateNameException, IOException {
Structure structure = new StructureDataType( "annotation_set_ref_list_" + size, 0 );
structure.add( DWORD, "size", null );
int index = 0;
for ( AnnotationSetReferenceItem item : items ) {
structure.add( item.toDataType( ), "item" + index, null );
++index;
}
structure.setCategoryPath( new CategoryPath( "/dex/annotation_set_ref_list" ) );
return structure;
}
public List<AnnotationSetReferenceItem> getItems() {
return items;
}
}

View file

@ -0,0 +1,133 @@
/* ###
* 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.dex.format;
import ghidra.app.util.bin.BinaryReader;
import ghidra.app.util.bin.StructConverter;
import ghidra.program.model.data.*;
import ghidra.util.exception.DuplicateNameException;
import java.io.IOException;
import java.util.*;
/**
* annotations_directory_item
*
* referenced from class_def_item
*
* appears in the data section
*
* alignment: 4 bytes
*/
public class AnnotationsDirectoryItem implements StructConverter {
private int classAnnotationsOffset;
private int fieldsSize;
private int annotatedMethodsSize;
private int annotatedParametersSize;
private List< FieldAnnotation > fieldAnnotations = new ArrayList< FieldAnnotation >( );
private List< MethodAnnotation > methodAnnotations = new ArrayList< MethodAnnotation >( );
private List< ParameterAnnotation > parameterAnnotations = new ArrayList< ParameterAnnotation >( );
private AnnotationSetItem _classAnnotations;
public AnnotationsDirectoryItem( BinaryReader reader ) throws IOException {
classAnnotationsOffset = reader.readNextInt( );
fieldsSize = reader.readNextInt( );
annotatedMethodsSize = reader.readNextInt( );
annotatedParametersSize = reader.readNextInt( );
for ( int i = 0 ; i < fieldsSize ; ++i ) {
fieldAnnotations.add( new FieldAnnotation( reader ) );
}
for ( int i = 0 ; i < annotatedMethodsSize ; ++i ) {
methodAnnotations.add( new MethodAnnotation( reader ) );
}
for ( int i = 0 ; i < annotatedParametersSize ; ++i ) {
parameterAnnotations.add( new ParameterAnnotation( reader ) );
}
if ( classAnnotationsOffset > 0 ){
long oldIndex = reader.getPointerIndex( );
try {
reader.setPointerIndex( classAnnotationsOffset );
_classAnnotations = new AnnotationSetItem( reader );
}
finally {
reader.setPointerIndex( oldIndex );
}
}
}
public int getClassAnnotationsOffset( ) {
return classAnnotationsOffset;
}
public int getFieldsSize( ) {
return fieldsSize;
}
public int getAnnotatedMethodsSize( ) {
return annotatedMethodsSize;
}
public int getAnnotatedParametersSize( ) {
return annotatedParametersSize;
}
public List< FieldAnnotation > getFieldAnnotations( ) {
return Collections.unmodifiableList( fieldAnnotations );
}
public List< MethodAnnotation > getMethodAnnotations( ) {
return Collections.unmodifiableList( methodAnnotations );
}
public List< ParameterAnnotation > getParameterAnnotations( ) {
return Collections.unmodifiableList( parameterAnnotations );
}
public AnnotationSetItem getClassAnnotations( ) {
return _classAnnotations;
}
@Override
public DataType toDataType( ) throws DuplicateNameException, IOException {
Structure structure = new StructureDataType( "annotations_directory_item_" + fieldsSize + "_" + annotatedMethodsSize + "_" + annotatedParametersSize, 0 );
structure.add( DWORD, "class_annotations_off", null );
structure.add( DWORD, "fields_size", null );
structure.add( DWORD, "annotated_methods_size", null );
structure.add( DWORD, "annotated_parameters_size", null );
int index = 0;
for ( FieldAnnotation field : fieldAnnotations ) {
structure.add( field.toDataType( ), "field_" + index, null );
++index;
}
index = 0;
for ( MethodAnnotation method : methodAnnotations ) {
structure.add( method.toDataType( ), "method_" + index, null );
++index;
}
index = 0;
for ( ParameterAnnotation parameter : parameterAnnotations ) {
structure.add( parameter.toDataType( ), "parameter_" + index, null );
++index;
}
structure.setCategoryPath( new CategoryPath( "/dex/annotations_directory_item" ) );
return structure;
}
}

View file

@ -0,0 +1,173 @@
/* ###
* 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.dex.format;
import ghidra.app.util.bin.BinaryReader;
import ghidra.app.util.bin.StructConverter;
import ghidra.file.formats.android.dex.util.Leb128;
import ghidra.program.model.data.*;
import ghidra.util.exception.DuplicateNameException;
import java.io.IOException;
import java.util.*;
public class ClassDataItem implements StructConverter {
private int staticFieldsSize;
private int instanceFieldsSize;
private int directMethodsSize;
private int virtualMethodsSize;
private int staticFieldsSizeLength;// in bytes
private int instanceFieldsSizeLength;// in bytes
private int directMethodsSizeLength;// in bytes
private int virtualMethodsSizeLength;// in bytes
private List< EncodedField > staticFields = new ArrayList< EncodedField >( );
private List< EncodedField > instancesFields = new ArrayList< EncodedField >( );
private List< EncodedMethod > directMethods = new ArrayList< EncodedMethod >( );
private List< EncodedMethod > virtualMethods = new ArrayList< EncodedMethod >( );
public ClassDataItem( BinaryReader reader ) throws IOException {
staticFieldsSize = Leb128.readUnsignedLeb128( reader.readByteArray( reader.getPointerIndex( ), 5 ) );
staticFieldsSizeLength = Leb128.unsignedLeb128Size( staticFieldsSize );
reader.readNextByteArray( staticFieldsSizeLength );// consume leb...
instanceFieldsSize = Leb128.readUnsignedLeb128( reader.readByteArray( reader.getPointerIndex( ), 5 ) );
instanceFieldsSizeLength = Leb128.unsignedLeb128Size( instanceFieldsSize );
reader.readNextByteArray( instanceFieldsSizeLength );// consume leb...
directMethodsSize = Leb128.readUnsignedLeb128( reader.readByteArray( reader.getPointerIndex( ), 5 ) );
directMethodsSizeLength = Leb128.unsignedLeb128Size( directMethodsSize );
reader.readNextByteArray( directMethodsSizeLength );// consume leb...
virtualMethodsSize = Leb128.readUnsignedLeb128( reader.readByteArray( reader.getPointerIndex( ), 5 ) );
virtualMethodsSizeLength = Leb128.unsignedLeb128Size( virtualMethodsSize );
reader.readNextByteArray( virtualMethodsSizeLength );// consume leb...
for ( int i = 0 ; i < staticFieldsSize ; ++i ) {
staticFields.add( new EncodedField( reader ) );
}
for ( int i = 0 ; i < instanceFieldsSize ; ++i ) {
instancesFields.add( new EncodedField( reader ) );
}
int methodIndex = 0;
for ( int i = 0 ; i < directMethodsSize ; ++i ) {
EncodedMethod encodedMethod = new EncodedMethod( reader );
directMethods.add( encodedMethod );
methodIndex += encodedMethod.getMethodIndexDifference( );
encodedMethod.setMethodIndex( methodIndex );
}
methodIndex = 0;
for ( int i = 0 ; i < virtualMethodsSize ; ++i ) {
EncodedMethod encodedMethod = new EncodedMethod( reader );
virtualMethods.add( encodedMethod );
methodIndex += encodedMethod.getMethodIndexDifference( );
encodedMethod.setMethodIndex( methodIndex );
}
}
public List< EncodedField > getInstancesFields( ) {
return Collections.unmodifiableList( instancesFields );
}
public List< EncodedField > getStaticFields( ) {
return Collections.unmodifiableList( staticFields );
}
public List< EncodedMethod > getDirectMethods( ) {
return Collections.unmodifiableList( directMethods );
}
public List< EncodedMethod > getVirtualMethods( ) {
return Collections.unmodifiableList( virtualMethods );
}
public int getStaticFieldsSize( ) {
return staticFieldsSize;
}
public int getInstanceFieldsSize( ) {
return instanceFieldsSize;
}
public int getDirectMethodsSize( ) {
return directMethodsSize;
}
public int getVirtualMethodsSize( ) {
return virtualMethodsSize;
}
public EncodedMethod getMethodByIndex( int index ) {
for ( int i = 0 ; i < directMethods.size() ; ++i ) {
EncodedMethod encodedMethod = directMethods.get( i );
if ( encodedMethod.getMethodIndex( ) == index ) {
return encodedMethod;
}
}
for ( int i = 0 ; i < virtualMethods.size() ; ++i ) {
EncodedMethod encodedMethod = virtualMethods.get( i );
if ( encodedMethod.getMethodIndex( ) == index ) {
return encodedMethod;
}
}
return null;
}
@Override
public DataType toDataType( ) throws DuplicateNameException, IOException {
// int unique = 0;
String name = "class_data_item" + "_" + staticFieldsSizeLength + "_" + instanceFieldsSizeLength + "_" + directMethodsSizeLength + "_" + virtualMethodsSizeLength;
Structure structure = new StructureDataType( name, 0 );
structure.add( new ArrayDataType( BYTE, staticFieldsSizeLength, BYTE.getLength( ) ), "static_fields", null );
structure.add( new ArrayDataType( BYTE, instanceFieldsSizeLength, BYTE.getLength( ) ), "instance_fields", null );
structure.add( new ArrayDataType( BYTE, directMethodsSizeLength, BYTE.getLength( ) ), "direct_methods", null );
structure.add( new ArrayDataType( BYTE, virtualMethodsSizeLength, BYTE.getLength( ) ), "virtual_methods", null );
// int index = 0;
// for ( EncodedField field : staticFields ) {
// DataType dataType = field.toDataType( );
// structure.add( dataType, "staticField" + index, null );
// ++index;
// }
// index = 0;
// for ( EncodedField field : instancesFields ) {
// DataType dataType = field.toDataType( );
// structure.add( dataType, "instancesField" + index, null );
// ++index;
// }
// index = 0;
// for ( EncodedMethod method : directMethods ) {
// DataType dataType = method.toDataType( );
// structure.add( dataType, "directMethod" + index, null );
// ++index;
// }
// index = 0;
// for ( EncodedMethod method : virtualMethods ) {
// DataType dataType = method.toDataType( );
// structure.add( dataType, "virtualMethod" + index, null );
// ++index;
// }
structure.setCategoryPath( new CategoryPath( "/dex/class_data_item" ) );
// try {
// structure.setName( name + "_" + Integer.toHexString( unique ) );
// }
// catch ( Exception e ) {
// // ignore
// }
return structure;
}
}

View file

@ -0,0 +1,178 @@
/* ###
* 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.dex.format;
import ghidra.app.util.bin.*;
import ghidra.file.formats.android.dex.util.DexUtil;
import ghidra.program.model.data.CategoryPath;
import ghidra.program.model.data.DataType;
import ghidra.util.exception.CancelledException;
import ghidra.util.exception.DuplicateNameException;
import ghidra.util.task.TaskMonitor;
import java.io.IOException;
public class ClassDefItem implements StructConverter {
private int classIndex;
private int accessFlags;
private int superClassIndex;
private int interfacesOffset;
private int sourceFileIndex;
private int annotationsOffset;
private int classDataOffset;
private int staticValuesOffset;
private TypeList _interfaces;
private AnnotationsDirectoryItem _annotationsDirectoryItem;
private ClassDataItem _classDataItem;
private EncodedArrayItem _staticValues;
public ClassDefItem( BinaryReader reader ) throws IOException {
classIndex = reader.readNextInt( );
accessFlags = reader.readNextInt( );
superClassIndex = reader.readNextInt( );
interfacesOffset = reader.readNextInt( );
sourceFileIndex = reader.readNextInt( );
annotationsOffset = reader.readNextInt( );
classDataOffset = reader.readNextInt( );
staticValuesOffset = reader.readNextInt( );
if ( interfacesOffset > 0 ) {
long oldIndex = reader.getPointerIndex( );
try {
reader.setPointerIndex( interfacesOffset );
_interfaces = new TypeList( reader );
}
finally {
reader.setPointerIndex( oldIndex );
}
}
if ( annotationsOffset > 0 ) {
long oldIndex = reader.getPointerIndex( );
try {
reader.setPointerIndex( annotationsOffset );
_annotationsDirectoryItem = new AnnotationsDirectoryItem( reader );
}
finally {
reader.setPointerIndex( oldIndex );
}
}
if ( classDataOffset > 0 ) {
long oldIndex = reader.getPointerIndex( );
try {
reader.setPointerIndex( classDataOffset );
_classDataItem = new ClassDataItem( reader );
}
finally {
reader.setPointerIndex( oldIndex );
}
}
if ( staticValuesOffset > 0 ) {
long oldIndex = reader.getPointerIndex( );
try {
reader.setPointerIndex( staticValuesOffset );
_staticValues = new EncodedArrayItem( reader );
}
finally {
reader.setPointerIndex( oldIndex );
}
}
}
public int getClassIndex( ) {
return classIndex;
}
public int getAccessFlags( ) {
return accessFlags;
}
public int getSuperClassIndex( ) {
return superClassIndex;
}
public int getInterfacesOffset( ) {
return interfacesOffset;
}
public int getSourceFileIndex( ) {
return sourceFileIndex;
}
public int getAnnotationsOffset( ) {
return annotationsOffset;
}
public int getClassDataOffset( ) {
return classDataOffset;
}
public int getStaticValuesOffset( ) {
return staticValuesOffset;
}
public TypeList getInterfaces( ) {
return _interfaces;
}
public AnnotationsDirectoryItem getAnnotationsDirectoryItem( ) {
return _annotationsDirectoryItem;
}
public ClassDataItem getClassDataItem( ) {
return _classDataItem;
}
public EncodedArrayItem getStaticValues( ) {
return _staticValues;
}
@Override
public DataType toDataType( ) throws DuplicateNameException, IOException {
DataType dataType = StructConverterUtil.toDataType( ClassDefItem.class );
dataType.setCategoryPath( new CategoryPath( "/dex" ) );
return dataType;
}
public String toString( DexHeader header, int index, TaskMonitor monitor ) throws CancelledException {
StringBuilder builder = new StringBuilder( );
if ( index != -1 ) {
builder.append( "Class Index: 0x" + Integer.toHexString( index ) + "\n" );
}
builder.append( "Class: " + DexUtil.convertTypeIndexToString( header, getClassIndex( ) ) + "\n" );
builder.append( "Class Access Flags:\n" + AccessFlags.toString( getAccessFlags( ) ) + "\n" );
builder.append( "Superclass: " + DexUtil.convertTypeIndexToString( header, getSuperClassIndex( ) ) + "\n" );
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 ( getSourceFileIndex( ) > 0 ) {
builder.append( "Source File: " + DexUtil.convertToString( header, getSourceFileIndex( ) ) + "\n" );
}
return builder.toString( );
}
}

View file

@ -0,0 +1,230 @@
/* ###
* 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.dex.format;
import ghidra.app.util.bin.BinaryReader;
import ghidra.app.util.bin.StructConverter;
import ghidra.program.model.data.*;
import ghidra.util.exception.DuplicateNameException;
import java.io.IOException;
import java.util.*;
/**
* code_item
*
* referenced from encoded_method
*
* appears in the data section
*
* alignment: 4 bytes
*/
public class CodeItem implements StructConverter {
private short registersSize;
private short incomingSize;
private short outgoingSize;
private short triesSize;
private int debugInfoOffset;
private int instructionSize;
private short [] instructions;
private byte [] instructionBytes;
private short padding;
private List< TryItem > tries = new ArrayList< TryItem >( );
private EncodedCatchHandlerList handlers;
private DebugInfoItem debugInfo;
public CodeItem( BinaryReader reader ) throws IOException {
registersSize = reader.readNextShort( );
incomingSize = reader.readNextShort( );
outgoingSize = reader.readNextShort( );
triesSize = reader.readNextShort( );
debugInfoOffset = reader.readNextInt( );
instructionSize = reader.readNextInt( );
if ( instructionSize == 0 ) {
instructionBytes = new byte[ 0 ];
instructions = new short[ 0 ];
}
else {
instructionBytes = reader.readByteArray( reader.getPointerIndex( ), instructionSize * 2 );
instructions = reader.readNextShortArray( instructionSize );
}
if ( hasPadding( ) ) {
padding = reader.readNextShort( );
}
for ( int i = 0 ; i < triesSize ; ++i ) {
tries.add( new TryItem( reader ) );
}
if ( triesSize > 0 ) {
handlers = new EncodedCatchHandlerList( reader );
}
if ( debugInfoOffset > 0 ) {
long oldIndex = reader.getPointerIndex( );
try {
reader.setPointerIndex( debugInfoOffset );
debugInfo = new DebugInfoItem( reader );
}
finally {
reader.setPointerIndex( oldIndex );
}
}
}
/**
* <pre>
* The number of registers used by this code
* </pre>
*/
public short getRegistersSize( ) {
return registersSize;
}
/**
* <pre>
* The number of words of incoming arguments to the method that this code is for
* </pre>
*/
public short getIncomingSize( ) {
return incomingSize;
}
/**
* <pre>
* The number of words of outgoing argument space required by this code for method invocation
* </pre>
*/
public short getOutgoingSize( ) {
return outgoingSize;
}
/**
* <pre>
* The number of try_items for this instance.
* If non-zero, then these appear as the tries array just
* after the insns in this instance.
* </pre>
*/
public short getTriesSize( ) {
return triesSize;
}
/**
* <pre>
* Offset from the start of the file to the debug info
* (line numbers + local variable info) sequence for this code, or 0 if there
* simply is no information. The offset, if non-zero, should be to a location
* in the data section. The format of the data is specified by "debug_info_item" below.
* </pre>
*/
public int getDebugInfoOffset( ) {
return debugInfoOffset;
}
/**
* Size of the instructions list, in 16-bit code units
*/
public int getInstructionSize( ) {
return instructionSize;
}
/**
* <pre>
* Actual array of bytecode.
* The format of code in an insns array is specified by the companion document Dalvik bytecode.
* Note that though this is defined as an array of ushort,
* there are some internal structures that prefer four-byte alignment.
* Also, if this happens to be in an endian-swapped file, then the swapping is
* only done on individual ushorts and not on the larger internal structures.
* </pre>
*/
public short [] getInstructions( ) {
return instructions;
}
public byte [] getInstructionBytes( ) {
return instructionBytes;
}
/**
* <pre>
* Two bytes of padding to make tries four-byte aligned.
* This element is only present if tries_size is non-zero and insns_size is odd.
* </pre>
*/
public short getPadding( ) {
return padding;
}
/**
* <pre>
* Array indicating where in the code exceptions are caught and how to handle them.
* Elements of the array must be non-overlapping in range and in order from low to high address.
* This element is only present if tries_size is non-zero.
* </pre>
*/
public List< TryItem > getTries( ) {
return Collections.unmodifiableList( tries );
}
/**
* <pre>
* Bytes representing a list of lists of catch types and associated handler addresses.
* Each try_item has a byte-wise offset into this structure.
* This element is only present if tries_size is non-zero.
* </pre>
*/
public EncodedCatchHandlerList getHandlerList( ) {
return handlers;
}
public DebugInfoItem getDebugInfo( ) {
return debugInfo;
}
@Override
public DataType toDataType( ) throws DuplicateNameException, IOException {
String suffix = hasPadding( ) ? "_p" : "";
String name = "code_item" + "_" + ( instructionSize * 2 ) + suffix;
Structure structure = new StructureDataType( name, 0 );
structure.add( WORD, "registers_size", null );
structure.add( WORD, "ins_size", null );
structure.add( WORD, "outs_size", null );
structure.add( WORD, "tries_size", null );
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 );
}
// for ( int i = 0 ; i < tries.size( ) ; ++i ) {
// DataType dataType = tries.get( i ).toDataType( );
// structure.add( dataType, "tries_" + i, null );
// unique = dataType.getLength( );
// }
// if ( triesSize != 0 ) {
// DataType dataType = handlers.toDataType( );
// structure.add( dataType, "handlers", null );
// unique = dataType.getLength( );
// }
structure.setCategoryPath( new CategoryPath( "/dex/code_item" ) );
return structure;
}
private boolean hasPadding( ) {
return ( instructionSize % 2 ) != 0;
}
}

View file

@ -0,0 +1,135 @@
/* ###
* 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.dex.format;
import ghidra.app.util.bin.BinaryReader;
import ghidra.app.util.bin.StructConverter;
import ghidra.file.formats.android.dex.util.Leb128;
import ghidra.program.model.data.ArrayDataType;
import ghidra.program.model.data.CategoryPath;
import ghidra.program.model.data.DataType;
import ghidra.program.model.data.Structure;
import ghidra.program.model.data.StructureDataType;
import ghidra.util.exception.DuplicateNameException;
import java.io.IOException;
public class DebugInfoItem implements StructConverter {
private int lineStart;
private int lineStartLength;// in bytes
private int parametersSize;
private int parametersSizeLength;// in bytes
private int [] parameterNames;
private int [] parameterNamesLengths;
private byte [] stateMachineOpcodes;
public DebugInfoItem( BinaryReader reader ) throws IOException {
lineStart = Leb128.readUnsignedLeb128( reader.readByteArray( reader.getPointerIndex( ), 5 ) );
lineStartLength = Leb128.unsignedLeb128Size( lineStart );
reader.readNextByteArray( lineStartLength );// consume leb...
parametersSize = Leb128.readUnsignedLeb128( reader.readByteArray( reader.getPointerIndex( ), 5 ) );
parametersSizeLength = Leb128.unsignedLeb128Size( parametersSize );
reader.readNextByteArray( parametersSizeLength );// consume leb...
parameterNames = new int[ parametersSize ];
parameterNamesLengths = new int[ parametersSize ];
for ( int i = 0 ; i < parametersSize ; ++i ) {
int value = Leb128.readUnsignedLeb128( reader.readByteArray( reader.getPointerIndex( ), 5 ) );
int valueLength = Leb128.unsignedLeb128Size( value );
reader.readNextByteArray( valueLength );// consume leb...
parameterNames[ i ] = value - 1;// uleb128p1
parameterNamesLengths[ i ] = valueLength;
}
long startIndex = reader.getPointerIndex( );
int count = DebugInfoStateMachineReader.computeLength( reader );
reader.setPointerIndex( startIndex );
stateMachineOpcodes = reader.readNextByteArray( count );
}
/**
* <pre>
* The initial value for the state machine's line register.
* Does not represent an actual positions entry.
* </pre>
*/
public int getLineStart( ) {
return lineStart;
}
/**
* <pre>
* The number of parameter names that are encoded.
* There should be one per method parameter, excluding an instance method's this, if any.
* </pre>
*/
public int getParametersSize( ) {
return parametersSize;
}
/**
* <pre>
* String index of the method parameter name.
* An encoded value of NO_INDEX indicates that no name is available for the associated parameter.
* The type descriptor and signature are implied from the method descriptor and signature.
* </pre>
*/
public int [] getParameterNames( ) {
return parameterNames;
}
public byte [] getStateMachineOpcodes( ) {
return stateMachineOpcodes;
}
@Override
public DataType toDataType( ) throws DuplicateNameException, IOException {
StringBuilder builder = new StringBuilder( );
builder.append( "debug_info_item" + "_" );
builder.append( lineStartLength + "" );
builder.append( parametersSizeLength + "" );
builder.append( parametersSize + "" );
builder.append( stateMachineOpcodes.length + "" );
Structure structure = new StructureDataType( builder.toString( ), 0 );
structure.add( new ArrayDataType( BYTE, lineStartLength, BYTE.getLength( ) ), "line_start", null );
structure.add( new ArrayDataType( BYTE, parametersSizeLength, BYTE.getLength( ) ), "parameters_size", null );
for ( int i = 0 ; i < parametersSize ; ++i ) {
ArrayDataType dataType = new ArrayDataType( BYTE, parameterNamesLengths[ i ], BYTE.getLength( ) );
structure.add( dataType, "parameter_" + i, null );
builder.append( dataType.getLength( ) + "" );
}
ArrayDataType stateMachineArray = new ArrayDataType( BYTE, stateMachineOpcodes.length, BYTE.getLength( ) );
structure.add( stateMachineArray, "state_machine", null );
structure.setCategoryPath( new CategoryPath( "/dex/debug_info_item" ) );
try {
structure.setName( builder.toString( ) );
}
catch ( Exception e ) {
// ignore
}
return structure;
}
}

View file

@ -0,0 +1,136 @@
/* ###
* 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.dex.format;
import ghidra.app.util.bin.BinaryReader;
import ghidra.file.formats.android.dex.util.Leb128;
import java.io.IOException;
class DebugInfoStateMachineReader {
static int computeLength( BinaryReader reader ) throws IOException {
int length = 0;
while ( true ) {
if ( length > 0x10000 ) {//don't loop forever!
return 0;
}
byte opcode = reader.readNextByte( );
++length;
switch( opcode ) {
case DebugStateMachineOpCodes.DBG_END_SEQUENCE: {
return length;//done!
}
case DebugStateMachineOpCodes.DBG_ADVANCE_PC: {
int advance = Leb128.readUnsignedLeb128( reader.readByteArray( reader.getPointerIndex( ), 5 ) );
int advanceLength = Leb128.unsignedLeb128Size( advance );
reader.setPointerIndex( reader.getPointerIndex( ) + advanceLength );
length += advanceLength;
break;
}
case DebugStateMachineOpCodes.DBG_ADVANCE_LINE: {
int advance = Leb128.readSignedLeb128( reader.readByteArray( reader.getPointerIndex( ), 5 ) );
int advanceLength = Leb128.signedLeb128Size( advance );
reader.setPointerIndex( reader.getPointerIndex( ) + advanceLength );
length += advanceLength;
break;
}
case DebugStateMachineOpCodes.DBG_START_LOCAL: {
int register = Leb128.readUnsignedLeb128( reader.readByteArray( reader.getPointerIndex( ), 5 ) );
int registerLength = Leb128.unsignedLeb128Size( register );
reader.setPointerIndex( reader.getPointerIndex( ) + registerLength );
length += registerLength;
//TODO uleb128p1
int name = Leb128.readUnsignedLeb128( reader.readByteArray( reader.getPointerIndex( ), 5 ) );
int nameLength = Leb128.unsignedLeb128Size( name );
reader.setPointerIndex( reader.getPointerIndex( ) + nameLength );
length += nameLength;
//TODO uleb128p1
int type = Leb128.readUnsignedLeb128( reader.readByteArray( reader.getPointerIndex( ), 5 ) );
int typeLength = Leb128.unsignedLeb128Size( type );
reader.setPointerIndex( reader.getPointerIndex( ) + typeLength );
length += typeLength;
break;
}
case DebugStateMachineOpCodes.DBG_START_LOCAL_EXTENDED: {
int register = Leb128.readUnsignedLeb128( reader.readByteArray( reader.getPointerIndex( ), 5 ) );
int registerLength = Leb128.unsignedLeb128Size( register );
reader.setPointerIndex( reader.getPointerIndex( ) + registerLength );
length += registerLength;
//TODO uleb128p1
int name = Leb128.readUnsignedLeb128( reader.readByteArray( reader.getPointerIndex( ), 5 ) );
int nameLength = Leb128.unsignedLeb128Size( name );
reader.setPointerIndex( reader.getPointerIndex( ) + nameLength );
length += nameLength;
//TODO uleb128p1
int type = Leb128.readUnsignedLeb128( reader.readByteArray( reader.getPointerIndex( ), 5 ) );
int typeLength = Leb128.unsignedLeb128Size( type );
reader.setPointerIndex( reader.getPointerIndex( ) + typeLength );
length += typeLength;
//TODO uleb128p1
int signature = Leb128.readUnsignedLeb128( reader.readByteArray( reader.getPointerIndex( ), 5 ) );
int signatureLength = Leb128.unsignedLeb128Size( signature );
reader.setPointerIndex( reader.getPointerIndex( ) + signatureLength );
length += signatureLength;
break;
}
case DebugStateMachineOpCodes.DBG_END_LOCAL: {
int register = Leb128.readUnsignedLeb128( reader.readByteArray( reader.getPointerIndex( ), 5 ) );
int registerLength = Leb128.unsignedLeb128Size( register );
reader.setPointerIndex( reader.getPointerIndex( ) + registerLength );
length += registerLength;
break;
}
case DebugStateMachineOpCodes.DBG_RESTART_LOCAL: {
int register = Leb128.readUnsignedLeb128( reader.readByteArray( reader.getPointerIndex( ), 5 ) );
int registerLength = Leb128.unsignedLeb128Size( register );
reader.setPointerIndex( reader.getPointerIndex( ) + registerLength );
length += registerLength;
break;
}
case DebugStateMachineOpCodes.DBG_SET_PROLOGUE_END: {
break;
}
case DebugStateMachineOpCodes.DBG_SET_EPILOGUE_BEGIN: {
break;
}
case DebugStateMachineOpCodes.DBG_SET_FILE: {
//TODO uleb128p1
int name = Leb128.readUnsignedLeb128( reader.readByteArray( reader.getPointerIndex( ), 5 ) );
int nameLength = Leb128.unsignedLeb128Size( name );
reader.setPointerIndex( reader.getPointerIndex( ) + nameLength );
length += nameLength;
break;
}
default: {
break;
}
}
}
}
}

View file

@ -0,0 +1,141 @@
/* ###
* 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.dex.format;
public final class DebugStateMachineOpCodes {
/**
* <pre>
* Terminates a debug info sequence for a code_item
* </pre>
*/
public final static byte DBG_END_SEQUENCE = 0x00;
/**
* <pre>
* Advances the address register without emitting a positions entry
*
* addr_diff: amount to add to address register
*
* uleb128 addr_diff
* </pre>
*/
public final static byte DBG_ADVANCE_PC = 0x01;
/**
* <pre>
* Advances the line register without emitting a positions entry
*
* line_diff: amount to change line register by
*
* sleb128 line_diff
* </pre>
*/
public final static byte DBG_ADVANCE_LINE = 0x02;
/**
* <pre>
* Introduces a local variable at the current address. Either name_idx or type_idx may be NO_INDEX to indicate that that value is unknown.
*
* uleb128 register_num
* uleb128p1 name_idx
* uleb128p1 type_idx
*
* register_num: register that will contain local
* name_idx: string index of the name
* type_idx: type index of the type
* </pre>
*/
public final static byte DBG_START_LOCAL = 0x03;
/**
* <pre>
* Introduces a local with a type signature at the current address.
* Any of name_idx, type_idx, or sig_idx may be NO_INDEX to indicate that that value is unknown.
* (If sig_idx is -1, though, the same data could be represented more efficiently using the opcode DBG_START_LOCAL.)
*
* Note: See the discussion under "dalvik.annotation.Signature" below for caveats about handling signatures.
*
* register_num: register that will contain local
* name_idx: string index of the name
* type_idx: type index of the type
* sig_idx: string index of the type signature
*
* uleb128 register_num
* uleb128p1 name_idx
* uleb128p1 type_idx
* uleb128p1 sig_idx
* </pre>
*/
public final static byte DBG_START_LOCAL_EXTENDED = 0x04;
/**
* <pre>
* Marks a currently-live local variable as out of scope at the current address
*
* register_num: register that contained local
*
* uleb128 register_num
*/
public final static byte DBG_END_LOCAL = 0x05;
/**
* <pre>
* Re-introduces a local variable at the current address. The name and type are the same as the last local that was live in the specified register.
*
* register_num: register to restart
*
* uleb128 register_num
*
* </pre>
*/
public final static byte DBG_RESTART_LOCAL = 0x06;
/**
* <pre>
* Sets the prologue_end state machine register, indicating that the next position
* entry that is added should be considered the end of a method prologue
* (an appropriate place for a method breakpoint).
* The prologue_end register is cleared by any special (>= 0x0a) opcode.
* </pre>
*/
public final static byte DBG_SET_PROLOGUE_END = 0x07;
/**
* <pre>
* Sets the epilogue_begin state machine register, indicating that
* the next position entry that is added should be considered the
* beginning of a method epilogue (an appropriate place to suspend
* execution before method exit).
* The epilogue_begin register is cleared by any special (>= 0x0a) opcode.
* </pre>
*/
public final static byte DBG_SET_EPILOGUE_BEGIN = 0x08;
/**
* <pre>
* Indicates that all subsequent line number entries make reference to this source file name, instead of the default name specified in code_item
*
* name_idx: string index of source file name; NO_INDEX if unknown
*
* uleb128p1 name_idx
* </pre>
*/
public final static byte DBG_SET_FILE = 0x09;
/**
* <pre>
* Advances the line and address registers, emits a position entry, and clears prologue_end and epilogue_begin. See below for description.
*
* Special Opcodes 0x0a...0xff (none) advances the line and address registers, emits a position entry, and clears prologue_end and epilogue_begin. See below for description.
* </pre>
*/
public final static boolean isSpecialOpCode( byte opcode ) {
return ( opcode & 0xff ) >= 0xa && ( opcode & 0xff ) <= 0xff;
}
}

View file

@ -0,0 +1,64 @@
/* ###
* 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.dex.format;
import ghidra.app.util.bin.ByteProvider;
public final class DexConstants {
//public final static String DEX_MAGIC = "dex\n035\0";
public final static String DEX_MAGIC_BASE = "dex\n";
public final static int DEX_VERSION_LENGTH = 4;
public final static String DEX_VERSION_009 = "009";
/** Expected version string */
public final static String DEX_VERSION_035 = "035";
/**
* Dex version 036 skipped because of an old dalvik bug on some versions
* of android where dex files with that version number would erroneously
* be accepted and run.
*
* @see https://android.googlesource.com/platform/art/+/master/libdexfile/dex/standard_dex_file.cc
*/
public final static String DEX_VERSION_036 = "036";
/** V037 was introduced in API LEVEL 24 */
public final static String DEX_VERSION_037 = "037";
/** V038 was introduced in API LEVEL 26 */
public final static String DEX_VERSION_038 = "038";
/** V039 was introduced in API LEVEL 28 */
public final static String DEX_VERSION_039 = "039";
public final static String MACHINE = "1";
public final static int ENDIAN_CONSTANT = 0x12345678;
public final static int REVERSE_ENDIAN_CONSTANT = 0x78563412;
public final static String kClassesDex = "classes.dex";
public final static boolean isDexFile( ByteProvider provider ) {
try {
byte [] bytes = provider.readBytes( 0, DEX_MAGIC_BASE.length( ) );
return DEX_MAGIC_BASE.equals( new String( bytes ) );
}
catch ( Exception e ) {
// ignore
}
return false;
}
}

View file

@ -0,0 +1,388 @@
/* ###
* 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.dex.format;
import java.io.IOException;
import java.util.*;
import ghidra.app.util.bin.BinaryReader;
import ghidra.app.util.bin.StructConverter;
import ghidra.file.formats.android.dex.util.DexUtil;
import ghidra.program.model.address.Address;
import ghidra.program.model.data.*;
import ghidra.program.model.listing.Program;
import ghidra.program.model.mem.MemoryAccessException;
import ghidra.util.NumericUtilities;
import ghidra.util.datastruct.FixedSizeHashMap;
import ghidra.util.exception.DuplicateNameException;
public class DexHeader implements StructConverter {
private byte[] magic;
private byte [] version;
private int checksum;
private byte[] signature;
private int fileSize;
private int headerSize;
private int endianTag;
private int linkSize;
private int linkOffset;
private int mapOffset;
private int stringIdsSize;
private int stringIdsOffset;
private int typeIdsSize;
private int typeIdsOffset;
private int protoIdsSize;
private int protoIdsOffset;
private int fieldIdsSize;
private int fieldIdsOffset;
private int methodIdsSize;
private int methodIdsOffset;
private int classDefsIdsSize;
private int classDefsIdsOffset;
private int dataSize;
private int dataOffset;
private MapList mapList;
private List<StringIDItem> strings = new ArrayList<>();
private List<TypeIDItem> types = new ArrayList<>();
private List<PrototypesIDItem> prototypes = new ArrayList<>();
private List<FieldIDItem> fields = new ArrayList<>();
private List<MethodIDItem> methods = new ArrayList<>();
private List<ClassDefItem> classDefs = new ArrayList<>();
private AddressCache methodXref = new AddressCache(); // Index to method address cache
private DataTypeCache typeXref = new DataTypeCache(); // Index to datatype cache
public static class AddressCache extends FixedSizeHashMap<Integer, Address> {
private static final int MAX_ENTRIES = 500;
public AddressCache() {
super(700, MAX_ENTRIES);
}
}
public static class DataTypeCache extends FixedSizeHashMap<Integer, DataType> {
private static final int MAX_ENTRIES = 100;
public DataTypeCache() {
super(150, MAX_ENTRIES);
}
}
public DexHeader(BinaryReader reader) throws IOException {
magic = reader.readNextByteArray( DexConstants.DEX_MAGIC_BASE.length( ) );
version = reader.readNextByteArray( DexConstants.DEX_VERSION_LENGTH );
if (!DexConstants.DEX_MAGIC_BASE.equals(new String(magic))) {
throw new IOException("not a dex file.");
}
checksum = reader.readNextInt();
signature = reader.readNextByteArray(20);
fileSize = reader.readNextInt();
headerSize = reader.readNextInt();
endianTag = reader.readNextInt();
linkSize = reader.readNextInt();
linkOffset = reader.readNextInt();
mapOffset = reader.readNextInt();
stringIdsSize = reader.readNextInt();
stringIdsOffset = reader.readNextInt();
typeIdsSize = reader.readNextInt();
typeIdsOffset = reader.readNextInt();
protoIdsSize = reader.readNextInt();
protoIdsOffset = reader.readNextInt();
fieldIdsSize = reader.readNextInt();
fieldIdsOffset = reader.readNextInt();
methodIdsSize = reader.readNextInt();
methodIdsOffset = reader.readNextInt();
classDefsIdsSize = reader.readNextInt();
classDefsIdsOffset = reader.readNextInt();
dataSize = reader.readNextInt();
dataOffset = reader.readNextInt();
reader.setPointerIndex(mapOffset);
if (mapOffset > 0) {
mapList = new MapList(reader);
}
reader.setPointerIndex(stringIdsOffset);
for (int i = 0; i < stringIdsSize; ++i) {
strings.add(new StringIDItem(reader));
}
reader.setPointerIndex(typeIdsOffset);
for (int i = 0; i < typeIdsSize; ++i) {
types.add(new TypeIDItem(reader));
}
reader.setPointerIndex(protoIdsOffset);
for (int i = 0; i < protoIdsSize; ++i) {
prototypes.add(new PrototypesIDItem(reader));
}
reader.setPointerIndex(fieldIdsOffset);
for (int i = 0; i < fieldIdsSize; ++i) {
fields.add(new FieldIDItem(reader));
}
reader.setPointerIndex(methodIdsOffset);
for (int i = 0; i < methodIdsSize; ++i) {
methods.add(new MethodIDItem(reader));
}
reader.setPointerIndex(classDefsIdsOffset);
for (int i = 0; i < classDefsIdsSize; ++i) {
classDefs.add(new ClassDefItem(reader));
}
}
@Override
public DataType toDataType() throws DuplicateNameException, IOException {
Structure structure = new StructureDataType("header_item", 0);
structure.add(UTF8, 8, "magic", null);
structure.add(DWORD, "checksum", "adler-32");
String comment = "SHA1:" + NumericUtilities.convertBytesToString(signature);
structure.add(new ArrayDataType(BYTE, 20, BYTE.getLength()), "signature", comment);
structure.add(DWORD, "fileSize", null);
structure.add(DWORD, "headerSize", null);
structure.add(DWORD, "endianTag", null);
structure.add(DWORD, "linkSize", null);
structure.add(DWORD, "linkOffset", null);
structure.add(DWORD, "mapOffset", null);
structure.add(DWORD, "stringIdsSize", null);
structure.add(DWORD, "stringIdsOffset", null);
structure.add(DWORD, "typeIdsSize", null);
structure.add(DWORD, "typeIdsOffset", null);
structure.add(DWORD, "protoIdsSize", null);
structure.add(DWORD, "protoIdsOffset", null);
structure.add(DWORD, "fieldIdsSize", null);
structure.add(DWORD, "fieldIdsOffset", null);
structure.add(DWORD, "methodIdsSize", null);
structure.add(DWORD, "methodIdsOffset", null);
structure.add(DWORD, "classDefsIdsSize", null);
structure.add(DWORD, "classDefsIdsOffset", null);
structure.add(DWORD, "dataSize", null);
structure.add(DWORD, "dataOffset", null);
structure.setCategoryPath(new CategoryPath("/dex"));
return structure;
}
public byte[] getMagic() {
return magic;
}
public byte [] getVersion( ) {
return version;
}
/**
* Adler32 checksum of the rest of the file (everything but magic and this field);
* used to detect file corruption
*/
public int getChecksum() {
return checksum;
}
/**
* SHA-1 signature (hash) of the rest of the file (everything but magic, checksum, and this field);
* used to uniquely identify files
*/
public byte[] getSignature() {
return signature;
}
/**
* Size of the entire file (including the header), in bytes
*/
public int getFileSize() {
return fileSize;
}
/**
* Size of the header (this entire section), in bytes.
* This allows for at least a limited amount of
* backwards/forwards compatibility without invalidating the format.
*/
public int getHeaderSize() {
return headerSize;
}
/**
* Endianness tag. Either "ENDIAN_CONSTANT or REVERSE_ENDIAN_CONSTANT".
*/
public int getEndianTag() {
return endianTag;
}
public int getStringIdsOffset() {
return stringIdsOffset;
}
public int getStringIdsSize() {
return stringIdsSize;
}
public List<StringIDItem> getStrings() {
return Collections.unmodifiableList(strings);
}
public int getClassDefsIdsOffset() {
return classDefsIdsOffset;
}
public int getClassDefsIdsSize() {
return classDefsIdsSize;
}
public List<ClassDefItem> getClassDefs() {
return Collections.unmodifiableList(classDefs);
}
public int getDataOffset() {
return dataOffset;
}
public int getDataSize() {
return dataSize;
}
public int getFieldIdsOffset() {
return fieldIdsOffset;
}
public int getFieldIdsSize() {
return fieldIdsSize;
}
public List<FieldIDItem> getFields() {
return Collections.unmodifiableList(fields);
}
public int getMethodIdsOffset() {
return methodIdsOffset;
}
public int getMethodIdsSize() {
return methodIdsSize;
}
public List<MethodIDItem> getMethods() {
return Collections.unmodifiableList(methods);
}
public int getTypeIdsOffset() {
return typeIdsOffset;
}
public int getTypeIdsSize() {
return typeIdsSize;
}
public List<TypeIDItem> getTypes() {
return Collections.unmodifiableList(types);
}
public int getProtoIdsOffset() {
return protoIdsOffset;
}
public int getProtoIdsSize() {
return protoIdsSize;
}
public List<PrototypesIDItem> getPrototypes() {
return Collections.unmodifiableList(prototypes);
}
public int getLinkOffset() {
return linkOffset;
}
public int getLinkSize() {
return linkSize;
}
public int getMapOffset() {
return mapOffset;
}
public MapList getMapList() {
return mapList;
}
public Address getMethodAddress(Program program, int methodId) {
if (methodId < 0 || methodId >= methodIdsSize) {
return Address.NO_ADDRESS;
}
Address addr;
synchronized (methodXref) {
addr = methodXref.get(methodId);
if (addr == null) { // First time we've tried to access address
addr = DexUtil.toLookupAddress(program, methodId); // Find "__lookup__" address
int val;
try {
val = program.getMemory().getInt(addr);
if (val != -1) { // If there is an address here, it is in memory location of function
addr = program.getAddressFactory().getDefaultAddressSpace().getAddress(
val & 0xffffffffL);
}
// Otherwise, the method is external, and we use the lookup address as placeholder
}
catch (MemoryAccessException e) {
addr = Address.NO_ADDRESS;
}
methodXref.put(methodId, addr);
}
}
return addr;
}
public DataType getDataType(Program program, short typeShort) {
int typeId = typeShort & 0xffff;
if (typeId < 0 || typeId >= typeIdsSize) {
return null;
}
DataType res;
synchronized (typeXref) {
res = typeXref.get(typeId);
if (res == null) {
TypeIDItem typeIDItem = types.get(typeId);
String typeString = DexUtil.convertToString(this, typeIDItem.getDescriptorIndex());
if (typeString.length() != 0 && typeString.charAt(0) == 'L') {
StringBuilder buffer = new StringBuilder();
buffer.append(DexUtil.HANDLE_PATH);
buffer.append("group").append(typeId / 100);
buffer.append(CategoryPath.DELIMITER_CHAR);
buffer.append("type").append(typeId);
DataType handleType =
program.getDataTypeManager().getDataType(buffer.toString());
if (handleType instanceof TypeDef) {
res = new PointerDataType(((TypeDef) handleType).getDataType(),
program.getDataTypeManager());
}
}
if (res == null) {
res = DexUtil.toDataType(program.getDataTypeManager(), typeString);
}
if (res != null) {
typeXref.put(typeId, res);
}
}
}
return res;
}
}

View file

@ -0,0 +1,92 @@
/* ###
* 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.dex.format;
import ghidra.app.util.bin.BinaryReader;
import ghidra.app.util.bin.StructConverter;
import ghidra.file.formats.android.dex.util.Leb128;
import ghidra.program.model.data.*;
import ghidra.util.exception.DuplicateNameException;
import java.io.IOException;
import java.util.*;
public class EncodedAnnotation implements StructConverter {
private int typeIndex;
private int typeIndexLength;// in bytes
private int size;
private int sizeLength;// in bytes
private List< AnnotationElement > elements = new ArrayList< AnnotationElement >( );
public EncodedAnnotation( BinaryReader reader ) throws IOException {
typeIndex = Leb128.readUnsignedLeb128( reader.readByteArray( reader.getPointerIndex( ), 5 ) );
typeIndexLength = Leb128.unsignedLeb128Size( typeIndex );
reader.readNextByteArray( typeIndexLength );// consume leb...
size = Leb128.readUnsignedLeb128( reader.readByteArray( reader.getPointerIndex( ), 5 ) );
sizeLength = Leb128.unsignedLeb128Size( size );
reader.readNextByteArray( sizeLength );// consume leb...
for ( int i = 0 ; i < size ; ++i ) {
elements.add( new AnnotationElement( reader ) );
}
}
public int getTypeIndex( ) {
return typeIndex;
}
public int getSize( ) {
return size;
}
public List<AnnotationElement> getElements() {
return Collections.unmodifiableList( elements );
}
@Override
public DataType toDataType( ) throws DuplicateNameException, IOException {
StringBuilder builder = new StringBuilder( );
builder.append( "encoded_annotation" + "_" );
builder.append( typeIndexLength + "_" );
builder.append( sizeLength + "_" );
builder.append( elements.size( ) + "_" );
Structure structure = new StructureDataType( builder.toString( ), 0 );
structure.add( new ArrayDataType( BYTE, typeIndexLength, BYTE.getLength( ) ), "typeIndex", null );
structure.add( new ArrayDataType( BYTE, sizeLength, BYTE.getLength( ) ), "size", null );
int index = 0;
for ( AnnotationElement element : elements ) {
DataType dataType = element.toDataType( );
structure.add( dataType, "element" + index, null );
++index;
builder.append( "" + dataType.getName( ) );
}
structure.setCategoryPath( new CategoryPath( "/dex/encoded_annotation" ) );
try {
structure.setName( builder.toString( ) );
}
catch ( Exception e ) {
// ignore
}
return structure;
}
}

View file

@ -0,0 +1,75 @@
/* ###
* 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.dex.format;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import ghidra.app.util.bin.BinaryReader;
import ghidra.app.util.bin.StructConverter;
import ghidra.file.formats.android.dex.util.Leb128;
import ghidra.program.model.data.*;
import ghidra.util.exception.DuplicateNameException;
public class EncodedArray implements StructConverter {
private int size;
private int sizeLength;// in bytes
// private List< EncodedValue > values = new ArrayList< EncodedValue >( );
private byte [] values;
public EncodedArray( BinaryReader reader ) throws IOException {
size = Leb128.readUnsignedLeb128( reader.readByteArray( reader.getPointerIndex( ), 5 ) );
sizeLength = Leb128.unsignedLeb128Size( size );
reader.readNextByteArray( sizeLength );// consume leb...
long oldIndex = reader.getPointerIndex( );
List< EncodedValue > valuesList = new ArrayList< EncodedValue >( );
for ( int i = 0 ; i < size ; ++i ) {
valuesList.add( new EncodedValue( reader ) );
}
int nBytes = (int) ( reader.getPointerIndex() - oldIndex );
reader.setPointerIndex(oldIndex);
values = reader.readNextByteArray(nBytes); // Re-read the encoded values as a byte array
}
public int getSize( ) {
return size;
}
public byte [] getValues( ) {
return values;
}
@Override
public DataType toDataType( ) throws DuplicateNameException, IOException {
Structure structure = new StructureDataType( "encoded_array_" + values.length, 0 );
structure.add( new ArrayDataType( BYTE, sizeLength, BYTE.getLength( ) ), "size", null );
if ( values.length > 0 ) {
structure.add( new ArrayDataType( BYTE, values.length, BYTE.getLength( ) ), "values", null );
}
// int index = 0;
// for ( EncodedValue value : values ) {
// structure.add( value.toDataType( ), "value" + index, null );
// ++index;
// }
structure.setCategoryPath( new CategoryPath( "/dex/encoded_array" ) );
return structure;
}
}

View file

@ -0,0 +1,46 @@
/* ###
* 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.dex.format;
import ghidra.app.util.bin.BinaryReader;
import ghidra.app.util.bin.StructConverter;
import ghidra.program.model.data.*;
import ghidra.util.exception.DuplicateNameException;
import java.io.IOException;
public class EncodedArrayItem implements StructConverter {
private EncodedArray array;
public EncodedArrayItem( BinaryReader reader ) throws IOException {
array = new EncodedArray( reader );
}
public EncodedArray getArray( ) {
return array;
}
@Override
public DataType toDataType( ) throws DuplicateNameException, IOException {
DataType dataType = array.toDataType( );
Structure structure = new StructureDataType( "encoded_array_item_" + dataType.getLength( ), 0 );
structure.add( dataType, "value", null );
structure.setCategoryPath( new CategoryPath( "/dex/encoded_array_item" ) );
return structure;
}
}

View file

@ -0,0 +1,108 @@
/* ###
* 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.dex.format;
import ghidra.app.util.bin.BinaryReader;
import ghidra.app.util.bin.StructConverter;
import ghidra.file.formats.android.dex.util.Leb128;
import ghidra.program.model.data.ArrayDataType;
import ghidra.program.model.data.CategoryPath;
import ghidra.program.model.data.DataType;
import ghidra.program.model.data.Structure;
import ghidra.program.model.data.StructureDataType;
import ghidra.util.exception.DuplicateNameException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
public class EncodedCatchHandler implements StructConverter {
private int size;
private int sizeLength;// in bytes
private List< EncodedTypeAddressPair > handlers = new ArrayList< EncodedTypeAddressPair >( );
private int catchAllAddress;
private int catchAllAddressLength;
public EncodedCatchHandler( BinaryReader reader ) throws IOException {
size = Leb128.readSignedLeb128( reader.readByteArray( reader.getPointerIndex( ), 5 ) );
sizeLength = Leb128.signedLeb128Size( size );
reader.readNextByteArray( sizeLength );// consume leb...
for ( int i = 0 ; i < Math.abs( size ) ; ++i ) {
handlers.add( new EncodedTypeAddressPair( reader ) );
}
if ( size <= 0 ) {// This element is only present if size is non-positive.
catchAllAddress = Leb128.readUnsignedLeb128( reader.readByteArray( reader.getPointerIndex( ), 5 ) );
catchAllAddressLength = Leb128.unsignedLeb128Size( catchAllAddress );
reader.readNextByteArray( catchAllAddressLength );// consume leb...
}
}
/**
* <pre>
* Number of catch types in this list. If non-positive, then this is the
* negative of the number of catch types, and the catches are followed by a catch-all handler.
* For example: A size of 0 means that there is a catch-all but no explicitly typed catches.
* A size of 2 means that there are two explicitly typed catches and no catch-all.
* And a size of -1 means that there is one typed catch along with a catch-all.
* </pre>
*/
public int getSize( ) {
return size;
}
/**
* Stream of abs(size) encoded items, one for each caught type, in the order that the types should be tested.
*/
public List< EncodedTypeAddressPair > getPairs( ) {
return handlers;
}
/**
* Bytecode address of the catch-all handler. This element is only present if size is non-positive.
*/
public int getCatchAllAddress( ) {
return catchAllAddress;
}
@Override
public DataType toDataType( ) throws DuplicateNameException, IOException {
StringBuilder builder = new StringBuilder( );
builder.append("encoded_catch_handler_" + sizeLength + "_" + catchAllAddressLength + "_" + handlers.size( ) );
Structure structure = new StructureDataType( builder.toString( ), 0 );
structure.add( new ArrayDataType( BYTE, sizeLength, BYTE.getLength( ) ), "size", null );
int index = 0;
for ( EncodedTypeAddressPair pair : handlers ) {
DataType dataType = pair.toDataType( );
structure.add( dataType, "handler_" + index, null );
builder.append( pair.getDataTypeIdString( ) );
}
if ( size <= 0 ) {// This element is only present if size is non-positive.
structure.add( new ArrayDataType( BYTE, catchAllAddressLength, BYTE.getLength( ) ), "catch_all_addr", null );
}
structure.setCategoryPath( new CategoryPath( "/dex/encoded_catch_handler" ) );
try {
structure.setName( builder.toString( ) );
}
catch ( Exception e ) {
// ignore
}
return structure;
}
}

View file

@ -0,0 +1,81 @@
/* ###
* 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.dex.format;
import ghidra.app.util.bin.BinaryReader;
import ghidra.app.util.bin.StructConverter;
import ghidra.file.formats.android.dex.util.Leb128;
import ghidra.program.model.data.ArrayDataType;
import ghidra.program.model.data.CategoryPath;
import ghidra.program.model.data.DataType;
import ghidra.program.model.data.Structure;
import ghidra.program.model.data.StructureDataType;
import ghidra.util.exception.DuplicateNameException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
public class EncodedCatchHandlerList implements StructConverter {
private int size;
private int sizeLength;// in bytes
private List< EncodedCatchHandler > handlers = new ArrayList< EncodedCatchHandler >( );
public EncodedCatchHandlerList( BinaryReader reader ) throws IOException {
size = Leb128.readUnsignedLeb128( reader.readByteArray( reader.getPointerIndex( ), 5 ) );
sizeLength = Leb128.unsignedLeb128Size( size );
reader.readNextByteArray( sizeLength );// consume leb...
for ( int i = 0 ; i < size ; ++i ) {
handlers.add( new EncodedCatchHandler( reader ) );
}
}
/**
* size of this list, in entries
*/
public int getSize( ) {
return size;
}
public List< EncodedCatchHandler > getHandlers( ) {
return handlers;
}
@Override
public DataType toDataType( ) throws DuplicateNameException, IOException {
// int unique = 0;
String name = "encoded_catch_handler_list" + "_" + sizeLength;
Structure structure = new StructureDataType( name, 0 );
structure.add( new ArrayDataType( BYTE, sizeLength, BYTE.getLength( ) ), "size", null );
// int index = 0;
// for ( EncodedCatchHandler handler : handlers ) {
// DataType dataType = handler.toDataType( );
// structure.add( dataType, "handler_" + index, null );
// unique += dataType.getLength( );
// }
structure.setCategoryPath( new CategoryPath( "/dex/encoded_catch_handler_list" ) );
// try {
// structure.setName( name + "_" + Integer.toHexString( unique ) );
// }
// catch ( Exception e ) {
// // ignore
// }
return structure;
}
}

View file

@ -0,0 +1,73 @@
/* ###
* 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.dex.format;
import ghidra.app.util.bin.BinaryReader;
import ghidra.app.util.bin.StructConverter;
import ghidra.file.formats.android.dex.util.Leb128;
import ghidra.program.model.data.ArrayDataType;
import ghidra.program.model.data.CategoryPath;
import ghidra.program.model.data.DataType;
import ghidra.program.model.data.Structure;
import ghidra.program.model.data.StructureDataType;
import ghidra.util.exception.DuplicateNameException;
import java.io.IOException;
public class EncodedField implements StructConverter {
private long _fileOffset;
private int fieldIndexDifference;
private int fieldIndexDifferenceLength;// in bytes
private int accessFlags;
private int accessFlagsLength;// in bytes
public EncodedField( BinaryReader reader ) throws IOException {
_fileOffset = reader.getPointerIndex( );
fieldIndexDifference = Leb128.readUnsignedLeb128( reader.readByteArray( reader.getPointerIndex( ), 5 ) );
fieldIndexDifferenceLength = Leb128.unsignedLeb128Size( fieldIndexDifference );
reader.readNextByteArray( fieldIndexDifferenceLength );// consume leb...
accessFlags = Leb128.readUnsignedLeb128( reader.readByteArray( reader.getPointerIndex( ), 5 ) );
accessFlagsLength = Leb128.unsignedLeb128Size( accessFlags );
reader.readNextByteArray( accessFlagsLength );// consume leb...
}
public long getFileOffset( ) {
return _fileOffset;
}
public int getFieldIndexDifference( ) {
return fieldIndexDifference;
}
public int getAccessFlags( ) {
return accessFlags;
}
@Override
public DataType toDataType( ) throws DuplicateNameException, IOException {
String name = "encoded_field_" + fieldIndexDifferenceLength + "_" + accessFlagsLength;
Structure structure = new StructureDataType( name, 0 );
structure.add( new ArrayDataType( BYTE, fieldIndexDifferenceLength, BYTE.getLength( ) ), "field_idx_diff", null );
structure.add( new ArrayDataType( BYTE, accessFlagsLength, BYTE.getLength( ) ), "accessFlags", null );
structure.setCategoryPath( new CategoryPath( "/dex/encoded_field" ) );
return structure;
}
}

View file

@ -0,0 +1,110 @@
/* ###
* 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.dex.format;
import ghidra.app.util.bin.BinaryReader;
import ghidra.app.util.bin.StructConverter;
import ghidra.file.formats.android.dex.util.Leb128;
import ghidra.program.model.data.*;
import ghidra.util.exception.DuplicateNameException;
import java.io.IOException;
public class EncodedMethod implements StructConverter {
private long _fileOffset;
private int _methodIndex;
private int methodIndexDifference;
private int accessFlags;
private int codeOffset;
private int methodIndexDifferenceLength;// in bytes
private int accessFlagsLength;// in bytes
private int codeOffsetLength;// in bytes
private CodeItem codeItem;
public EncodedMethod( BinaryReader reader ) throws IOException {
_fileOffset = reader.getPointerIndex( );
methodIndexDifference = Leb128.readUnsignedLeb128( reader.readByteArray( reader.getPointerIndex( ), 5 ) );
methodIndexDifferenceLength = Leb128.unsignedLeb128Size( methodIndexDifference );
reader.readNextByteArray( methodIndexDifferenceLength );// consume leb...
accessFlags = Leb128.readUnsignedLeb128( reader.readByteArray( reader.getPointerIndex( ), 5 ) );
accessFlagsLength = Leb128.unsignedLeb128Size( accessFlags );
reader.readNextByteArray( accessFlagsLength );// consume leb...
codeOffset = Leb128.readUnsignedLeb128( reader.readByteArray( reader.getPointerIndex( ), 5 ) );
codeOffsetLength = Leb128.unsignedLeb128Size( codeOffset );
reader.readNextByteArray( codeOffsetLength );// consume leb...
if ( codeOffset > 0 ) {
long oldIndex = reader.getPointerIndex( );
try {
reader.setPointerIndex( codeOffset );
codeItem = new CodeItem( reader );
}
finally {
reader.setPointerIndex( oldIndex );
}
}
}
public long getFileOffset( ) {
return _fileOffset;
}
void setMethodIndex( int methodIndex ) {
_methodIndex = methodIndex;
}
public int getMethodIndex( ) {
return _methodIndex;
}
public int getMethodIndexDifference( ) {
return methodIndexDifference;
}
public int getAccessFlags( ) {
return accessFlags;
}
public boolean isStatic( ) {
return ( accessFlags & AccessFlags.ACC_STATIC ) != 0;
}
public int getCodeOffset( ) {
return codeOffset;
}
public CodeItem getCodeItem( ) {
return codeItem;
}
@Override
public DataType toDataType( ) throws DuplicateNameException, IOException {
String name = "encoded_method_" + methodIndexDifferenceLength + "_" + accessFlagsLength + "_" + codeOffsetLength;
Structure structure = new StructureDataType( name, 0 );
structure.add( new ArrayDataType( BYTE, methodIndexDifferenceLength, BYTE.getLength( ) ), "method_idx_diff", null );
structure.add( new ArrayDataType( BYTE, accessFlagsLength, BYTE.getLength( ) ), "access_flags", null );
structure.add( new ArrayDataType( BYTE, codeOffsetLength, BYTE.getLength( ) ), "code_off", null );
structure.setCategoryPath( new CategoryPath( "/dex/encoded_method" ) );
return structure;
}
}

View file

@ -0,0 +1,72 @@
/* ###
* 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.dex.format;
import ghidra.app.util.bin.BinaryReader;
import ghidra.app.util.bin.StructConverter;
import ghidra.file.formats.android.dex.util.Leb128;
import ghidra.program.model.data.ArrayDataType;
import ghidra.program.model.data.CategoryPath;
import ghidra.program.model.data.DataType;
import ghidra.program.model.data.Structure;
import ghidra.program.model.data.StructureDataType;
import ghidra.util.exception.DuplicateNameException;
import java.io.IOException;
public class EncodedTypeAddressPair implements StructConverter {
private int typeIndex;
private int address;
private int typeIndexLength;// in bytes
private int addressLength;// in bytes
public EncodedTypeAddressPair( BinaryReader reader ) throws IOException {
typeIndex = Leb128.readUnsignedLeb128( reader.readByteArray( reader.getPointerIndex( ), 5 ) );
typeIndexLength = Leb128.unsignedLeb128Size( typeIndex );
reader.readNextByteArray( typeIndexLength );// consume leb...
address = Leb128.readUnsignedLeb128( reader.readByteArray( reader.getPointerIndex( ), 5 ) );
addressLength = Leb128.unsignedLeb128Size( address );
reader.readNextByteArray( addressLength );// consume leb...
}
public int getTypeIndex( ) {
return typeIndex;
}
public int getAddress( ) {
return address;
}
/**
* This method is only used for data type creation.
* Makes names unique to prevent ".conflicts".
*/
String getDataTypeIdString() {
return typeIndexLength + "" + addressLength;
}
@Override
public DataType toDataType( ) throws DuplicateNameException, IOException {
Structure structure = new StructureDataType( "encoded_type_addr_pair_" + typeIndexLength + "_" + addressLength, 0 );
structure.add( new ArrayDataType( BYTE, typeIndexLength, BYTE.getLength( ) ), "type_idx", null );
structure.add( new ArrayDataType( BYTE, addressLength, BYTE.getLength( ) ), "addr", null );
structure.setCategoryPath( new CategoryPath( "/dex/encoded_type_addr_pair" ) );
return structure;
}
}

View file

@ -0,0 +1,165 @@
/* ###
* 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.dex.format;
import ghidra.app.util.bin.BinaryReader;
import ghidra.app.util.bin.StructConverter;
import ghidra.program.model.data.*;
import ghidra.util.InvalidNameException;
import ghidra.util.exception.DuplicateNameException;
import java.io.IOException;
public class EncodedValue implements StructConverter {
private byte value;
private byte valueType;
private byte valueArgs;
private byte [] valueBytes;
private EncodedArray array;
private EncodedAnnotation annotation;
public EncodedValue( BinaryReader reader ) throws IOException {
value = reader.readNextByte( );
valueType = ( byte ) ( value & 0x1f );
valueArgs = ( byte ) ( ( value & 0xe0 ) >> 5 );
// length of value[] is based on TYPE....
switch ( valueType ) {
case ValueFormats.VALUE_BYTE:
case ValueFormats.VALUE_SHORT:
case ValueFormats.VALUE_CHAR:
case ValueFormats.VALUE_INT:
case ValueFormats.VALUE_LONG:
case ValueFormats.VALUE_FLOAT:
case ValueFormats.VALUE_DOUBLE:
case ValueFormats.VALUE_STRING:
case ValueFormats.VALUE_TYPE:
case ValueFormats.VALUE_FIELD:
case ValueFormats.VALUE_METHOD:
case ValueFormats.VALUE_ENUM: {
valueBytes = reader.readNextByteArray( valueArgs + 1 );
break;
}
case ValueFormats.VALUE_ARRAY: {
array = new EncodedArray( reader );
break;
}
case ValueFormats.VALUE_ANNOTATION: {
annotation = new EncodedAnnotation( reader );
break;
}
case ValueFormats.VALUE_NULL: {
break;// do nothing...
}
case ValueFormats.VALUE_BOOLEAN: {
break;// do nothing...
}
default : {
//TODO throw new RuntimeException( "unsupported encoded value: 0x" + Integer.toHexString( valueType & 0xff ) );
}
}
}
public byte getValueArgs( ) {
return valueArgs;
}
public byte getValueType( ) {
return valueType;
}
public byte [] getValueBytes( ) {
return valueBytes;
}
public byte getValueByte( ) {
return valueBytes[ 0 ];
}
public EncodedArray getArray( ) {
return array;
}
public EncodedAnnotation getAnnotation( ) {
return annotation;
}
public boolean isValueBoolean( ) {
return valueArgs == 1;
}
byte getValue( ) {
return value;
}
@Override
public DataType toDataType( ) throws DuplicateNameException, IOException {
StringBuilder builder = new StringBuilder( "encoded_value_0x" + Integer.toHexString( value & 0xff ) );
Structure structure = new StructureDataType( builder.toString( ), 0 );
structure.add( BYTE, "valueType", null );
switch ( valueType ) {
case ValueFormats.VALUE_BYTE:
case ValueFormats.VALUE_SHORT:
case ValueFormats.VALUE_CHAR:
case ValueFormats.VALUE_INT:
case ValueFormats.VALUE_LONG:
case ValueFormats.VALUE_FLOAT:
case ValueFormats.VALUE_DOUBLE:
case ValueFormats.VALUE_STRING:
case ValueFormats.VALUE_TYPE:
case ValueFormats.VALUE_FIELD:
case ValueFormats.VALUE_METHOD:
case ValueFormats.VALUE_ENUM: {
int length = ( valueArgs & 0xff ) + 1;
structure.add( new ArrayDataType( BYTE, length, BYTE.getLength( ) ), "value", null );
builder.append( "_" + length );
break;
}
case ValueFormats.VALUE_ARRAY: {
builder.append( "_" + array.getValues().length );
structure.add( array.toDataType( ), "value", null );
break;
}
case ValueFormats.VALUE_ANNOTATION: {
DataType dataType = annotation.toDataType( );
structure.add( dataType, "value", null );
builder.append( "_" + dataType.getName( ) );
break;
}
case ValueFormats.VALUE_NULL: {
break;// do nothing
}
case ValueFormats.VALUE_BOOLEAN: {
break;// do nothing
}
default : {
//TODO throw new RuntimeException( "unsupported encoded value: 0x" + Integer.toHexString( valueType & 0xff ) );
}
}
try {
structure.setName( builder.toString( ) );
}
catch ( InvalidNameException e ) {
// ignore, should never happen
}
structure.setCategoryPath( new CategoryPath( "/dex/encoded_value" ) );
return structure;
}
}

View file

@ -0,0 +1,79 @@
/* ###
* 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.dex.format;
import ghidra.app.util.bin.*;
import ghidra.program.model.data.CategoryPath;
import ghidra.program.model.data.DataType;
import ghidra.util.exception.DuplicateNameException;
import java.io.IOException;
/**
*
* field_annotation format
*
* Name Format Description
*
* field_idx uint index into the field_ids list for the identity of the field being annotated
*
* annotations_off uint offset from the start of the file to the list of annotations for the field. The offset should be to a location in the data section. The format of the data is specified by
* "annotation_set_item" below.
*
*/
public class FieldAnnotation implements StructConverter {
private int fieldIndex;
private int annotationsOffset;
private AnnotationSetItem _annotationSetItem;
public FieldAnnotation( BinaryReader reader ) throws IOException {
fieldIndex = reader.readNextInt( );
annotationsOffset = reader.readNextInt( );
if ( annotationsOffset > 0 ) {
long oldIndex = reader.getPointerIndex( );
try {
reader.setPointerIndex( annotationsOffset );
_annotationSetItem = new AnnotationSetItem( reader );
}
finally {
reader.setPointerIndex( oldIndex );
}
}
}
public int getFieldIndex( ) {
return fieldIndex;
}
public int getAnnotationsOffset( ) {
return annotationsOffset;
}
public AnnotationSetItem getAnnotationSetItem( ) {
return _annotationSetItem;
}
@Override
public DataType toDataType( ) throws DuplicateNameException, IOException {
DataType dataType = StructConverterUtil.toDataType( FieldAnnotation.class );
dataType.setCategoryPath( new CategoryPath( "/dex" ) );
return dataType;
}
}

View file

@ -0,0 +1,56 @@
/* ###
* 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.dex.format;
import ghidra.app.util.bin.*;
import ghidra.program.model.data.CategoryPath;
import ghidra.program.model.data.DataType;
import ghidra.util.exception.DuplicateNameException;
import java.io.IOException;
public class FieldIDItem implements StructConverter {
private short classIndex;
private short typeIndex;
private int nameIndex;
public FieldIDItem( BinaryReader reader ) throws IOException {
classIndex = reader.readNextShort( );
typeIndex = reader.readNextShort( );
nameIndex = reader.readNextInt( );
}
public short getClassIndex( ) {
return classIndex;
}
public short getTypeIndex( ) {
return typeIndex;
}
public int getNameIndex( ) {
return nameIndex;
}
@Override
public DataType toDataType( ) throws DuplicateNameException, IOException {
DataType dataType = StructConverterUtil.toDataType( FieldIDItem.class );
dataType.setCategoryPath( new CategoryPath( "/dex" ) );
return dataType;
}
}

View file

@ -0,0 +1,68 @@
/* ###
* 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.dex.format;
import ghidra.app.util.bin.BinaryReader;
import ghidra.app.util.bin.StructConverter;
import ghidra.program.model.data.*;
import ghidra.util.exception.DuplicateNameException;
import java.io.IOException;
public class FilledArrayDataPayload implements StructConverter {
public final static short MAGIC = 0x0300;
private short ident;
private short elementWidth;
private int size;
private byte [] data;
public FilledArrayDataPayload( BinaryReader reader ) throws IOException {
ident = reader.readNextShort( );
elementWidth = reader.readNextShort( );
size = reader.readNextInt( );
data = reader.readNextByteArray( size * elementWidth );
}
public short getIdent( ) {
return ident;
}
public short getElementWidth( ) {
return elementWidth;
}
public int getSize( ) {
return size;
}
public byte [] getData( ) {
return data;
}
@Override
public DataType toDataType( ) throws DuplicateNameException, IOException {
Structure structure = new StructureDataType( "filled_array_data_payload_" + elementWidth + "_" + size, 0 );
structure.add( WORD, "ident", null );
structure.add( WORD, "element_width", null );
structure.add( DWORD, "size", null );
structure.add( new ArrayDataType( BYTE, size * elementWidth, BYTE.getLength( ) ), "data", null );
structure.setCategoryPath( new CategoryPath( "/dex/filled_array_data_payload" ) );
return structure;
}
}

View file

@ -0,0 +1,62 @@
/* ###
* 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.dex.format;
import ghidra.app.util.bin.*;
import ghidra.program.model.data.CategoryPath;
import ghidra.program.model.data.DataType;
import ghidra.util.exception.DuplicateNameException;
import java.io.IOException;
public class MapItem implements StructConverter {
private short type;
private short unused;
private int size;
private int offset;
public MapItem( BinaryReader reader ) throws IOException {
type = reader.readNextShort( );
unused = reader.readNextShort( );
size = reader.readNextInt( );
offset = reader.readNextInt( );
}
public short getType( ) {
return type;
}
public short getUnused( ) {
return unused;
}
public int getSize( ) {
return size;
}
public int getOffset( ) {
return offset;
}
@Override
public DataType toDataType( ) throws DuplicateNameException, IOException {
DataType dataType = StructConverterUtil.toDataType( MapItem.class );
dataType.setCategoryPath( new CategoryPath( "/dex" ) );
return dataType;
}
}

View file

@ -0,0 +1,55 @@
/* ###
* 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.dex.format;
import java.lang.reflect.Field;
public final class MapItemTypeCodes {
public final static short TYPE_HEADER_ITEM = 0x0000;// 0x70
public final static short TYPE_STRING_ID_ITEM = 0x0001;// 0x04
public final static short TYPE_TYPE_ID_ITEM = 0x0002;// 0x04
public final static short TYPE_PROTO_ID_ITEM = 0x0003;// 0x0c
public final static short TYPE_FIELD_ID_ITEM = 0x0004;// 0x08
public final static short TYPE_METHOD_ID_ITEM = 0x0005;// 0x08
public final static short TYPE_CLASS_DEF_ITEM = 0x0006;// 0x20
public final static short TYPE_MAP_LIST = 0x1000;// 4 + (item.size * 12)
public final static short TYPE_TYPE_LIST = 0x1001;// 4 + (item.size * 2)
public final static short TYPE_ANNOTATION_SET_REF_LIST = 0x1002;// 4 + (item.size * 4)
public final static short TYPE_ANNOTATION_SET_ITEM = 0x1003;// 4 + (item.size * 4)
public final static short TYPE_CLASS_DATA_ITEM = 0x2000;// implicit; must parse
public final static short TYPE_CODE_ITEM = 0x2001;// implicit; must parse
public final static short TYPE_STRING_DATA_ITEM = 0x2002;// implicit; must parse
public final static short TYPE_DEBUG_INFO_ITEM = 0x2003;// implicit; must parse
public final static short TYPE_ANNOTATION_ITEM = 0x2004;// implicit; must parse
public final static short TYPE_ENCODED_ARRAY_ITEM = 0x2005;// implicit; must parse
public final static short TYPE_ANNOTATIONS_DIRECTORY_ITEM = 0x2006;// implicit; must parse
public final static String toString( short type ) {
try {
Field [] fields = MapItemTypeCodes.class.getDeclaredFields( );
for ( Field field : fields ) {
if ( field.getShort( null ) == type ) {
return field.getName( );
}
}
}
catch ( Exception e ) {
// ignore
}
return "Type:" + type;
}
}

View file

@ -0,0 +1,58 @@
/* ###
* 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.dex.format;
import ghidra.app.util.bin.BinaryReader;
import ghidra.app.util.bin.StructConverter;
import ghidra.program.model.data.*;
import ghidra.util.exception.DuplicateNameException;
import java.io.IOException;
import java.util.*;
public class MapList implements StructConverter {
private int size;
private List< MapItem > items = new ArrayList< MapItem >( );
public MapList( BinaryReader reader ) throws IOException {
size = reader.readNextInt( );
for ( int i = 0 ; i < size ; ++i ) {
items.add( new MapItem( reader ) );
}
}
public int getSize( ) {
return size;
}
public List< MapItem > getItems( ) {
return Collections.unmodifiableList( items );
}
@Override
public DataType toDataType( ) throws DuplicateNameException, IOException {
Structure structure = new StructureDataType( "MapList_" + size, 0 );
structure.add( DWORD, "size", null );
int index = 0;
for ( MapItem item : items ) {
structure.add( item.toDataType( ), "item_" + ( index++ ), null );
}
structure.setCategoryPath( new CategoryPath( "/dex" ) );
return structure;
}
}

View file

@ -0,0 +1,66 @@
/* ###
* 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.dex.format;
import ghidra.app.util.bin.*;
import ghidra.program.model.data.*;
import ghidra.util.exception.DuplicateNameException;
import java.io.IOException;
public class MethodAnnotation implements StructConverter {
private int methodIndex;
private int annotationsOffset;
private AnnotationSetItem _annotationSetItem;
public MethodAnnotation( BinaryReader reader ) throws IOException {
methodIndex = reader.readNextInt( );
annotationsOffset = reader.readNextInt( );
if ( annotationsOffset > 0 ) {
long oldIndex = reader.getPointerIndex( );
try {
reader.setPointerIndex( annotationsOffset );
_annotationSetItem = new AnnotationSetItem( reader );
}
finally {
reader.setPointerIndex( oldIndex );
}
}
}
public int getMethodIndex( ) {
return methodIndex;
}
public int getAnnotationsOffset( ) {
return annotationsOffset;
}
public AnnotationSetItem getAnnotationSetItem( ) {
return _annotationSetItem;
}
@Override
public DataType toDataType( ) throws DuplicateNameException, IOException {
DataType dataType = StructConverterUtil.toDataType( MethodAnnotation.class );
dataType.setCategoryPath( new CategoryPath( "/dex" ) );
return dataType;
}
}

View file

@ -0,0 +1,63 @@
/* ###
* 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.dex.format;
import ghidra.app.util.bin.*;
import ghidra.program.model.data.CategoryPath;
import ghidra.program.model.data.DataType;
import ghidra.util.exception.DuplicateNameException;
import java.io.IOException;
public class MethodIDItem implements StructConverter {
private long _fileOffset;
private short classIndex;
private short protoIndex;
private int nameIndex;
public MethodIDItem( BinaryReader reader ) throws IOException {
_fileOffset = reader.getPointerIndex( );
classIndex = reader.readNextShort( );
protoIndex = reader.readNextShort( );
nameIndex = reader.readNextInt( );
}
public long getFileOffset() {
return _fileOffset;
}
public int getClassIndex( ) {
return classIndex;
}
public short getProtoIndex( ) {
return protoIndex;
}
public int getNameIndex( ) {
return nameIndex;
}
@Override
public DataType toDataType( ) throws DuplicateNameException, IOException {
DataType dataType = StructConverterUtil.toDataType( MethodIDItem.class );
dataType.setCategoryPath( new CategoryPath( "/dex" ) );
return dataType;
}
}

View file

@ -0,0 +1,53 @@
/* ###
* 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.dex.format;
import java.io.*;
public final class ModifiedUTF8 {
public final static String decode( InputStream in, char [] out ) throws UTFDataFormatException, IOException {
int s = 0;
while ( true ) {
char a = ( char ) ( in.read( ) & 0xff );
if ( a == 0 ) {
return new String( out, 0, s );
}
out[ s ] = a;
if ( a < '\u0080' ) {
s++;
}
else if ( ( a & 0xe0 ) == 0xc0 ) {
int b = in.read( ) & 0xff;
if ( ( b & 0xC0 ) != 0x80 ) {
throw new UTFDataFormatException( "bad second byte" );
}
out[ s++ ] = ( char ) ( ( ( a & 0x1F ) << 6 ) | ( b & 0x3F ) );
}
else if ( ( a & 0xf0 ) == 0xe0 ) {
int b = in.read( ) & 0xff;
int c = in.read( ) & 0xff;
if ( ( ( b & 0xC0 ) != 0x80 ) || ( ( c & 0xC0 ) != 0x80 ) ) {
throw new UTFDataFormatException( "bad second or third byte" );
}
out[ s++ ] = ( char ) ( ( ( a & 0x0F ) << 12 ) | ( ( b & 0x3F ) << 6 ) | ( c & 0x3F ) );
}
else {
throw new UTFDataFormatException( "bad byte" );
}
}
}
}

View file

@ -0,0 +1,68 @@
/* ###
* 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.dex.format;
import ghidra.app.util.bin.BinaryReader;
import ghidra.app.util.bin.StructConverter;
import ghidra.program.model.data.*;
import ghidra.util.exception.DuplicateNameException;
import java.io.IOException;
public class PackedSwitchPayload implements StructConverter {
public final static short MAGIC = 0x0100;
private short ident;
private short size;
private int firstKey;
private int [] targets;
public PackedSwitchPayload( BinaryReader reader ) throws IOException {
ident = reader.readNextShort( );
size = reader.readNextShort( );
firstKey = reader.readNextInt( );
targets = reader.readNextIntArray( size & 0xffff );
}
public short getIdent( ) {
return ident;
}
public short getSize( ) {
return size;
}
public int getFirstKey( ) {
return firstKey;
}
public int [] getTargets( ) {
return targets;
}
@Override
public DataType toDataType( ) throws DuplicateNameException, IOException {
Structure structure = new StructureDataType( "packed_switch_payload_" + size, 0 );
structure.add( WORD, "ident", null );
structure.add( WORD, "size", null );
structure.add( DWORD, "first_key", null );
structure.add( new ArrayDataType( DWORD, size & 0xffff, DWORD.getLength( ) ), "targets", null );
structure.setCategoryPath( new CategoryPath( "/dex/packed_switch_payload" ) );
return structure;
}
}

View file

@ -0,0 +1,66 @@
/* ###
* 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.dex.format;
import ghidra.app.util.bin.*;
import ghidra.program.model.data.*;
import ghidra.util.exception.DuplicateNameException;
import java.io.IOException;
public class ParameterAnnotation implements StructConverter {
private int methodIndex;
private int annotationsOffset;
private AnnotationSetReferenceList _annotationSetReferenceList;
public ParameterAnnotation( BinaryReader reader ) throws IOException {
methodIndex = reader.readNextInt( );
annotationsOffset = reader.readNextInt( );
if ( annotationsOffset > 0 ) {
long oldIndex = reader.getPointerIndex( );
try {
reader.setPointerIndex( annotationsOffset );
_annotationSetReferenceList = new AnnotationSetReferenceList( reader );
}
finally {
reader.setPointerIndex( oldIndex );
}
}
}
public int getMethodIndex( ) {
return methodIndex;
}
public int getAnnotationsOffset( ) {
return annotationsOffset;
}
public AnnotationSetReferenceList getAnnotationSetReferenceList( ) {
return _annotationSetReferenceList;
}
@Override
public DataType toDataType( ) throws DuplicateNameException, IOException {
DataType dataType = StructConverterUtil.toDataType( ParameterAnnotation.class );
dataType.setCategoryPath( new CategoryPath( "/dex" ) );
return dataType;
}
}

View file

@ -0,0 +1,71 @@
/* ###
* 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.dex.format;
import ghidra.app.util.bin.*;
import ghidra.program.model.data.*;
import ghidra.util.exception.DuplicateNameException;
import java.io.IOException;
public class PrototypesIDItem implements StructConverter {
private int shortyIndex;
private int returnTypeIndex;
private int parametersOffset;
private TypeList _parameters;
public PrototypesIDItem( BinaryReader reader ) throws IOException {
shortyIndex = reader.readNextInt( );
returnTypeIndex = reader.readNextInt( );
parametersOffset = reader.readNextInt( );
if ( parametersOffset > 0 ) {
long oldIndex = reader.getPointerIndex( );
try {
reader.setPointerIndex( parametersOffset );
_parameters = new TypeList( reader );
}
finally {
reader.setPointerIndex( oldIndex );
}
}
}
public int getShortyIndex( ) {
return shortyIndex;
}
public int getReturnTypeIndex( ) {
return returnTypeIndex;
}
public int getParametersOffset( ) {
return parametersOffset;
}
public TypeList getParameters( ) {
return _parameters;
}
@Override
public DataType toDataType( ) throws DuplicateNameException, IOException {
DataType dataType = StructConverterUtil.toDataType( PrototypesIDItem.class );
dataType.setCategoryPath( new CategoryPath( "/dex" ) );
return dataType;
}
}

View file

@ -0,0 +1,68 @@
/* ###
* 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.dex.format;
import ghidra.app.util.bin.BinaryReader;
import ghidra.app.util.bin.StructConverter;
import ghidra.program.model.data.*;
import ghidra.util.exception.DuplicateNameException;
import java.io.IOException;
public class SparseSwitchPayload implements StructConverter {
public final static short MAGIC = 0x0200;
private short ident;
private short size;
private int [] keys;
private int [] targets;
public SparseSwitchPayload( BinaryReader reader ) throws IOException {
ident = reader.readNextShort( );
size = reader.readNextShort( );
keys = reader.readNextIntArray( size & 0xffff );
targets = reader.readNextIntArray( size & 0xffff );
}
public short getIdent( ) {
return ident;
}
public short getSize( ) {
return size;
}
public int [] getKeys( ) {
return keys;
}
public int [] getTargets( ) {
return targets;
}
@Override
public DataType toDataType( ) throws DuplicateNameException, IOException {
Structure structure = new StructureDataType( "sparse_switch_payload_" + size, 0 );
structure.add( WORD, "ident", null );
structure.add( WORD, "size", null );
structure.add( new ArrayDataType( DWORD, size & 0xffff, DWORD.getLength( ) ), "keys", null );
structure.add( new ArrayDataType( DWORD, size & 0xffff, DWORD.getLength( ) ), "targets", null );
structure.setCategoryPath( new CategoryPath( "/dex/sparse_switch_payload" ) );
return structure;
}
}

View file

@ -0,0 +1,87 @@
/* ###
* 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.dex.format;
import ghidra.app.util.bin.BinaryReader;
import ghidra.app.util.bin.StructConverter;
import ghidra.file.formats.android.dex.util.Leb128;
import ghidra.program.model.data.ArrayDataType;
import ghidra.program.model.data.CategoryPath;
import ghidra.program.model.data.DataType;
import ghidra.program.model.data.Structure;
import ghidra.program.model.data.StructureDataType;
import ghidra.util.exception.DuplicateNameException;
import java.io.ByteArrayInputStream;
import java.io.IOException;
public class StringDataItem implements StructConverter {
private int stringLength;
private int lebLength;
private int actualLength;
private String string;
public StringDataItem( StringIDItem stringItem, BinaryReader reader ) throws IOException {
long oldIndex = reader.getPointerIndex( );
try {
reader.setPointerIndex( stringItem.getStringDataOffset( ) );
stringLength = Leb128.readUnsignedLeb128( reader.readByteArray( stringItem.getStringDataOffset( ), 5 ) );
lebLength = Leb128.unsignedLeb128Size( stringLength );
reader.readNextByteArray( lebLength );// consume leb...
actualLength = computeActualLength( reader );
byte [] stringBytes = reader.readNextByteArray( actualLength );
ByteArrayInputStream in = new ByteArrayInputStream( stringBytes );
char [] out = new char[ stringLength ];
string = ModifiedUTF8.decode( in, out );
}
finally {
reader.setPointerIndex( oldIndex );
}
}
public String getString( ) {
return string;
}
@Override
public DataType toDataType( ) throws DuplicateNameException, IOException {
Structure structure = new StructureDataType( "string_data_item_" + actualLength, 0 );
structure.add( new ArrayDataType( BYTE, lebLength, BYTE.getLength( ) ), "utf16_size", null );
structure.add( UTF8, actualLength, "data", null );
structure.setCategoryPath( new CategoryPath( "/dex/string_data_item" ) );
return structure;
}
private int computeActualLength( BinaryReader reader ) throws IOException {
int count = 0;
while ( count < 0x200000 ) {// don't run forever!
if ( reader.readByte( reader.getPointerIndex( ) + count ) == 0x0 ) {
break;
}
++count;
}
return count + 1;
}
}

View file

@ -0,0 +1,50 @@
/* ###
* 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.dex.format;
import ghidra.app.util.bin.*;
import ghidra.program.model.data.*;
import ghidra.util.exception.DuplicateNameException;
import java.io.IOException;
public class StringIDItem implements StructConverter {
private int stringDataOffset;
private StringDataItem _stringDataItem;
public StringIDItem( BinaryReader reader ) throws IOException {
stringDataOffset = reader.readNextInt( );
_stringDataItem = new StringDataItem( this, reader );
}
public int getStringDataOffset( ) {
return stringDataOffset;
}
public StringDataItem getStringDataItem( ) {
return _stringDataItem;
}
@Override
public DataType toDataType( ) throws DuplicateNameException, IOException {
DataType dataType = StructConverterUtil.toDataType( StringIDItem.class );
dataType.setCategoryPath( new CategoryPath( "/dex" ) );
return dataType;
}
}

View file

@ -0,0 +1,79 @@
/* ###
* 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.dex.format;
import java.io.IOException;
import ghidra.app.util.bin.*;
import ghidra.program.model.data.*;
import ghidra.util.exception.DuplicateNameException;
/**
*
* try_item format
*
*/
public class TryItem implements StructConverter {
private int startAddress;
private short instructionCount;
private short handlerOffset;
TryItem( BinaryReader reader ) throws IOException {
startAddress = reader.readNextInt( );
instructionCount = reader.readNextShort( );
handlerOffset = reader.readNextShort( );
}
/**
* <pre>
* Start address of the block of code covered by this entry.
* The address is a count of 16-bit code units to the start of the first covered instruction.
* </pre>
*/
public int getStartAddress( ) {
return startAddress;
}
/**
* <pre>
* Number of 16-bit code units covered by this entry.
* The last code unit covered (inclusive) is start_addr + insn_count - 1.
* </pre>
*/
public short getInstructionCount( ) {
return instructionCount;
}
/**
* <pre>
* Offset in bytes from the start of the associated encoded_catch_hander_list to
* the encoded_catch_handler for this entry.
* This must be an offset to the start of an encoded_catch_handler.
* </pre>
*/
public short getHandlerOffset( ) {
return handlerOffset;
}
@Override
public DataType toDataType( ) throws DuplicateNameException, IOException {
DataType dataType = StructConverterUtil.toDataType( TryItem.class );
dataType.setCategoryPath( new CategoryPath( "/dex" ) );
return dataType;
}
}

View file

@ -0,0 +1,49 @@
/* ###
* 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.dex.format;
import java.io.IOException;
import ghidra.app.util.bin.*;
import ghidra.program.model.data.CategoryPath;
import ghidra.program.model.data.DataType;
import ghidra.util.exception.DuplicateNameException;
public class TypeIDItem implements StructConverter {
@Override
public int hashCode() {
return descriptorIndex;
}
private int descriptorIndex;
public TypeIDItem( BinaryReader reader ) throws IOException {
descriptorIndex = reader.readNextInt( );
}
public int getDescriptorIndex( ) {
return descriptorIndex;
}
@Override
public DataType toDataType( ) throws DuplicateNameException, IOException {
DataType dataType = StructConverterUtil.toDataType( TypeIDItem.class );
dataType.setCategoryPath( new CategoryPath( "/dex" ) );
return dataType;
}
}

View file

@ -0,0 +1,44 @@
/* ###
* 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.dex.format;
import ghidra.app.util.bin.*;
import ghidra.program.model.data.CategoryPath;
import ghidra.program.model.data.DataType;
import ghidra.util.exception.DuplicateNameException;
import java.io.IOException;
public class TypeItem implements StructConverter {
private short typeIndex;
public TypeItem( BinaryReader reader ) throws IOException {
typeIndex = reader.readNextShort( );
}
public short getType( ) {
return typeIndex;
}
@Override
public DataType toDataType( ) throws DuplicateNameException, IOException {
DataType dataType = StructConverterUtil.toDataType( TypeItem.class );
dataType.setCategoryPath( new CategoryPath( "/dex" ) );
return dataType;
}
}

View file

@ -0,0 +1,64 @@
/* ###
* 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.dex.format;
import ghidra.app.util.bin.BinaryReader;
import ghidra.app.util.bin.StructConverter;
import ghidra.program.model.data.*;
import ghidra.util.exception.DuplicateNameException;
import java.io.IOException;
import java.util.*;
public class TypeList implements StructConverter {
private int size;
private List< TypeItem > items = new ArrayList< TypeItem >( );
public TypeList( BinaryReader reader ) throws IOException {
size = reader.readNextInt( );
for ( int i = 0 ; i < size ; ++i ) {
items.add( new TypeItem( reader ) );
}
}
/**
* Size of the list, in entries
*/
public int getSize( ) {
return size;
}
/**
* Elements of the list
*/
public List< TypeItem > getItems( ) {
return Collections.unmodifiableList( items );
}
@Override
public DataType toDataType( ) throws DuplicateNameException, IOException {
Structure structure = new StructureDataType( "type_list" + size, 0 );
structure.add( DWORD, "size", null );
int index = 0;
for ( TypeItem item : items ) {
structure.add( item.toDataType( ), "item_" + ( index++ ), null );
}
structure.setCategoryPath( new CategoryPath( "/dex" ) );
return structure;
}
}

View file

@ -0,0 +1,61 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.file.formats.android.dex.format;
import java.lang.reflect.Field;
public final class ValueFormats {
public final static byte VALUE_BYTE = 0x00;// (none; must be 0) ubyte[1] signed one-byte integer value
public final static byte VALUE_SHORT = 0x02;// size - 1 (0...1) ubyte[size] signed two-byte integer value, sign-extended
public final static byte VALUE_CHAR = 0x03;// size - 1 (0...1) ubyte[size] unsigned two-byte integer value, zero-extended
public final static byte VALUE_INT = 0x04;// size - 1 (0...3) ubyte[size] signed four-byte integer value, sign-extended
public final static byte VALUE_LONG = 0x06;// size - 1 (0...7) ubyte[size] signed eight-byte integer value, sign-extended
public final static byte VALUE_FLOAT = 0x10;// size - 1 (0...3) ubyte[size] four-byte bit pattern, zero-extended to the right, and interpreted as an IEEE754 32-bit floating point value
public final static byte VALUE_DOUBLE = 0x11;// size - 1 (0...7) ubyte[size] eight-byte bit pattern, zero-extended to the right, and interpreted as an IEEE754 64-bit floating point value
public final static byte VALUE_STRING = 0x17;// size - 1 (0...3) ubyte[size] unsigned (zero-extended) four-byte integer value, interpreted as an index into the string_ids section and representing a
// string value
public final static byte VALUE_TYPE = 0x18;// size - 1 (0...3) ubyte[size] unsigned (zero-extended) four-byte integer value, interpreted as an index into the type_ids section and representing a
// reflective type/class value
public final static byte VALUE_FIELD = 0x19;// size - 1 (0...3) ubyte[size] unsigned (zero-extended) four-byte integer value, interpreted as an index into the field_ids section and representing a
// reflective field value
public final static byte VALUE_METHOD = 0x1a;// size - 1 (0...3) ubyte[size] unsigned (zero-extended) four-byte integer value, interpreted as an index into the method_ids section and representing a
// reflective method value
public final static byte VALUE_ENUM = 0x1b;// size - 1 (0...3) ubyte[size] unsigned (zero-extended) four-byte integer value, interpreted as an index into the field_ids section and representing the
// value of an enumerated type constant
public final static byte VALUE_ARRAY = 0x1c;// (none; must be 0) encoded_array an array of values, in the format specified by "encoded_array format" below. The size of the value is implicit in the
// encoding.
public final static byte VALUE_ANNOTATION = 0x1d;// (none; must be 0) encoded_annotation a sub-annotation, in the format specified by "encoded_annotation format" below. The size of the value is
// implicit in the encoding.
public final static byte VALUE_NULL = 0x1e;// (none; must be 0) (none) null reference value
public final static byte VALUE_BOOLEAN = 0x1f;// boolean (0...1) (none) one-bit value; 0 for false and 1 for true. The bit is represented in the value_arg.
public final static String toString( byte value ) {
try {
Field [ ] fields = ValueFormats.class.getDeclaredFields( );
for ( Field field : fields ) {
if ( field.getByte( null ) == value ) {
return field.getName( );
}
}
}
catch ( Exception e ) {
// ignore
}
return "Value:" + value;
}
}

View file

@ -0,0 +1,193 @@
/* ###
* 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.dex.util;
import java.util.StringTokenizer;
import ghidra.file.formats.android.dex.format.*;
import ghidra.program.model.address.*;
import ghidra.program.model.data.*;
import ghidra.program.model.listing.Program;
import ghidra.program.model.symbol.*;
import ghidra.util.exception.DuplicateNameException;
import ghidra.util.exception.InvalidInputException;
public final class DexUtil {
public final static long METHOD_ADDRESS = 0x50000000;
public final static long LOOKUP_ADDRESS = 0xE0000000;
public final static long MAX_METHOD_LENGTH = (long) Math.pow(2, 16) * 4;
public final static String CLASSDEF_NAME = "__classdef__";
public final static String CATEGORY_PATH = "classes/";
public final static String HANDLE_PATH = "/handles/";
public static Address toLookupAddress(Program program, int methodIndex) {
AddressFactory addressFactory = program.getAddressFactory();
AddressSpace defaultAddressSpace = addressFactory.getDefaultAddressSpace();
return defaultAddressSpace.getAddress(DexUtil.LOOKUP_ADDRESS + (methodIndex * 4));
}
public static Namespace getOrCreateNameSpace(Program program, String name) {
SymbolTable symbolTable = program.getSymbolTable();
Namespace parent = program.getGlobalNamespace();
Namespace namespace = symbolTable.getNamespace(name, parent);
if (namespace != null) {
return namespace;
}
try {
return symbolTable.createNameSpace(parent, name, SourceType.ANALYSIS);
}
catch (Exception e) {
return program.getGlobalNamespace();
}
}
public static Namespace createNameSpaceFromMangledClassName(Program program, String className)
throws InvalidInputException {
Namespace namespace = program.getGlobalNamespace();
return createNameSpaceFromMangledClassName(program, namespace, className);
}
public static Namespace createNameSpaceFromMangledClassName(Program program,
Namespace parentNamespace, String className) throws InvalidInputException {
SymbolTable symbolTable = program.getSymbolTable();
if (className.startsWith("L") && className.endsWith(";")) {
String str = className.substring(1, className.length() - 1);
StringTokenizer tokenizer = new StringTokenizer(str, "/");
while (tokenizer.hasMoreTokens()) {
String token = tokenizer.nextToken();
Namespace ns = symbolTable.getNamespace(token, parentNamespace);
if (ns != null) {
parentNamespace = ns;
continue;
}
try {
if (tokenizer.hasMoreElements()) { // package name
parentNamespace = symbolTable.createNameSpace(parentNamespace, token,
SourceType.ANALYSIS);
}
else { // last token should be the class name
parentNamespace =
symbolTable.createClass(parentNamespace, token, SourceType.ANALYSIS);
}
}
catch (DuplicateNameException e) {
// Should never reach here because we already checked for the symbol name
return null;
}
}
}
return parentNamespace;
}
public static String convertTypeIndexToString(DexHeader header, short typeIndex) {
return convertTypeIndexToString(header, typeIndex & 0xffff);
}
public static String convertTypeIndexToString(DexHeader header, int typeIndex) {
if (typeIndex == -1) {//java.lang.Object, no super class
return "<none>";
}
TypeIDItem typeItem = header.getTypes().get(typeIndex);
return convertToString(header, typeItem.getDescriptorIndex());
}
public static String convertToString(DexHeader header, int stringIndex) {
StringIDItem stringItem = header.getStrings().get(stringIndex);
return stringItem.getStringDataItem().getString();
}
public static String convertPrototypeIndexToString(DexHeader header, short prototypeIndex) {
PrototypesIDItem prototype = header.getPrototypes().get(prototypeIndex & 0xffff);
StringBuilder builder = new StringBuilder();
builder.append(convertTypeIndexToString(header, prototype.getReturnTypeIndex()));
builder.append("( ");
TypeList parameters = prototype.getParameters();
if (parameters != null) {
for (TypeItem parameter : parameters.getItems()) {
builder.append(convertTypeIndexToString(header, parameter.getType()));
builder.append("\n\t");
}
}
builder.append(" )");
return builder.toString();
}
public static String[] convertClassStringToPathArray(String prefix, String classString) {
int len = classString.length();
if (len == 0) {
return null;
}
if (classString.charAt(0) != 'L') {
return null;
}
if (classString.charAt(len - 1) != ';') {
return null;
}
return (prefix + classString.substring(1, len - 1)).split("/");
}
public static DataType toDataType(DataTypeManager dtm, String dataTypeString) {
if (dataTypeString.length() == 0) {
return null;
}
switch (dataTypeString.charAt(0)) {
case 'B':
return SignedByteDataType.dataType;
case 'C':
return CharDataType.dataType;
case 'D':
return DoubleDataType.dataType;
case 'F':
return FloatDataType.dataType;
case 'I':
return IntegerDataType.dataType;
case 'J':
return new LongDataType(dtm);
case 'S':
return ShortDataType.dataType;
case 'Z':
return BooleanDataType.dataType;
case 'V':
return VoidDataType.dataType;
case 'L':
String[] path = convertClassStringToPathArray(CATEGORY_PATH, dataTypeString);
StringBuilder builder = new StringBuilder();
for (int i = 0; i < path.length - 1; ++i) {
builder.append(CategoryPath.DELIMITER_CHAR).append(path[i]);
}
CategoryPath catPath = new CategoryPath(builder.toString());
DataType exist = dtm.getDataType(catPath, path[path.length - 1]);
if (exist == null) {
exist = Undefined4DataType.dataType;
}
return new PointerDataType(exist, dtm);
case '[':
DataType subDataType = toDataType(dtm, dataTypeString.substring(1));
return new PointerDataType(subDataType, dtm);
default:
return null;
}
}
}

View file

@ -0,0 +1,202 @@
/* ###
* IP: Apache License 2.0
*/
/*
* Copyright (C) 2008 The Android Open Source Project
*
* 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.dex.util;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import com.googlecode.d2j.DexException;
import ghidra.util.NumericUtilities;
/**
* Reads and writes DWARFv3 LEB 128 signed and unsigned integers. See DWARF v3 section 7.6.
*/
public final class Leb128 {
private Leb128() {
}
/**
* Gets the number of bytes in the unsigned LEB128 encoding of the given value.
*
* @param value
* the value in question
* @return its write size, in bytes
*/
public static int unsignedLeb128Size( int value ) {
// TODO: This could be much cleverer.
int remaining = value >> 7;
int count = 0;
while ( remaining != 0 ) {
remaining >>= 7;
count++;
}
return count + 1;
}
/**
* Gets the number of bytes in the signed LEB128 encoding of the given value.
*
* @param value
* the value in question
* @return its write size, in bytes
*/
public static int signedLeb128Size( int value ) {
// TODO: This could be much cleverer.
int remaining = value >> 7;
int count = 0;
boolean hasMore = true;
int end = ( ( value & Integer.MIN_VALUE ) == 0 ) ? 0 : -1;
while ( hasMore ) {
hasMore = ( remaining != end ) || ( ( remaining & 1 ) != ( ( value >> 6 ) & 1 ) );
value = remaining;
remaining >>= 7;
count++;
}
return count;
// ByteArrayOutputStream out = new ByteArrayOutputStream( );
// int remaining = value >>> 7;
//
// while ( remaining != 0 ) {
// out.write( ( byte ) ( ( value & 0x7f ) | 0x80 ) );
// value = remaining;
// remaining >>>= 7;
// }
//
// out.write( ( byte ) ( value & 0x7f ) );
//
// return out.toByteArray( ).length;
}
public static int readSignedLeb128( byte [] bytes ) {
return readSignedLeb128( new ByteArrayInputStream( bytes ) );
}
/**
* Reads an signed integer from {@code in}.
*/
public static int readSignedLeb128( ByteArrayInputStream in ) {
int result = 0;
int cur;
int count = 0;
int signBits = -1;
do {
cur = in.read( ) & 0xff;
result |= ( cur & 0x7f ) << ( count * 7 );
signBits <<= 7;
count++;
}
while ( ( ( cur & 0x80 ) == 0x80 ) && count < 5 );
if ( ( cur & 0x80 ) == 0x80 ) {
throw new DexException( "invalid LEB128 sequence" );
}
// Sign extend if appropriate
if ( ( ( signBits >> 1 ) & result ) != 0 ) {
result |= signBits;
}
return result;
}
public static int readUnsignedLeb128( byte [] bytes ) {
return readUnsignedLeb128( new ByteArrayInputStream( bytes ) );
}
/**
* Reads an unsigned integer from {@code in}.
*/
public static int readUnsignedLeb128( ByteArrayInputStream in ) {
int result = 0;
int cur;
int count = 0;
do {
cur = in.read( ) & 0xff;
result |= ( cur & 0x7f ) << ( count * 7 );
count++;
}
while ( ( ( cur & 0x80 ) == 0x80 ) && count < 5 );
if ( ( cur & 0x80 ) == 0x80 ) {
throw new DexException( "invalid LEB128 sequence" );
}
return result;
}
/**
* Writes {@code value} as an unsigned integer to {@code out}, starting at {@code offset}. Returns the number of bytes written.
*/
public static void writeUnsignedLeb128( ByteArrayOutputStream out, int value ) {
int remaining = value >>> 7;
while ( remaining != 0 ) {
out.write( ( byte ) ( ( value & 0x7f ) | 0x80 ) );
value = remaining;
remaining >>>= 7;
}
out.write( ( byte ) ( value & 0x7f ) );
}
/**
* Writes {@code value} as a signed integer to {@code out}, starting at {@code offset}. Returns the number of bytes written.
*/
public static void writeSignedLeb128( ByteArrayOutputStream out, int value ) {
int remaining = value >> 7;
boolean hasMore = true;
int end = ( ( value & Integer.MIN_VALUE ) == 0 ) ? 0 : -1;
while ( hasMore ) {
hasMore = ( remaining != end ) || ( ( remaining & 1 ) != ( ( value >> 6 ) & 1 ) );
out.write( ( byte ) ( ( value & 0x7f ) | ( hasMore ? 0x80 : 0 ) ) );
value = remaining;
remaining >>= 7;
}
}
public static void main( String [] args ) {
//ByteArrayOutputStream out = new ByteArrayOutputStream( );
//writeSignedLeb128( out, -12345 );
//System.out.println( "array length: " + out.toByteArray( ).length );
//System.out.println( "actual length: " + signedLeb128Size( -12345 ) );
//System.out.println( readSignedLeb128( out.toByteArray( ) ) );
System.out.println( readUnsignedLeb128( NumericUtilities.convertStringToBytes("807f" ) ) );
}
}

View file

@ -0,0 +1,112 @@
/* ###
* 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.kernel;
import java.io.IOException;
import java.io.InputStream;
import java.util.*;
import ghidra.app.util.bin.ByteProvider;
import ghidra.formats.gfilesystem.*;
import ghidra.formats.gfilesystem.annotations.FileSystemInfo;
import ghidra.formats.gfilesystem.factory.GFileSystemBaseFactory;
import ghidra.util.exception.CancelledException;
import ghidra.util.exception.CryptoException;
import ghidra.util.task.TaskMonitor;
@FileSystemInfo(type = "androidkernel", description = "Android Compressed Kernel", factory = GFileSystemBaseFactory.class)
public class KernelFileSystem extends GFileSystemBase {
private static final int INDEX_WHERE_TO_START = 0x2000;//where to start looking for compressed kernel
private GFile compressedKernelFile = null;
private long compressedKernelIndex;
private long compressedKernelLength;
public KernelFileSystem(String fileSystemName, ByteProvider provider) {
super(fileSystemName, provider);
}
@Override
public boolean isValid(TaskMonitor monitor) throws IOException {
byte[] bytes = provider.readBytes(0, 0x20);
for (int i = 0; i < bytes.length; i += 4) {
if (bytes[i] == (byte) 0x00 && bytes[i + 1] == (byte) 0x00 &&
bytes[i + 2] == (byte) 0xa0 && bytes[i + 3] == (byte) 0xe1) {
continue;
}
return false;
}
return true;
}
@Override
public void open(TaskMonitor monitor) throws IOException, CryptoException, CancelledException {
// Scan through the file looking for the message below.
// The next byte after the message should be the compressed kernel
final String message = "uncompression error";
int index = INDEX_WHERE_TO_START;
monitor.setMaximum(provider.length());
while (index < provider.length() - message.length() + 1) {
monitor.checkCanceled();
monitor.setProgress(index);
String actualMessage = new String(provider.readBytes(index, message.length()));
if (message.equals(actualMessage)) {// immediately following this string is the compressed pay-load....
compressedKernelIndex =
(index & 0xffffffffL) + (message.length() & 0xffffffffL) + 1;
compressedKernelLength = provider.length() - compressedKernelIndex;
compressedKernelFile = GFileImpl.fromFilename(this, root, "compressed-kernel",
false, compressedKernelLength, null);
break;
}
++index;
}
}
@Override
public List<GFile> getListing(GFile directory) throws IOException {
return (directory == null || directory.equals(root)) && (compressedKernelFile != null)
? Arrays.asList(compressedKernelFile)
: Collections.emptyList();
}
@Override
public String getInfo(GFile file, TaskMonitor monitor) throws IOException {
return null;
}
@Override
protected InputStream getData(GFile file, TaskMonitor monitor)
throws IOException, CancelledException, CryptoException {
if (compressedKernelFile != null) {
return provider.getInputStream(compressedKernelIndex);
}
return null;
}
}

View file

@ -0,0 +1,39 @@
/* ###
* 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.odex;
import ghidra.app.util.bin.ByteProvider;
public final class OdexConstants {
public final static String ODEX_MAGIC_35 = "dey\n035\0";
public final static String ODEX_MAGIC_36 = "dey\n036\0";
public final static String ODEX_MAGIC_37 = "dey\n037\0";
public final static int ODEX_MAGIC_LENGTH = ODEX_MAGIC_36.length();
public final static boolean isOdexFile( ByteProvider provider ) {
try {
byte [] bytes = provider.readBytes( 0, ODEX_MAGIC_LENGTH );
return ODEX_MAGIC_35.equals( new String( bytes ) ) ||
ODEX_MAGIC_36.equals( new String( bytes ) );
}
catch ( Exception e ) {
// ignore
}
return false;
}
}

View file

@ -0,0 +1,120 @@
/* ###
* 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.odex;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import ghidra.app.util.bin.BinaryReader;
import ghidra.app.util.bin.ByteProvider;
import ghidra.formats.gfilesystem.*;
import ghidra.formats.gfilesystem.annotations.FileSystemInfo;
import ghidra.formats.gfilesystem.factory.GFileSystemBaseFactory;
import ghidra.util.BoundedInputStream;
import ghidra.util.exception.CancelledException;
import ghidra.util.exception.CryptoException;
import ghidra.util.task.TaskMonitor;
@FileSystemInfo(type = "odex", description = "ODEX", factory = GFileSystemBaseFactory.class)
public class OdexFileSystem extends GFileSystemBase {
private OdexHeader odexHeader;
private GFileImpl dexFile;
private GFileImpl depsFile;
private GFileImpl auxFile;
public OdexFileSystem(String fileSystemName, ByteProvider provider) {
super(fileSystemName, provider);
}
@Override
protected InputStream getData(GFile file, TaskMonitor monitor)
throws IOException, CancelledException, CryptoException {
if (file != null) {
if (file.equals(dexFile)) {
return new BoundedInputStream(provider.getInputStream(odexHeader.getDexOffset()),
odexHeader.getDexLength());
}
if (file.equals(depsFile)) {
return new BoundedInputStream(provider.getInputStream(odexHeader.getDepsOffset()),
odexHeader.getDepsLength());
}
if (file.equals(auxFile)) {
return new BoundedInputStream(provider.getInputStream(odexHeader.getAuxOffset()),
odexHeader.getAuxLength());
}
}
return null;
}
@Override
public String getInfo(GFile file, TaskMonitor monitor) throws IOException {
StringBuilder builder = new StringBuilder();
builder.append("Magic: " + odexHeader.getMagic()).append("\n");
builder.append("Dex Offset: " + Integer.toHexString(odexHeader.getDexOffset())).append(
"\n");
builder.append("Dex Length: " + Integer.toHexString(odexHeader.getDexLength())).append(
"\n");
builder.append("Deps Offset: " + Integer.toHexString(odexHeader.getDepsOffset())).append(
"\n");
builder.append("Deps Length: " + Integer.toHexString(odexHeader.getDepsLength())).append(
"\n");
builder.append("Aux Offset: " + Integer.toHexString(odexHeader.getAuxOffset())).append(
"\n");
builder.append("Aux Length: " + Integer.toHexString(odexHeader.getAuxLength())).append(
"\n");
builder.append("Flags: " + Integer.toHexString(odexHeader.getFlags())).append("\n");
return builder.toString();
}
@Override
public List<GFile> getListing(GFile directory) throws IOException {
List<GFile> list = new ArrayList<>();
if (directory == null || directory.equals(root)) {
list.add(dexFile);
list.add(depsFile);
list.add(auxFile);
}
return list;
}
@Override
public boolean isValid(TaskMonitor monitor) throws IOException {
return OdexConstants.isOdexFile(provider);
}
@Override
public void open(TaskMonitor monitor) throws IOException, CryptoException, CancelledException {
BinaryReader reader = new BinaryReader(provider, true);
odexHeader = new OdexHeader(reader);
dexFile = GFileImpl.fromFilename(this, root, "dex", false, odexHeader.getDexLength(), null);
depsFile =
GFileImpl.fromFilename(this, root, "deps", false, odexHeader.getDepsLength(), null);
auxFile = GFileImpl.fromFilename(this, root, "aux", false, odexHeader.getAuxLength(), null);
}
@Override
public void close() throws IOException {
super.close();
odexHeader = null;
dexFile = null;
}
}

View file

@ -0,0 +1,115 @@
/* ###
* 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.odex;
import java.io.IOException;
import ghidra.app.util.bin.BinaryReader;
import ghidra.app.util.bin.StructConverter;
import ghidra.program.model.data.DataType;
import ghidra.program.model.data.Structure;
import ghidra.program.model.data.StructureDataType;
import ghidra.util.exception.DuplicateNameException;
public class OdexHeader implements StructConverter {
private byte [] magic;
private int dexOffset;
private int dexLength;
private int depsOffset;
private int depsLength;
private int auxOffset;
private int auxLength;
private int flags;
private int padding;
public OdexHeader( BinaryReader reader ) throws IOException {
magic = reader.readNextByteArray( OdexConstants.ODEX_MAGIC_LENGTH );
dexOffset = reader.readNextInt();
dexLength = reader.readNextInt();
depsOffset = reader.readNextInt();
depsLength = reader.readNextInt();
auxOffset = reader.readNextInt();
auxLength = reader.readNextInt();
flags = reader.readNextInt();
padding = reader.readNextInt();
}
public String getMagic() {
return new String( magic );
}
/**
* Returns byte offset to the optimized DEX file.
*/
public int getDexOffset() {
return dexOffset;
}
/**
* Returns byte length of the optimized DEX file.
*/
public int getDexLength() {
return dexLength;
}
/**
* Return byte offset to the framework dependencies.
*/
public int getDepsOffset() {
return depsOffset;
}
/**
* Return byte length of the framework dependencies.
*/
public int getDepsLength() {
return depsLength;
}
/**
* Return byte offset to the auxiliary data.
*/
public int getAuxOffset() {
return auxOffset;
}
/**
* Return byte length to the auxiliary data.
*/
public int getAuxLength() {
return auxLength;
}
/**
* Return the ODEX flags.
*/
public int getFlags() {
return flags;
}
public int getPadding() {
return padding;
}
@Override
public DataType toDataType() throws DuplicateNameException, IOException {
Structure structure = new StructureDataType( "odex_header_item", 0 );
structure.add( UTF8, OdexConstants.ODEX_MAGIC_LENGTH, "magic", null );
structure.add( DWORD, "dex_offset", null );
structure.add( DWORD, "dex_length", null );
structure.add( DWORD, "deps_offset", null );
structure.add( DWORD, "deps_length", null );
structure.add( DWORD, "aux_offset", null );
structure.add( DWORD, "aux_length", null );
structure.add( DWORD, "flags", null );
structure.add( DWORD, "padding", null );
return structure;
}
}

View file

@ -0,0 +1,153 @@
/* ###
* 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.odex;
import ghidra.app.services.AnalysisPriority;
import ghidra.app.services.AnalyzerType;
import ghidra.app.util.bin.BinaryReader;
import ghidra.app.util.bin.ByteProvider;
import ghidra.app.util.bin.MemoryByteProvider;
import ghidra.app.util.importer.MessageLog;
import ghidra.file.analyzers.FileFormatAnalyzer;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressSetView;
import ghidra.program.model.data.DWordDataType;
import ghidra.program.model.data.DataType;
import ghidra.program.model.data.StringDataType;
import ghidra.program.model.listing.Program;
import ghidra.program.model.mem.Memory;
import ghidra.program.model.mem.MemoryBlock;
import ghidra.util.task.TaskMonitor;
public class OdexHeaderFormatAnalyzer extends FileFormatAnalyzer {
@Override
public boolean analyze( Program program, AddressSetView set, TaskMonitor monitor, MessageLog log ) throws Exception {
Address address = toAddr( program, 0x0 );
if ( getDataAt( program, address ) != null ) {
log.appendMsg( "data already exists." );
return true;
}
Memory memory = program.getMemory( );
MemoryBlock block = memory.getBlock( "ram" );
block.setRead( true );
block.setWrite( false );
block.setExecute( false );
ByteProvider provider = new MemoryByteProvider( program.getMemory( ), program.getMinAddress( ) );
BinaryReader reader = new BinaryReader( provider, true );
OdexHeader header = new OdexHeader( reader );
DataType headerDataType = header.toDataType();
createData( program, address, headerDataType);
createFragment(program, "header", address, address.add(headerDataType.getLength()));
Address dexAddress = toAddr(program, header.getDexOffset());
createFragment(program, "dex", dexAddress, dexAddress.add(header.getDexLength()));
Address depsAddress = toAddr(program, header.getDepsOffset());
createFragment(program, "deps", depsAddress, depsAddress.add(header.getDepsLength()));
processDeps( program, header, monitor, log );
Address auxAddress = toAddr(program, header.getAuxOffset());
createFragment(program, "aux", auxAddress, auxAddress.add(header.getAuxLength()));
monitor.setMessage( "ODEX: cleaning up tree" );
removeEmptyFragments( program );
return true;
}
@Override
public boolean canAnalyze( Program program ) {
ByteProvider provider = new MemoryByteProvider( program.getMemory( ), program.getMinAddress( ) );
return OdexConstants.isOdexFile( provider );
}
@Override
public AnalyzerType getAnalysisType( ) {
return AnalyzerType.BYTE_ANALYZER;
}
@Override
public boolean getDefaultEnablement( Program program ) {
return true;
}
@Override
public String getDescription( ) {
return "Android ODEX Header Format";
}
@Override
public String getName( ) {
return "Android ODEX Header Format";
}
@Override
public AnalysisPriority getPriority( ) {
return new AnalysisPriority( 0 );
}
@Override
public boolean isPrototype( ) {
return false;
}
private void processDeps(Program program, OdexHeader header,
TaskMonitor monitor, MessageLog log) throws Exception {
int depsOffset = header.getDepsOffset();
int depsLength = header.getDepsLength();
Address depsAddress = toAddr( program, depsOffset );
Address depsEndAddress = depsAddress.add( depsLength );
createData( program, depsAddress, new DWordDataType() );
depsAddress = depsAddress.add( 4 );
createData( program, depsAddress, new DWordDataType() );
depsAddress = depsAddress.add( 4 );
createData( program, depsAddress, new DWordDataType() );
depsAddress = depsAddress.add( 4 );
createData( program, depsAddress, new DWordDataType() );
depsAddress = depsAddress.add( 4 );
while ( depsAddress.compareTo(depsEndAddress) < 0 ) {
monitor.checkCanceled();
createData( program, depsAddress, new DWordDataType() );
int stringLength = program.getMemory().getInt(depsAddress);
depsAddress = depsAddress.add( 4 );
program.getListing().createData(depsAddress, new StringDataType(), stringLength);
depsAddress = depsAddress.add( stringLength );
for ( int i = 0; i < 5; ++i ) {
createData( program, depsAddress, new DWordDataType() );
depsAddress = depsAddress.add( 4 );
}
}
}
}

View file

@ -0,0 +1,185 @@
/* ###
* IP: Apache License 2.0
*/
package ghidra.file.formats.android.xml;
import ghidra.util.StringUtilities;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor;
import java.io.*;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import android.content.res.AXmlResourceParser;
import android.util.TypedValue;
/**
* NOTE: most of this logic was copied form AndroidXmlFileSystem, which had the following
* note: "most of this code was hijacked from AXMLPrinter.java class!"
*
*/
public class AndroidXmlConvertor {
private static final float[] RADIX_MULTS =
{ 0.00390625F, 3.051758E-05F, 1.192093E-07F, 4.656613E-10F };
private static final String[] DIMENSION_UNITS = { "px", "dip", "sp", "pt", "in", "mm", "", "" };
private static final String[] FRACTION_UNITS = { "%", "%p", "", "", "", "", "", "" };
public static final byte[] ANDROID_BINARY_XML_MAGIC = { 0x03, 0x00, 0x08, 0x00 };
public static final int ANDROID_BINARY_XML_MAGIC_LEN = 4;
public static void convert(InputStream is, PrintWriter out, TaskMonitor monitor)
throws IOException, CancelledException {
monitor.setMessage("Converting Android Binary XML to Text...");
AXmlResourceParser parser = new AXmlResourceParser();
parser.open(is);
try {
int indent = -1;
int type;
while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
monitor.checkCanceled();
StringBuffer buffer = new StringBuffer();
switch (type) {
case XmlPullParser.START_DOCUMENT:
buffer.append("<?xml version=\"1.0\" encoding=\"utf-8\"?>");
buffer.append("\n");
break;
case XmlPullParser.START_TAG: {
++indent;
buffer.append(StringUtilities.pad("", '\t', indent));
buffer.append("<");
buffer.append(getNamespacePrefix(parser.getPrefix()));
buffer.append(parser.getName());
buffer.append("\n");
int namespaceCountBefore = parser.getNamespaceCount(parser.getDepth() - 1);
int namespaceCount = parser.getNamespaceCount(parser.getDepth());
++indent;
for (int i = namespaceCountBefore; i != namespaceCount; ++i) {
buffer.append(StringUtilities.pad("", '\t', indent));
buffer.append("xmlns:");
buffer.append(parser.getNamespacePrefix(i));
buffer.append("=");
buffer.append("\"");
buffer.append(parser.getNamespaceUri(i));
buffer.append("\"");
buffer.append("\n");
}
for (int i = 0; i < parser.getAttributeCount(); ++i) {
buffer.append(StringUtilities.pad("", '\t', indent));
buffer.append(getNamespacePrefix(parser.getAttributePrefix(i)));
buffer.append(parser.getAttributeName(i));
buffer.append("=");
buffer.append("\"");
buffer.append(getAttributeValue(parser, i));
buffer.append("\"");
buffer.append("\n");
}
buffer.append(StringUtilities.pad("", '\t', indent));
buffer.append(">");
buffer.append("\n");
--indent;
}
break;
case XmlPullParser.END_TAG: {
buffer.append(StringUtilities.pad("", '\t', indent));
buffer.append("<");
buffer.append("/");
buffer.append(getNamespacePrefix(parser.getPrefix()));
buffer.append(parser.getName());
buffer.append(">");
buffer.append("\n");
--indent;
}
break;
case XmlPullParser.TEXT: {
buffer.append(StringUtilities.pad("", '\t', indent));
buffer.append(parser.getText());
buffer.append("\n");
}
}
out.print(buffer.toString());
}
out.println();
}
catch (XmlPullParserException | ArrayIndexOutOfBoundsException e) {
throw new IOException("Failed to read AXML file", e);
}
finally {
parser.close();
}
}
private static String getNamespacePrefix(String prefix) {
if (prefix == null || prefix.length() == 0) {
return "";
}
return prefix + ":";
}
private static String getAttributeValue(AXmlResourceParser parser, int index) {
int type = parser.getAttributeValueType(index);
int data = parser.getAttributeValueData(index);
if (type == TypedValue.TYPE_STRING) {
return parser.getAttributeValue(index);
}
if (type == TypedValue.TYPE_ATTRIBUTE) {
return String.format("?%s%08X", getPackage(data), data);
}
if (type == TypedValue.TYPE_REFERENCE) {
return String.format("@%s%08X", getPackage(data), data);
}
if (type == TypedValue.TYPE_FLOAT) {
return String.valueOf(Float.intBitsToFloat(data));
}
if (type == TypedValue.TYPE_INT_HEX) {
return String.format("0x%08X", data);
}
if (type == TypedValue.TYPE_INT_BOOLEAN) {
return data == 0 ? "false" : "true";
}
if (type == TypedValue.TYPE_DIMENSION) {
return Float.toString(complexToFloat(data)) +
DIMENSION_UNITS[data & TypedValue.COMPLEX_UNIT_MASK];
}
if (type == TypedValue.TYPE_FRACTION) {
return Float.toString(complexToFloat(data)) +
FRACTION_UNITS[data & TypedValue.COMPLEX_UNIT_MASK];
}
if (type >= TypedValue.TYPE_FIRST_COLOR_INT && type <= TypedValue.TYPE_LAST_COLOR_INT) {
return String.format("#%08X", data);
}
if (type >= TypedValue.TYPE_FIRST_INT && type <= TypedValue.TYPE_LAST_INT) {
return String.valueOf(data);
}
return String.format("<0x%X, type 0x%02X>", data, type);
}
private static String getPackage(int id) {
if (id >>> 24 == 1) {
return "android:";
}
return "";
}
private static float complexToFloat(int complex) {
return (complex & 0xffffff00) * RADIX_MULTS[complex >> 4 & 3];
}
}

View file

@ -0,0 +1,110 @@
/* ###
* 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.xml;
import java.io.*;
import java.util.*;
import ghidra.app.util.bin.*;
import ghidra.formats.gfilesystem.*;
import ghidra.formats.gfilesystem.annotations.FileSystemInfo;
import ghidra.formats.gfilesystem.factory.GFileSystemBaseFactory;
import ghidra.util.exception.CancelledException;
import ghidra.util.exception.CryptoException;
import ghidra.util.task.TaskMonitor;
/**
* This is a {@link GFileSystem} that provides a single file, which is the text version
* of a binary android xml file.
* <p>
*
* NOTE: most of this code was hijacked from AXMLPrinter.java class!
*
*/
@FileSystemInfo(type = "androidxml", description = "Android XML", factory = GFileSystemBaseFactory.class)
public class AndroidXmlFileSystem extends GFileSystemBase {
public static boolean isAndroidXmlFile(File f, TaskMonitor monitor) throws IOException {
try (RandomAccessByteProvider rabp = new RandomAccessByteProvider(f)) {
return isAndroidXmlFile(rabp, monitor);
}
}
public static boolean isAndroidXmlFile(ByteProvider provider, TaskMonitor monitor)
throws IOException {
byte[] actualBytes =
provider.readBytes(0, AndroidXmlConvertor.ANDROID_BINARY_XML_MAGIC.length);
if (!Arrays.equals(actualBytes, AndroidXmlConvertor.ANDROID_BINARY_XML_MAGIC)) {
return false;
}
try (InputStream is = new ByteProviderInputStream(provider, 0, provider.length())) {
StringWriter sw = new StringWriter();
AndroidXmlConvertor.convert(is, new PrintWriter(sw), monitor);
return true;
}
catch (IOException | CancelledException e) {
return false;
}
}
private GFileImpl payloadFile;
private byte[] payloadBytes;
public AndroidXmlFileSystem(String fileSystemName, ByteProvider provider) {
super(fileSystemName, provider);
}
public GFile getPayloadFile() {
return payloadFile;
}
@Override
public boolean isValid(TaskMonitor monitor) throws IOException {
return isAndroidXmlFile(provider, monitor);
}
@Override
public void open(TaskMonitor monitor) throws IOException, CryptoException, CancelledException {
try (InputStream is = new ByteProviderInputStream(provider, 0, provider.length())) {
StringWriter sw = new StringWriter();
AndroidXmlConvertor.convert(is, new PrintWriter(sw), monitor);
payloadBytes = sw.toString().getBytes();
}
catch (IOException e) {
payloadBytes = "failed to convert".getBytes();
}
payloadFile = GFileImpl.fromFilename(this, root, "XML", false, payloadBytes.length, null);
}
@Override
protected InputStream getData(GFile file, TaskMonitor monitor)
throws IOException, CancelledException, CryptoException {
return new ByteArrayInputStream(payloadBytes);
}
@Override
public String getInfo(GFile file, TaskMonitor monitor) throws IOException {
return null;
}
@Override
public List<GFile> getListing(GFile directory) throws IOException {
List<GFile> tmp = new ArrayList<>();
tmp.add(payloadFile);
return tmp;
}
}

View file

@ -0,0 +1,218 @@
/* ###
* 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.bplist;
import java.util.HashMap;
import java.util.Map;
import ghidra.app.util.bin.BinaryReader;
import ghidra.app.util.bin.ByteProvider;
import ghidra.app.util.importer.MessageLog;
import ghidra.file.analyzers.FileFormatAnalyzer;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressSetView;
import ghidra.program.model.data.*;
import ghidra.program.model.listing.Data;
import ghidra.program.model.listing.Program;
import ghidra.program.model.mem.Memory;
import ghidra.program.model.mem.MemoryBlock;
import ghidra.program.model.symbol.SourceType;
import ghidra.util.task.TaskMonitor;
public class BinaryPropertyListAnalyzer extends FileFormatAnalyzer {
@Override
public String getName() {
return "Binary Property List (BPLIST) Annotation";
}
@Override
public boolean getDefaultEnablement(Program program) {
return false;// return canAnalyze( program );
}
@Override
public String getDescription() {
return "Annotates a Binary Property List (BPLIST).";
}
@Override
public boolean canAnalyze(Program program) {
// a binary plist does not specify it's length in the header,
// the file determines the length.
// therefore, a bplist must exists in it's own block
// search through each block looking for the magic number
Memory memory = program.getMemory();
MemoryBlock[] blocks = memory.getBlocks();
for (MemoryBlock block : blocks) {
if (BinaryPropertyListUtil.isBinaryPropertyList(memory, block.getStart())) {
return true;
}
}
return false;
}
@Override
public boolean isPrototype() {
return false;
}
@Override
public boolean analyze(Program program, AddressSetView set, TaskMonitor monitor, MessageLog log)
throws Exception {
Memory memory = program.getMemory();
for (MemoryBlock block : memory.getBlocks()) {
monitor.checkCanceled();
if (BinaryPropertyListUtil.isBinaryPropertyList(memory, block.getStart())) {
ByteProvider provider =
new ImmutableMemoryRangeByteProvider(memory, block.getStart(), block.getEnd());
markup(block.getStart(), provider, program, monitor);
}
}
removeEmptyFragments(program);
return true;
}
private void markup(Address baseAddress, ByteProvider provider, Program program,
TaskMonitor monitor) throws Exception {
BinaryReader reader = new BinaryReader(provider, false /* always big */);
try {
BinaryPropertyListHeader header =
markupBinaryPropertyListHeader(program, reader, baseAddress);
BinaryPropertyListTrailer trailer =
markupBinaryPropertyListTrailer(program, header, baseAddress);
markupObjects(reader, program, trailer, baseAddress, monitor);
markupOffsetTable(program, trailer, baseAddress, monitor);
}
finally {
provider.close();
}
}
private void markupOffsetTable(Program program, BinaryPropertyListTrailer trailer,
Address baseAddress, TaskMonitor monitor) throws Exception {
DataType offsetDataType = null;
if (trailer.getOffsetSize() == 1) {
offsetDataType = new ByteDataType();
}
else if (trailer.getOffsetSize() == 2) {
offsetDataType = new WordDataType();
}
else if (trailer.getOffsetSize() == 4) {
offsetDataType = new DWordDataType();
}
else if (trailer.getOffsetSize() == 8) {
offsetDataType = new QWordDataType();
}
else {
throw new RuntimeException("unexpected offset table element size");
}
Address offsetTableAddress = baseAddress.add(trailer.getOffsetTableOffset());
Address end = offsetTableAddress.add(trailer.getObjectCount() * offsetDataType.getLength());
ArrayDataType datatype =
new ArrayDataType(offsetDataType, trailer.getObjectCount(), offsetDataType.getLength());
clearListing(program, offsetTableAddress, end);
createData(program, offsetTableAddress, datatype);
setPlateComment(program, offsetTableAddress, "OFFSET_TABLE");
createFragment(program, "OFFSET_TABLE", offsetTableAddress, end);
}
private void markupObjects(BinaryReader reader, Program program,
BinaryPropertyListTrailer trailer, Address baseAddress, TaskMonitor monitor)
throws Exception {
Map<NSObject, Data> objectMap = new HashMap<NSObject, Data>();
for (int i = 0; i < trailer.getOffsetTable().length; ++i) {
monitor.checkCanceled();
int objectOffset = trailer.getOffsetTable()[i];
NSObject object = NSObjectParser.parseObject(reader, objectOffset, trailer);
Address objectAddress = baseAddress.add(objectOffset);
String name = BinaryPropertyListUtil.generateName(i);
setPlateComment(program, objectAddress, name + "\n" + object.toString());
program.getSymbolTable().createLabel(objectAddress, name, SourceType.ANALYSIS);
DataType objectDataType = object.toDataType();
createFragment(program, object.getType(), objectAddress,
objectAddress.add(objectDataType.getLength()));
clearListing(program, objectAddress, objectAddress.add(objectDataType.getLength()));
Data objectData = createData(program, objectAddress, objectDataType);
objectMap.put(object, objectData);
}
// markup the NSObjects as a second pass
// because all need to be created first
for (NSObject object : objectMap.keySet()) {
monitor.checkCanceled();
object.markup(objectMap.get(object), program, monitor);
}
// markup the nested PLists
for (NSObject object : objectMap.keySet()) {
monitor.checkCanceled();
handleNestedBinaryPlist(object, program, objectMap.get(object), monitor);
}
}
private BinaryPropertyListTrailer markupBinaryPropertyListTrailer(Program program,
BinaryPropertyListHeader header, Address baseAddress) throws Exception {
BinaryPropertyListTrailer trailer = header.getTrailer();
Address trailerAddress = baseAddress.add(trailer.getTrailerIndex());
DataType trailerDataType = trailer.toDataType();
clearListing(program, trailerAddress, trailerAddress.add(trailerDataType.getLength()));
createData(program, trailerAddress, trailerDataType);
createFragment(program, trailerDataType.getName(), trailerAddress,
trailerAddress.add(trailerDataType.getLength()));
setPlateComment(program, trailerAddress, "Binary Property List Trailer");
return trailer;
}
private BinaryPropertyListHeader markupBinaryPropertyListHeader(Program program,
BinaryReader reader, Address baseAddress) throws Exception {
BinaryPropertyListHeader header = new BinaryPropertyListHeader(reader);
Address headerAddress = baseAddress.add(0x0);
DataType headerDataType = header.toDataType();
clearListing(program, headerAddress, headerAddress.add(headerDataType.getLength()));
createData(program, headerAddress, headerDataType);
createFragment(program, headerDataType.getName(), headerAddress,
headerAddress.add(headerDataType.getLength()));
setPlateComment(program, headerAddress, "Binary Property List Header");
return header;
}
private void handleNestedBinaryPlist(NSObject object, Program program, Data objectData,
TaskMonitor monitor) throws Exception {
// if ( object instanceof NSData ) {// handle nested PLISTs
// NSData data = (NSData ) object;
//
// Data component = objectData.getComponent(
// objectData.getNumComponents( ) - 1 );
//
// ByteProvider nestedProvider = new ByteArrayProvider( data.getData( )
// );
//
// if ( BinaryPropertyListUtil.isBinaryPropertyList( nestedProvider ) )
// {
//
// program.getListing( ).clearCodeUnits( objectData.getMinAddress( ),
// objectData.getMaxAddress( ), true );
//
// markup( program, component.getMinAddress( ), component.getMaxAddress(
// ), monitor );
// }
// }
}
private void clearListing(Program program, Address startAddress, Address endAddress) {
program.getListing().clearCodeUnits(startAddress, endAddress.subtract(1), true);
}
}

View file

@ -0,0 +1,25 @@
/* ###
* 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.bplist;
public final class BinaryPropertyListConstants {
public final static String BINARY_PLIST_MAGIC = "bplist";
public final static int TRAILER_SIZE = 32;
public final static int MAJOR_VERSION_0 = '0';
}

View file

@ -0,0 +1,68 @@
/* ###
* 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.bplist;
import ghidra.app.util.bin.BinaryReader;
import ghidra.app.util.bin.StructConverter;
import ghidra.program.model.data.*;
import ghidra.util.exception.DuplicateNameException;
import java.io.IOException;
public class BinaryPropertyListHeader implements StructConverter {
private String magic;
private byte majorVersion;
private byte minorVersion;
private BinaryPropertyListTrailer trailer;
public BinaryPropertyListHeader( BinaryReader reader ) throws IOException {
if ( reader.isLittleEndian( ) ) {
throw new IOException( "BinaryReader must be BIG endian!" );
}
magic = reader.readNextAsciiString( BinaryPropertyListConstants.BINARY_PLIST_MAGIC.length( ) );
if ( !BinaryPropertyListConstants.BINARY_PLIST_MAGIC.equals( magic ) ) {
throw new IOException( "Not a valid binary PLIST" );
}
majorVersion = reader.readNextByte( );
minorVersion = reader.readNextByte( );
if ( majorVersion != BinaryPropertyListConstants.MAJOR_VERSION_0 ) {
throw new IOException( "Unsupported binary PLIST version: " + majorVersion + "." + minorVersion );
}
trailer = new BinaryPropertyListTrailer( reader );
}
public BinaryPropertyListTrailer getTrailer() {
return trailer;
}
@Override
public DataType toDataType() throws DuplicateNameException, IOException {
Structure structure = new StructureDataType( "bplist", 0 );
structure.add( STRING, BinaryPropertyListConstants.BINARY_PLIST_MAGIC.length( ), "magic", null );
structure.add( BYTE, "majorVersion", null );
structure.add( BYTE, "minorVersion", null );
return structure;
}
}

View file

@ -0,0 +1,113 @@
/* ###
* 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.bplist;
import ghidra.app.util.bin.BinaryReader;
import ghidra.app.util.bin.StructConverter;
import ghidra.program.model.data.*;
import ghidra.util.exception.DuplicateNameException;
import java.io.IOException;
public class BinaryPropertyListTrailer implements StructConverter {
private long trailerIndex;
private int offsetSize;
private int objectRefSize;
private int objectCount;
private int topObject;
private int offsetTableOffset;
private int [] offsetTable;
public BinaryPropertyListTrailer( BinaryReader reader ) throws IOException {
trailerIndex = reader.length( ) - BinaryPropertyListConstants.TRAILER_SIZE;
offsetSize = reader.readByte( trailerIndex + 6 ) & 0xff;
objectRefSize = reader.readByte( trailerIndex + 7 ) & 0xff;
objectCount = reader.readInt( trailerIndex + 12 ) & 0xffffffff;
topObject = reader.readInt( trailerIndex + 20 ) & 0xffffffff;
offsetTableOffset = reader.readInt( trailerIndex + 28 ) & 0xffffffff;
// if ( offsetSize != 4 ) {
// throw new IOException( "Unsupported binary PLIST offset size: " +
// offsetSize );
// }
offsetTable = new int [ objectCount ];
for ( int i = 0 ; i < objectCount ; ++i ) {
if ( offsetSize == 4 ) {
offsetTable[ i ] = reader.readInt( offsetTableOffset + i * offsetSize );
}
else if ( offsetSize == 2 ) {
offsetTable[ i ] = reader.readShort( offsetTableOffset + i * offsetSize ) & 0xffff;
}
else if ( offsetSize == 1 ) {
offsetTable[ i ] = reader.readByte( offsetTableOffset + i * offsetSize ) & 0xff;
}
else {
throw new RuntimeException( "Invalid offset size in binary PList" );
}
}
}
public int getOffsetSize() {
return offsetSize;
}
public int getObjectRefSize() {
return objectRefSize;
}
public int getObjectCount() {
return objectCount;
}
public int getTopObject() {
return topObject;
}
public int getOffsetTableOffset() {
return offsetTableOffset;
}
public int [] getOffsetTable() {
return offsetTable;
}
@Override
public DataType toDataType() throws DuplicateNameException, IOException {
Structure structure = new StructureDataType( "bplist_trailer", 0 );
structure.add( BYTE, "unk0", null );
structure.add( BYTE, "unk1", null );
structure.add( BYTE, "unk2", null );
structure.add( BYTE, "unk3", null );
structure.add( BYTE, "unk4", null );
structure.add( BYTE, "unk5", null );
structure.add( BYTE, "offsetSize", null );
structure.add( BYTE, "objectRefSize", null );
structure.add( QWORD, "objectCount", null );
structure.add( QWORD, "topObject", null );
structure.add( QWORD, "offsetTableOffset", null );
return structure;
}
public long getTrailerIndex() {
return trailerIndex;
}
}

View file

@ -0,0 +1,51 @@
/* ###
* 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.bplist;
import ghidra.app.util.bin.ByteProvider;
import ghidra.program.model.address.Address;
import ghidra.program.model.mem.Memory;
import java.io.IOException;
public final class BinaryPropertyListUtil {
public static boolean isBinaryPropertyList( ByteProvider provider ) throws IOException {
byte [] bytes = provider.readBytes( 0, BinaryPropertyListConstants.BINARY_PLIST_MAGIC.length( ) );
String magic = new String( bytes );
return BinaryPropertyListConstants.BINARY_PLIST_MAGIC.equals( magic );
}
public static boolean isBinaryPropertyList( Memory memory, Address address ) {
byte [] bytes = new byte [ BinaryPropertyListConstants.BINARY_PLIST_MAGIC.length( ) ];
try {
memory.getBytes( address, bytes );
}
catch ( Exception e ) {
// ignore
}
String magic = new String( bytes );
return BinaryPropertyListConstants.BINARY_PLIST_MAGIC.equals( magic );
}
public static String generateName( int index ) {
return generateName( index & 0xffffffffL );
}
public static String generateName( long index ) {
return "BPLIST_Index_" + Long.toHexString( index );
}
}

View file

@ -0,0 +1,103 @@
/* ###
* 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.bplist;
import ghidra.app.util.bin.ByteProvider;
import ghidra.program.model.address.Address;
import ghidra.program.model.mem.Memory;
import ghidra.program.model.mem.MemoryBlock;
import java.io.*;
class ImmutableMemoryRangeByteProvider implements ByteProvider {
private Memory memory;
private Address start;
private Address end;
ImmutableMemoryRangeByteProvider(Memory memory, Address start, Address end) {
this.memory = memory;
this.start = start;
this.end = end;
}
@Override
public File getFile() {
throw new UnsupportedOperationException();
}
@Override
public String getName() {
MemoryBlock block = memory.getBlock(start);
if (block != null) {
return block.getName();
}
return memory.getProgram().getName() + "_" + start.toString();
}
@Override
public String getAbsolutePath() {
throw new UnsupportedOperationException();
}
@Override
public long length() throws IOException {
return end.subtract(start) + 1;
}
@Override
public boolean isValidIndex(long index) {
Address indexAddress = start.add(index);
return start.compareTo(indexAddress) >= 0 && end.compareTo(indexAddress) <= 0;
}
@Override
public void close() throws IOException {
}
@Override
public byte readByte(long index) throws IOException {
Address indexAddress = start.add(index);
try {
return memory.getByte(indexAddress);
}
catch (Exception e) {
throw new IOException(e.getMessage());
}
}
@Override
public byte[] readBytes(long index, long length) throws IOException {
try {
byte[] bytes = new byte[(int) length];
Address indexAddress = start.add(index);
int nRead = memory.getBytes(indexAddress, bytes);
if (nRead != length) {
throw new IOException("Unable to read " + length + " bytes at index " + index);
}
return bytes;
}
catch (Exception e) {
throw new IOException(e.getMessage());
}
}
@Override
public InputStream getInputStream(long index) throws IOException {
throw new UnsupportedOperationException();
}
}

View file

@ -0,0 +1,103 @@
/* ###
* 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.bplist;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import ghidra.program.model.data.*;
import ghidra.program.model.listing.Data;
import ghidra.program.model.listing.Program;
import ghidra.program.model.symbol.*;
import ghidra.util.Msg;
import ghidra.util.exception.CancelledException;
import ghidra.util.exception.DuplicateNameException;
import ghidra.util.task.TaskMonitor;
public class NSArray extends NSObject {
private List<Integer> values = new ArrayList<Integer>();
private int objectRefSize;
public NSArray(int objectRefSize) {
this.objectRefSize = objectRefSize;
}
@Override
public String getType() {
return "NSArray";
}
@Override
public void markup(Data objectData, Program program, TaskMonitor monitor)
throws CancelledException {
ReferenceManager referenceManager = program.getReferenceManager();
for (int i = 0; i < objectData.getNumComponents(); ++i) {
monitor.checkCanceled();
Data component = objectData.getComponent(i);
if (component.getFieldName().startsWith("value")) {
long value = getValue(component);
String name = BinaryPropertyListUtil.generateName(value);
Symbol symbol = SymbolUtilities.getLabelOrFunctionSymbol(program, name,
err -> Msg.error(this, err));
if (symbol != null) {
referenceManager.addMemoryReference(component.getMinAddress(),
symbol.getAddress(), RefType.DATA, SourceType.ANALYSIS, 0);
}
}
}
}
@Override
public DataType toDataType() throws DuplicateNameException, IOException {
Structure structure = new StructureDataType("NSArray_" + values.size(), 0);
addHeader(structure, values.size());
for (int i = 0; i < values.size(); ++i) {
if (objectRefSize == 1) {
structure.add(BYTE, "value" + i, null);
}
else if (objectRefSize == 2) {
structure.add(WORD, "value" + i, null);
}
else if (objectRefSize == 4) {
structure.add(DWORD, "value" + i, null);
}
else if (objectRefSize == 8) {
structure.add(QWORD, "value" + i, null);
}
else {
throw new RuntimeException();
}
}
return structure;
}
public void add(int value) {
values.add(value);
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("NSArray {");
for (int i = 0; i < values.size(); ++i) {
builder.append("{0x" + Integer.toHexString(values.get(i)) + "}");
}
builder.append("}");
return builder.toString();
}
}

View file

@ -0,0 +1,56 @@
/* ###
* 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.bplist;
import ghidra.program.model.data.*;
import ghidra.util.StringUtilities;
import ghidra.util.exception.DuplicateNameException;
import java.io.IOException;
public class NSData extends NSObject {
private byte [] bytes;
public NSData( byte [] bytes ) {
this.bytes = bytes;
}
@Override
public String getType() {
return "NSData";
}
@Override
public DataType toDataType() throws DuplicateNameException, IOException {
Structure structure = new StructureDataType( "NSData_" + bytes.length, 0 );
addHeader( structure, bytes.length );
if ( bytes.length > 0 ) {
structure.add( new ArrayDataType( BYTE, bytes.length, BYTE.getLength( ) ), "data", null );
}
return structure;
}
@Override
public String toString() {
return StringUtilities.toQuotedString( bytes );
}
public byte [] getData() {
return bytes;
}
}

View file

@ -0,0 +1,54 @@
/* ###
* 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.bplist;
import ghidra.program.model.data.*;
import ghidra.util.exception.DuplicateNameException;
import java.io.IOException;
import java.util.Date;
public class NSDate extends NSObject {
public final static long EPOCH = 978307200000L;//Sun Dec 31 19:00:00 EST 2000
private double value;
public NSDate( double value ) {
this.value = value;
}
@Override
public String getType() {
return "NSDate";
}
public Date getDate() {
return new Date( EPOCH + (long ) ( 1000 * value ) );
}
@Override
public DataType toDataType() throws DuplicateNameException, IOException {
Structure structure = new StructureDataType( "NSDate", 0 );
structure.add( BYTE, "objectDescriptor", null );
structure.add( new DoubleDataType( ), "date", null );
return structure;
}
@Override
public String toString() {
return getDate( ).toString( );
}
}

View file

@ -0,0 +1,112 @@
/* ###
* 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.bplist;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import ghidra.program.model.data.*;
import ghidra.program.model.listing.Data;
import ghidra.program.model.listing.Program;
import ghidra.program.model.symbol.*;
import ghidra.util.Msg;
import ghidra.util.exception.CancelledException;
import ghidra.util.exception.DuplicateNameException;
import ghidra.util.task.TaskMonitor;
public class NSDictionary extends NSObject {
private List<Integer> keys = new ArrayList<Integer>();
private List<Integer> values = new ArrayList<Integer>();
private int objectRefSize;
public NSDictionary(int objectRefSize) {
this.objectRefSize = objectRefSize;
}
@Override
public String getType() {
return "NSDictionary";
}
@Override
public DataType toDataType() throws DuplicateNameException, IOException {
Structure structure = new StructureDataType("NSDictionary_" + keys.size(), 0);
addHeader(structure, keys.size());
for (int i = 0; i < keys.size(); ++i) {
if (objectRefSize == 1) {
structure.add(BYTE, "key" + i, null);
structure.add(BYTE, "value" + i, null);
}
else if (objectRefSize == 2) {
structure.add(WORD, "key" + i, null);
structure.add(WORD, "value" + i, null);
}
else if (objectRefSize == 4) {
structure.add(DWORD, "key" + i, null);
structure.add(DWORD, "value" + i, null);
}
else if (objectRefSize == 8) {
structure.add(QWORD, "key" + i, null);
structure.add(QWORD, "value" + i, null);
}
else {
throw new RuntimeException();
}
}
return structure;
}
public void put(int key, int value) {
keys.add(key);
values.add(value);
}
@Override
public void markup(Data objectData, Program program, TaskMonitor monitor)
throws CancelledException {
ReferenceManager referenceManager = program.getReferenceManager();
for (int i = 0; i < objectData.getNumComponents(); ++i) {
monitor.checkCanceled();
Data component = objectData.getComponent(i);
if (component.getFieldName().startsWith("key") ||
component.getFieldName().startsWith("value")) {
long value = getValue(component);
String name = BinaryPropertyListUtil.generateName(value);
Symbol symbol = SymbolUtilities.getLabelOrFunctionSymbol(program, name,
err -> Msg.error(this, err));
if (symbol != null) {
referenceManager.addMemoryReference(component.getMinAddress(),
symbol.getAddress(), RefType.DATA, SourceType.ANALYSIS, 0);
}
}
}
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("NSDictionary {");
for (int i = 0; i < keys.size(); ++i) {
builder.append("{0x" + Integer.toHexString(keys.get(i)) + ",0x" +
Integer.toHexString(values.get(i)) + "}");
}
builder.append("}");
return builder.toString();
}
}

View file

@ -0,0 +1,130 @@
/* ###
* 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.bplist;
import ghidra.program.model.data.*;
import ghidra.util.exception.DuplicateNameException;
import java.io.IOException;
public class NSNumber extends NSObject {
private NSNumberTypes type;
private double value;
public NSNumber( boolean value ) {
this.type = NSNumberTypes.BOOLEAN;
this.value = value ? 1 : 0;
}
public NSNumber( byte value ) {
this.type = NSNumberTypes.BYTE;
this.value = value;
}
public NSNumber( short value ) {
this.type = NSNumberTypes.SHORT;
this.value = value;
}
public NSNumber( int value ) {
this.type = NSNumberTypes.INTEGER;
this.value = value;
}
public NSNumber( long value ) {
this.type = NSNumberTypes.LONG;
this.value = value;
}
public NSNumber( double value ) {
this.type = NSNumberTypes.REAL;
this.value = value;
}
@Override
public String getType() {
return "NSNumber";
}
public NSNumberTypes getNumberType() {
return type;
}
public double doubleValue() {
return value;
}
public float floatValue() {
return (float ) value;
}
public int intValue() {
return (int ) value;
}
public boolean booleanValue() {
return value != 0;
}
public long longValue() {
return (long ) value;
}
@Override
public DataType toDataType() throws DuplicateNameException, IOException {
Structure structure = new StructureDataType( "NSNumber_" + type.name( ), 0 );
structure.add( BYTE, "objectDescriptor", null );
if ( type == NSNumberTypes.BYTE ) {
structure.add( BYTE, "value", null );
}
else if ( type == NSNumberTypes.SHORT ) {
structure.add( WORD, "value", null );
}
else if ( type == NSNumberTypes.INTEGER ) {
structure.add( DWORD, "value", null );
}
else if ( type == NSNumberTypes.LONG ) {
structure.add( QWORD, "value", null );
}
else if ( type == NSNumberTypes.REAL ) {
structure.add( new DoubleDataType( ), "value", null );
}
else if ( type == NSNumberTypes.BOOLEAN ) {
//don't add anything
}
return structure;
}
@Override
public String toString() {
switch ( type ) {
case BOOLEAN: {
return "" + booleanValue( );
}
case REAL: {
return "" + doubleValue( );
}
case BYTE:
case SHORT:
case INTEGER:
case LONG: {
return "" + longValue( );
}
}
throw new RuntimeException( );
}
}

View file

@ -0,0 +1,25 @@
/* ###
* 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.bplist;
public enum NSNumberTypes {
BYTE,
SHORT,
INTEGER,
LONG,
REAL,
BOOLEAN;
}

View file

@ -0,0 +1,91 @@
/* ###
* 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.bplist;
import java.math.BigInteger;
import ghidra.app.util.bin.StructConverter;
import ghidra.program.model.data.Structure;
import ghidra.program.model.listing.Data;
import ghidra.program.model.listing.Program;
import ghidra.program.model.mem.MemoryAccessException;
import ghidra.util.BigEndianDataConverter;
import ghidra.util.DataConverter;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor;
public abstract class NSObject implements StructConverter {
/**
* All data is stored BIG ENDIAN in a binary plist.
*/
protected DataConverter converter = new BigEndianDataConverter( );
public abstract String getType();
public abstract String toString();
protected void addHeader( Structure structure, int size ) {
if ( size < 0xf ) {
structure.add( BYTE, "objectDescriptor", null );
}
else if ( size < 0xff ) {
structure.add( BYTE, "objectDescriptor", null );
structure.add( BYTE, "indicator", null );
structure.add( BYTE, "length", null );
}
else if ( size < 0xffff ) {
structure.add( BYTE, "objectDescriptor", null );
structure.add( BYTE, "indicator", null );
structure.add( WORD, "length", null );
}
else {
throw new RuntimeException( "unexpected size in " + getClass( ).getName( ) );
}
}
public void markup(Data objectData, Program program, TaskMonitor monitor)
throws CancelledException {
}
protected long getValue( Data component ) {
try {
byte [] bytes = component.getBytes( );
// if ( bytes.length == 1 ) {
// return bytes[ 0 ] & 0xffL;
// }
// else if ( bytes.length == 2 ) {
// return converter.getShort( bytes ) & 0xffffL;
// }
// else if ( bytes.length == 4 ) {
// return converter.getInt( bytes ) & 0xffffffffL;
// }
// else if ( bytes.length == 8 ) {
// return converter.getLong( bytes );
// }
// else {
//
// }
BigInteger bi = new BigInteger( bytes );
return bi.longValue( );
}
catch ( MemoryAccessException e ) {
}
return -1;
}
}

View file

@ -0,0 +1,237 @@
/* ###
* 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.bplist;
import ghidra.app.util.bin.BinaryReader;
import java.io.IOException;
public final class NSObjectParser {
public static NSObject parseObject( BinaryReader reader, int objectOffset, BinaryPropertyListTrailer trailer ) throws IOException {
reader.setPointerIndex( objectOffset );
byte objectDescriptor = reader.readNextByte( );
int objectType = ( objectDescriptor & 0xf0 ) >> 4;
int objectInfo = ( objectDescriptor & 0x0f );
switch ( objectType ) {
case 0x0: { // simple
switch ( objectInfo ) {
case 0x0: { // NULL object
return null;
}
case 0x8: { // false
return new NSNumber( false );
}
case 0x9: { // TRUE
return new NSNumber( true );
}
case 0xc: { // URL w/o base URL TODO
return null;
}
case 0xd: { // URL w/ base URL TODO
return null;
}
case 0xe: { // 16 byte UUID TODO
return null;
}
case 0xf: { // filler byte
return null;
}
default: {
throw new IOException( "WARNING: The binary PLIST contains unknown SIMPLE object type: " + objectInfo );
}
}
}
case 0x1: { // integer
int length = (int ) Math.pow( 2, objectInfo );
switch ( length ) {
case 1: {
byte value = reader.readByte( objectOffset + 1 );
return new NSNumber( value );
}
case 2: {
short value = reader.readShort( objectOffset + 1 );
return new NSNumber( value );
}
case 4: {
int value = reader.readInt( objectOffset + 1 );
return new NSNumber( value );
}
case 8: {
long value = reader.readLong( objectOffset + 1 );
return new NSNumber( value );
}
}
throw new IOException( "WARNING: Invalid integer length specified in the binary PList." );
}
case 0x2: { // real
int length = (int ) Math.pow( 2, objectInfo );
if ( length == 4 ) {
int intValue = reader.readInt( objectOffset + 1 );
float floatValue = Float.intBitsToFloat( intValue );
return new NSNumber( floatValue );
}
else if ( length == 8 ) {
long longValue = reader.readLong( objectOffset + 1 );
double doubleValue = Double.longBitsToDouble( longValue );
return new NSNumber( doubleValue );
}
else {
throw new IOException( "WARNING: Invalid real number length specified in the binary PList." );
}
}
case 0x3: { // date
if ( objectInfo != 3 ) {
throw new IOException( "WARNING: Binary PLIST contains unknown date type:" + objectInfo );
}
long longValue = reader.readLong( objectOffset + 1 );
double doubleValue = Double.longBitsToDouble( longValue );
return new NSDate( doubleValue );
}
case 0x4: { // data
int length = parseLength( reader, objectInfo );
return new NSData( reader.readNextByteArray( length ) );
}
case 0x5: { // ascii string
int length = parseLength( reader, objectInfo );
return new NSString( reader.readNextAsciiString( length ), NSStringTypes.TYPE_ASCII );
}
case 0x6: { // utf-16-be string
int length = parseLength( reader, objectInfo );
return new NSString( reader.readNextUnicodeString( length ), NSStringTypes.TYPE_UTF16BE );
}
case 0x8: { // UID
int length = reader.readNextByte( ) & 0xff;
return new UID( reader.readNextByteArray( length ) );
}
case 0xa: { // array
int length = parseLength( reader, objectInfo );
NSArray array = new NSArray( trailer.getObjectRefSize( ) );
for ( int i = 0 ; i < length ; ++i ) {
if ( trailer.getObjectRefSize( ) == 1 ) {
int value = reader.readNextByte( ) & 0xff;
array.add( value );
}
else if ( trailer.getObjectRefSize( ) == 2 ) {
int value = reader.readNextShort( ) & 0xffff;
array.add( value );
}
else if ( trailer.getObjectRefSize( ) == 4 ) {
int value = reader.readNextInt( );
array.add( value );
}
else {
throw new RuntimeException( "Invalid offset size in binary PList" );
}
}
return array;
}
case 0xb: { // ordered set
int length = parseLength( reader, objectInfo );
NSSet set = new NSSet( true, trailer.getObjectRefSize( ) );
for ( int i = 0 ; i < length ; i++ ) {
if ( trailer.getObjectRefSize( ) == 1 ) {
int value = reader.readNextByte( ) & 0xff;
set.add( value );
}
else if ( trailer.getObjectRefSize( ) == 2 ) {
int value = reader.readNextShort( ) & 0xffff;
set.add( value );
}
else if ( trailer.getObjectRefSize( ) == 4 ) {
int value = reader.readNextInt( );
set.add( value );
}
else {
throw new RuntimeException( "Invalid offset size in binary PList" );
}
}
return set;
}
case 0xc: { // set
int length = parseLength( reader, objectInfo );
NSSet set = new NSSet( false, trailer.getObjectRefSize( ) );
for ( int i = 0 ; i < length ; i++ ) {
if ( trailer.getObjectRefSize( ) == 1 ) {
int value = reader.readNextByte( ) & 0xff;
set.add( value );
}
else if ( trailer.getObjectRefSize( ) == 2 ) {
int value = reader.readNextShort( ) & 0xffff;
set.add( value );
}
else if ( trailer.getObjectRefSize( ) == 4 ) {
int value = reader.readNextInt( );
set.add( value );
}
else {
throw new RuntimeException( "Invalid offset size in binary PList" );
}
}
return set;
}
case 0xd: { // dictionary
int length = parseLength( reader, objectInfo );
NSDictionary dictionary = new NSDictionary( trailer.getObjectRefSize( ) );
for ( int i = 0 ; i < length ; ++i ) {
if ( trailer.getObjectRefSize( ) == 1 ) {
int key = reader.readNextByte( ) & 0xff;
int value = reader.readNextByte( ) & 0xff;
dictionary.put( key, value );
}
else if ( trailer.getObjectRefSize( ) == 2 ) {
int key = reader.readNextShort( ) & 0xffff;
int value = reader.readNextShort( ) & 0xffff;
dictionary.put( key, value );
}
else if ( trailer.getObjectRefSize( ) == 4 ) {
int key = reader.readNextInt( );
int value = reader.readNextInt( );
dictionary.put( key, value );
}
else {
throw new RuntimeException( "Invalid offset size in binary PList" );
}
}
return dictionary;
}
default: {
throw new IOException( "WARNING: The binary PLIST contains unknown object type: " + objectType );
}
}
}
private static int parseLength( BinaryReader reader, int objectInfo ) throws IOException {
int length = objectInfo;
if ( objectInfo == 0xf ) {// longer than 0xf bytes...
int offset = reader.readNextByte( ) & 0xff;
if ( offset == 0x10 ) {
length = reader.readNextByte( ) & 0xff;
}
else if ( offset == 0x11 ) {
length = reader.readNextShort( ) & 0xffff;
}
else {
throw new RuntimeException( );
}
}
return length;
}
}

View file

@ -0,0 +1,124 @@
/* ###
* 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.bplist;
import java.io.IOException;
import java.util.*;
import ghidra.program.model.data.*;
import ghidra.program.model.listing.Data;
import ghidra.program.model.listing.Program;
import ghidra.program.model.symbol.*;
import ghidra.util.Msg;
import ghidra.util.exception.CancelledException;
import ghidra.util.exception.DuplicateNameException;
import ghidra.util.task.TaskMonitor;
public class NSSet extends NSObject {
private boolean ordered;
private int objectRefSize;
private Set<Integer> set;
public NSSet(boolean ordered, int objectRefSize) {
this.ordered = ordered;
this.objectRefSize = objectRefSize;
if (ordered) {
set = new TreeSet<Integer>();
}
else {
set = new LinkedHashSet<Integer>();
}
}
@Override
public String getType() {
return "NSSet";
}
public boolean isOrdered() {
return ordered;
}
public Set<Integer> getSet() {
return set;
}
public void add(int object) {
set.add(object);
}
@Override
public void markup(Data objectData, Program program, TaskMonitor monitor)
throws CancelledException {
ReferenceManager referenceManager = program.getReferenceManager();
for (int i = 0; i < objectData.getNumComponents(); ++i) {
monitor.checkCanceled();
Data component = objectData.getComponent(i);
if (component.getFieldName().startsWith("value")) {
long value = getValue(component);
String name = BinaryPropertyListUtil.generateName(value);
Symbol symbol = SymbolUtilities.getLabelOrFunctionSymbol(program, name,
err -> Msg.error(this, err));
if (symbol != null) {
referenceManager.addMemoryReference(component.getMinAddress(),
symbol.getAddress(), RefType.DATA, SourceType.ANALYSIS, 0);
}
}
}
}
@Override
public DataType toDataType() throws DuplicateNameException, IOException {
Structure structure = new StructureDataType("NSSet_" + set.size(), 0);
addHeader(structure, set.size());
for (int i = 0; i < set.size(); ++i) {
if (objectRefSize == 1) {
structure.add(BYTE, "value_" + i, null);
}
else if (objectRefSize == 2) {
structure.add(WORD, "value_" + i, null);
}
else if (objectRefSize == 4) {
structure.add(DWORD, "value_" + i, null);
}
else if (objectRefSize == 8) {
structure.add(QWORD, "value_" + i, null);
}
else {
throw new RuntimeException();
}
}
return structure;
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("{");
for (Integer object : set) {
builder.append(" ");
builder.append("0x" + Integer.toHexString(object));
builder.append(",");
}
builder.deleteCharAt(builder.length() - 1);// pop last comma
builder.append(" ");
builder.append("}");
return builder.toString();
}
}

Some files were not shown because too many files have changed in this diff Show more