ARSC/AXML Parser Rework (#3131)

* Supports ASRC with null renamed package.
* Rework ASRC Chunk parser to a loop to break assumption of order of chunks
* Break out unknown skips for alignment to ResourceTypes.h
* Add verbose information for file skips
* Add test for protected apk sample
* Rework chunk parsing for StringBlock
* Refactor AXML Parser to support proper header reading
* Fix parsing if attribute size reported does not align to actual size
This commit is contained in:
Connor Tumbleson 2023-07-12 05:33:28 -04:00 committed by GitHub
parent 86340503ac
commit bdbe1384bf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 366 additions and 313 deletions

View File

@ -190,19 +190,20 @@ public class ResourcesDecoder {
// compare resources.arsc package name to the one present in AndroidManifest
ResPackage resPackage = resTable.getCurrentResPackage();
String pkgOriginal = resPackage.getName();
String packageRenamed = resTable.getPackageRenamed();
String pkgRenamed = resTable.getPackageRenamed();
resTable.setPackageId(resPackage.getId());
resTable.setPackageOriginal(pkgOriginal);
// 1) Check if pkgOriginal is null (empty resources.arsc)
// 2) Check if pkgOriginal === mPackageRenamed
// 3) Check if pkgOriginal is ignored via IGNORED_PACKAGES
if (pkgOriginal == null || pkgOriginal.equalsIgnoreCase(packageRenamed)
// 2) Check if pkgRenamed is null
// 3) Check if pkgOriginal === mPackageRenamed
// 4) Check if pkgOriginal is ignored via IGNORED_PACKAGES
if (pkgOriginal == null || pkgRenamed == null || pkgOriginal.equalsIgnoreCase(pkgRenamed)
|| (Arrays.asList(IGNORED_PACKAGES).contains(pkgOriginal))) {
LOGGER.info("Regular manifest package...");
} else {
LOGGER.info("Renamed manifest package found! Replacing " + packageRenamed + " with " + pkgOriginal);
LOGGER.info("Renamed manifest package found! Replacing " + pkgRenamed + " with " + pkgOriginal);
ResXmlPatcher.renameManifestPackage(new File(filePath), pkgOriginal);
}
}

View File

@ -364,7 +364,7 @@ public class ResTable {
}
// only put rename-manifest-package into apktool.yml, if the change will be required
if (!renamed.equalsIgnoreCase(original)) {
if (renamed != null && !renamed.equalsIgnoreCase(original)) {
mApkInfo.packageInfo.renameManifestPackage = renamed;
}
mApkInfo.packageInfo.forcedPackageId = String.valueOf(id);

View File

@ -43,16 +43,22 @@ public class ARSCHeader {
try {
type = in.readShort();
} catch (EOFException ex) {
return new ARSCHeader(TYPE_NONE, 0, 0, countIn.getCount());
return new ARSCHeader(RES_NONE_TYPE, 0, 0, countIn.getCount());
}
return new ARSCHeader(type, in.readShort(), in.readInt(), start);
}
public final static short TYPE_NONE = -1;
public final static short TYPE_STRING_POOL = 0x0001;
public final static short TYPE_TABLE = 0x0002;
public final static short TYPE_XML = 0x0003;
public void skipChunk(ExtDataInput in) throws IOException {
in.skipBytes(chunkSize - headerSize);
}
public final static short RES_NONE_TYPE = -1;
public final static short RES_NULL_TYPE = 0x0000;
public final static short RES_STRING_POOL_TYPE = 0x0001;
public final static short RES_TABLE_TYPE = 0x0002;
public final static short RES_XML_TYPE = 0x0003;
// RES_TABLE_TYPE Chunks
public final static short XML_TYPE_PACKAGE = 0x0200;
public final static short XML_TYPE_TYPE = 0x0201;
public final static short XML_TYPE_SPEC_TYPE = 0x0202;
@ -60,4 +66,14 @@ public class ARSCHeader {
public final static short XML_TYPE_OVERLAY = 0x0204;
public final static short XML_TYPE_OVERLAY_POLICY = 0x0205;
public final static short XML_TYPE_STAGED_ALIAS = 0x0206;
// RES_XML_TYPE Chunks
public final static short RES_XML_FIRST_CHUNK_TYPE = 0x0100;
public final static short RES_XML_START_NAMESPACE_TYPE = 0x0100;
public final static short RES_XML_END_NAMESPACE_TYPE = 0x0101;
public final static short RES_XML_START_ELEMENT_TYPE = 0x0102;
public final static short RES_XML_END_ELEMENT_TYPE = 0x0103;
public final static short RES_XML_CDATA_TYPE = 0x0104;
public final static short RES_XML_LAST_CHUNK_TYPE = 0x017f;
public final static short RES_XML_RESOURCE_MAP_TYPE = 0x0180;
}

View File

@ -33,10 +33,7 @@ import java.io.DataInput;
import java.io.IOException;
import java.io.InputStream;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.*;
import java.util.logging.Logger;
public class ARSCDecoder {
@ -50,7 +47,7 @@ public class ARSCDecoder {
throws AndrolibException {
try {
ARSCDecoder decoder = new ARSCDecoder(arscStream, resTable, findFlagsOffsets, keepBroken);
ResPackage[] pkgs = decoder.readTableHeader();
ResPackage[] pkgs = decoder.readResourceTable();
return new ARSCData(pkgs, decoder.mFlagsOffsets == null
? null
: decoder.mFlagsOffsets.toArray(new FlagsOffset[0]));
@ -74,19 +71,85 @@ public class ARSCDecoder {
mKeepBroken = keepBroken;
}
private ResPackage[] readTableHeader() throws IOException, AndrolibException {
nextChunkCheckType(ARSCHeader.TYPE_TABLE);
int packageCount = mIn.readInt();
private ResPackage[] readResourceTable() throws IOException, AndrolibException {
Set<ResPackage> pkgs = new LinkedHashSet<>();
ResTypeSpec typeSpec;
mTableStrings = StringBlock.read(mIn);
ResPackage[] packages = new ResPackage[packageCount];
chunkLoop:
for (;;) {
nextChunk();
nextChunk();
for (int i = 0; i < packageCount; i++) {
mTypeIdOffset = 0;
packages[i] = readTablePackage();
switch (mHeader.type) {
case ARSCHeader.RES_NULL_TYPE:
readUnknownChunk();
break;
case ARSCHeader.RES_STRING_POOL_TYPE:
readStringPoolChunk();
break;
case ARSCHeader.RES_TABLE_TYPE:
readTableChunk();
break;
// Chunk types in RES_TABLE_TYPE
case ARSCHeader.XML_TYPE_PACKAGE:
mTypeIdOffset = 0;
pkgs.add(readTablePackage());
break;
case ARSCHeader.XML_TYPE_TYPE:
readTableType();
break;
case ARSCHeader.XML_TYPE_SPEC_TYPE:
typeSpec = readTableSpecType();
addTypeSpec(typeSpec);
break;
case ARSCHeader.XML_TYPE_LIBRARY:
readLibraryType();
break;
case ARSCHeader.XML_TYPE_OVERLAY:
readOverlaySpec();
break;
case ARSCHeader.XML_TYPE_OVERLAY_POLICY:
readOverlayPolicySpec();
break;
case ARSCHeader.XML_TYPE_STAGED_ALIAS:
readStagedAliasSpec();
break;
default:
if (mHeader.type != ARSCHeader.RES_NONE_TYPE) {
LOGGER.severe(String.format("Unknown chunk type: %04x", mHeader.type));
}
break chunkLoop;
}
}
return packages;
if (mPkg.getResSpecCount() > 0) {
addMissingResSpecs();
}
// We've detected sparse resources, lets record this so we can rebuild in that same format (sparse/not)
// with aapt2. aapt1 will ignore this.
if (! mResTable.getSparseResources()) {
mResTable.setSparseResources(true);
}
return pkgs.toArray(new ResPackage[0]);
}
private void readStringPoolChunk() throws IOException, AndrolibException {
checkChunkType(ARSCHeader.RES_STRING_POOL_TYPE);
mTableStrings = StringBlock.readWithoutChunk(mIn, mHeader.chunkSize);
}
private void readTableChunk() throws IOException, AndrolibException {
checkChunkType(ARSCHeader.RES_TABLE_TYPE);
mIn.skipInt(); // packageCount
}
private void readUnknownChunk() throws IOException, AndrolibException {
checkChunkType(ARSCHeader.RES_NULL_TYPE);
LOGGER.warning("Skipping unknown chunk data of size " + mHeader.chunkSize);
mHeader.skipChunk(mIn);
}
private ResPackage readTablePackage() throws IOException, AndrolibException {
@ -121,38 +184,12 @@ public class ARSCDecoder {
LOGGER.warning("Please report this application to Apktool for a fix: https://github.com/iBotPeaches/Apktool/issues/1728");
}
mTypeNames = StringBlock.read(mIn);
mSpecNames = StringBlock.read(mIn);
mTypeNames = StringBlock.readWithChunk(mIn);
mSpecNames = StringBlock.readWithChunk(mIn);
mResId = id << 24;
mPkg = new ResPackage(mResTable, id, name);
nextChunk();
chunkLoop:
for (;;) {
switch (mHeader.type) {
case ARSCHeader.XML_TYPE_TYPE:
case ARSCHeader.XML_TYPE_SPEC_TYPE:
readTableTypeSpec();
break;
case ARSCHeader.XML_TYPE_LIBRARY:
readLibraryType();
break;
case ARSCHeader.XML_TYPE_OVERLAY:
readOverlaySpec();
break;
case ARSCHeader.XML_TYPE_OVERLAY_POLICY:
readOverlayPolicySpec();
break;
case ARSCHeader.XML_TYPE_STAGED_ALIAS:
readStagedAliasSpec();
break;
default:
break chunkLoop;
}
}
return mPkg;
}
@ -168,8 +205,6 @@ public class ARSCDecoder {
packageName = mIn.readNullEndedString(128, true);
LOGGER.info(String.format("Decoding Shared Library (%s), pkgId: %d", packageName, packageId));
}
nextChunk();
}
private void readStagedAliasSpec() throws IOException {
@ -178,8 +213,6 @@ public class ARSCDecoder {
for (int i = 0; i < count; i++) {
LOGGER.fine(String.format("Skipping staged alias stagedId (%h) finalId: %h", mIn.readInt(), mIn.readInt()));
}
nextChunk();
}
private void readOverlaySpec() throws AndrolibException, IOException {
@ -187,8 +220,6 @@ public class ARSCDecoder {
String name = mIn.readNullEndedString(256, true);
String actor = mIn.readNullEndedString(256, true);
LOGGER.fine(String.format("Overlay name: \"%s\", actor: \"%s\")", name, actor));
nextChunk();
}
private void readOverlayPolicySpec() throws AndrolibException, IOException {
@ -199,48 +230,13 @@ public class ARSCDecoder {
for (int i = 0; i < count; i++) {
LOGGER.fine(String.format("Skipping overlay (%h)", mIn.readInt()));
}
nextChunk();
}
private void readTableTypeSpec() throws AndrolibException, IOException {
mTypeSpec = readSingleTableTypeSpec();
addTypeSpec(mTypeSpec);
int type = nextChunk().type;
ResTypeSpec resTypeSpec;
while (type == ARSCHeader.XML_TYPE_SPEC_TYPE) {
resTypeSpec = readSingleTableTypeSpec();
addTypeSpec(resTypeSpec);
type = nextChunk().type;
// We've detected sparse resources, lets record this so we can rebuild in that same format (sparse/not)
// with aapt2. aapt1 will ignore this.
if (! mResTable.getSparseResources()) {
mResTable.setSparseResources(true);
}
}
while (type == ARSCHeader.XML_TYPE_TYPE) {
readTableType();
// skip "TYPE 8 chunks" and/or padding data at the end of this chunk
if (mCountIn.getCount() < mHeader.endPosition) {
LOGGER.warning("Unknown data detected. Skipping: " + (mHeader.endPosition - mCountIn.getCount()) + " byte(s)");
mCountIn.skip(mHeader.endPosition - mCountIn.getCount());
}
type = nextChunk().type;
addMissingResSpecs();
}
}
private ResTypeSpec readSingleTableTypeSpec() throws AndrolibException, IOException {
private ResTypeSpec readTableSpecType() throws AndrolibException, IOException {
checkChunkType(ARSCHeader.XML_TYPE_SPEC_TYPE);
int id = mIn.readUnsignedByte();
mIn.skipBytes(3);
mIn.skipBytes(1); // reserved0
mIn.skipBytes(2); // reserved1
int entryCount = mIn.readInt();
if (mFlagsOffsets != null) {
@ -250,6 +246,7 @@ public class ARSCDecoder {
mIn.skipBytes(entryCount * 4); // flags
mTypeSpec = new ResTypeSpec(mTypeNames.getString(id - 1), mResTable, mPkg, id, entryCount);
mPkg.addType(mTypeSpec);
return mTypeSpec;
}
@ -311,6 +308,12 @@ public class ARSCDecoder {
readEntry(readEntryData());
}
// skip "TYPE 8 chunks" and/or padding data at the end of this chunk
if (mCountIn.getCount() < mHeader.endPosition) {
long bytesSkipped = mCountIn.skip(mHeader.endPosition - mCountIn.getCount());
LOGGER.warning("Unknown data detected. Skipping: " + bytesSkipped + " byte(s)");
}
return mType;
}
@ -616,11 +619,6 @@ public class ARSCDecoder {
}
}
private void nextChunkCheckType(int expectedType) throws IOException, AndrolibException {
nextChunk();
checkChunkType(expectedType);
}
private final ExtDataInput mIn;
private final ResTable mResTable;
private final CountingInputStream mCountIn;

View File

@ -20,6 +20,7 @@ import android.content.res.XmlResourceParser;
import android.util.TypedValue;
import brut.androlib.exceptions.AndrolibException;
import brut.androlib.res.data.ResID;
import brut.androlib.res.data.arsc.ARSCHeader;
import brut.androlib.res.data.axml.NamespaceStack;
import brut.androlib.res.xml.ResXmlEncoders;
import brut.util.ExtDataInput;
@ -65,31 +66,31 @@ public class AXmlResourceParser implements XmlResourceParser {
// We need to explicitly cast to DataInput as otherwise the constructor is ambiguous.
// We choose DataInput instead of InputStream as ExtDataInput wraps an InputStream in
// a DataInputStream which is big-endian and ignores the little-endian behavior.
m_reader = new ExtDataInput((DataInput) new LittleEndianDataInputStream(stream));
mIn = new ExtDataInput((DataInput) new LittleEndianDataInputStream(stream));
}
}
@Override
public void close() {
if (!m_operational) {
if (!isOperational) {
return;
}
m_operational = false;
m_reader = null;
m_strings = null;
m_resourceIDs = null;
m_namespaces.reset();
isOperational = false;
mIn = null;
mStringBlock = null;
mResourceIds = null;
mNamespaces.reset();
resetEventInfo();
}
@Override
public int next() throws XmlPullParserException, IOException {
if (m_reader == null) {
if (mIn == null) {
throw new XmlPullParserException("Parser is not opened.", this, null);
}
try {
doNext();
return m_event;
return mEvent;
} catch (IOException e) {
close();
throw e;
@ -134,8 +135,7 @@ public class AXmlResourceParser implements XmlResourceParser {
}
@Override
public void require(int type, String namespace, String name)
throws XmlPullParserException, IOException {
public void require(int type, String namespace, String name) throws XmlPullParserException {
if (type != getEventType() || (namespace != null && !namespace.equals(getNamespace()))
|| (name != null && !name.equals(getName()))) {
throw new XmlPullParserException(TYPES[type] + " is expected.", this, null);
@ -144,33 +144,33 @@ public class AXmlResourceParser implements XmlResourceParser {
@Override
public int getDepth() {
return m_namespaces.getDepth() - 1;
return mNamespaces.getDepth() - 1;
}
@Override
public int getEventType(){
return m_event;
return mEvent;
}
@Override
public int getLineNumber() {
return m_lineNumber;
return mLineNumber;
}
@Override
public String getName() {
if (m_name == -1 || (m_event != START_TAG && m_event != END_TAG)) {
if (mNameIndex == -1 || (mEvent != START_TAG && mEvent != END_TAG)) {
return null;
}
return m_strings.getString(m_name);
return mStringBlock.getString(mNameIndex);
}
@Override
public String getText() {
if (m_name == -1 || m_event != TEXT) {
if (mNameIndex == -1 || mEvent != TEXT) {
return null;
}
return m_strings.getString(m_name);
return mStringBlock.getString(mNameIndex);
}
@Override
@ -188,13 +188,13 @@ public class AXmlResourceParser implements XmlResourceParser {
@Override
public String getNamespace() {
return m_strings.getString(m_namespaceUri);
return mStringBlock.getString(mNamespaceIndex);
}
@Override
public String getPrefix() {
int prefix = m_namespaces.findPrefix(m_namespaceUri);
return m_strings.getString(prefix);
int prefix = mNamespaces.findPrefix(mNamespaceIndex);
return mStringBlock.getString(prefix);
}
@Override
@ -204,89 +204,89 @@ public class AXmlResourceParser implements XmlResourceParser {
@Override
public int getNamespaceCount(int depth) {
return m_namespaces.getAccumulatedCount(depth);
return mNamespaces.getAccumulatedCount(depth);
}
@Override
public String getNamespacePrefix(int pos) {
int prefix = m_namespaces.getPrefix(pos);
return m_strings.getString(prefix);
int prefix = mNamespaces.getPrefix(pos);
return mStringBlock.getString(prefix);
}
@Override
public String getNamespaceUri(int pos) {
int uri = m_namespaces.getUri(pos);
return m_strings.getString(uri);
int uri = mNamespaces.getUri(pos);
return mStringBlock.getString(uri);
}
@Override
public String getClassAttribute() {
if (m_classAttribute == -1) {
if (mClassIndex == -1) {
return null;
}
int offset = getAttributeOffset(m_classAttribute);
int value = m_attributes[offset + ATTRIBUTE_IX_VALUE_STRING];
return m_strings.getString(value);
int offset = getAttributeOffset(mClassIndex);
int value = mAttributes[offset + ATTRIBUTE_IX_VALUE_STRING];
return mStringBlock.getString(value);
}
@Override
public String getIdAttribute() {
if (m_idAttribute == -1) {
if (mIdIndex == -1) {
return null;
}
int offset = getAttributeOffset(m_idAttribute);
int value = m_attributes[offset + ATTRIBUTE_IX_VALUE_STRING];
return m_strings.getString(value);
int offset = getAttributeOffset(mIdIndex);
int value = mAttributes[offset + ATTRIBUTE_IX_VALUE_STRING];
return mStringBlock.getString(value);
}
@Override
public int getIdAttributeResourceValue(int defaultValue) {
if (m_idAttribute == -1) {
if (mIdIndex == -1) {
return defaultValue;
}
int offset = getAttributeOffset(m_idAttribute);
int valueType = m_attributes[offset + ATTRIBUTE_IX_VALUE_TYPE];
int offset = getAttributeOffset(mIdIndex);
int valueType = mAttributes[offset + ATTRIBUTE_IX_VALUE_TYPE];
if (valueType != TypedValue.TYPE_REFERENCE) {
return defaultValue;
}
return m_attributes[offset + ATTRIBUTE_IX_VALUE_DATA];
return mAttributes[offset + ATTRIBUTE_IX_VALUE_DATA];
}
@Override
public int getStyleAttribute() {
if (m_styleAttribute == -1) {
if (mStyleIndex == -1) {
return 0;
}
int offset = getAttributeOffset(m_styleAttribute);
return m_attributes[offset + ATTRIBUTE_IX_VALUE_DATA];
int offset = getAttributeOffset(mStyleIndex);
return mAttributes[offset + ATTRIBUTE_IX_VALUE_DATA];
}
@Override
public int getAttributeCount() {
if (m_event != START_TAG) {
if (mEvent != START_TAG) {
return -1;
}
return m_attributes.length / ATTRIBUTE_LENGTH;
return mAttributes.length / ATTRIBUTE_LENGTH;
}
@Override
public String getAttributeNamespace(int index) {
int offset = getAttributeOffset(index);
int namespace = m_attributes[offset + ATTRIBUTE_IX_NAMESPACE_URI];
int namespace = mAttributes[offset + ATTRIBUTE_IX_NAMESPACE_URI];
if (namespace == -1) {
return "";
}
// Minifiers like removing the namespace, so we will default to default namespace
// unless the pkgId of the resource is private. We will grab the non-standard one.
String value = m_strings.getString(namespace);
String value = mStringBlock.getString(namespace);
if (value == null || value.length() == 0) {
ResID resId = new ResID(getAttributeNameResource(index));
if (resId.pkgId == PRIVATE_PKG_ID) {
value = getNonDefaultNamespaceUri(offset);
} else {
value = android_ns;
value = "http://schemas.android.com/apk/res/android";
}
}
@ -294,9 +294,9 @@ public class AXmlResourceParser implements XmlResourceParser {
}
private String getNonDefaultNamespaceUri(int offset) {
String prefix = m_strings.getString(m_namespaces.getPrefix(offset));
String prefix = mStringBlock.getString(mNamespaces.getPrefix(offset));
if (prefix != null) {
return m_strings.getString(m_namespaces.getUri(offset));
return mStringBlock.getString(mNamespaces.getUri(offset));
}
// If we are here. There is some clever obfuscation going on. Our reference points to the namespace are gone.
@ -311,24 +311,24 @@ public class AXmlResourceParser implements XmlResourceParser {
@Override
public String getAttributePrefix(int index) {
int offset = getAttributeOffset(index);
int uri = m_attributes[offset + ATTRIBUTE_IX_NAMESPACE_URI];
int prefix = m_namespaces.findPrefix(uri);
int uri = mAttributes[offset + ATTRIBUTE_IX_NAMESPACE_URI];
int prefix = mNamespaces.findPrefix(uri);
if (prefix == -1) {
return "";
}
return m_strings.getString(prefix);
return mStringBlock.getString(prefix);
}
@Override
public String getAttributeName(int index) {
int offset = getAttributeOffset(index);
int name = m_attributes[offset + ATTRIBUTE_IX_NAME];
int name = mAttributes[offset + ATTRIBUTE_IX_NAME];
if (name == -1) {
return "";
}
String resourceMapValue;
String stringBlockValue = m_strings.getString(name);
String stringBlockValue = mStringBlock.getString(name);
int resourceId = getAttributeNameResource(index);
try {
@ -358,35 +358,35 @@ public class AXmlResourceParser implements XmlResourceParser {
@Override
public int getAttributeNameResource(int index) {
int offset = getAttributeOffset(index);
int name = m_attributes[offset + ATTRIBUTE_IX_NAME];
if (m_resourceIDs == null || name < 0 || name >= m_resourceIDs.length) {
int name = mAttributes[offset + ATTRIBUTE_IX_NAME];
if (mResourceIds == null || name < 0 || name >= mResourceIds.length) {
return 0;
}
return m_resourceIDs[name];
return mResourceIds[name];
}
@Override
public int getAttributeValueType(int index) {
int offset = getAttributeOffset(index);
return m_attributes[offset + ATTRIBUTE_IX_VALUE_TYPE];
return mAttributes[offset + ATTRIBUTE_IX_VALUE_TYPE];
}
@Override
public int getAttributeValueData(int index) {
int offset = getAttributeOffset(index);
return m_attributes[offset + ATTRIBUTE_IX_VALUE_DATA];
return mAttributes[offset + ATTRIBUTE_IX_VALUE_DATA];
}
@Override
public String getAttributeValue(int index) {
int offset = getAttributeOffset(index);
int valueType = m_attributes[offset + ATTRIBUTE_IX_VALUE_TYPE];
int valueData = m_attributes[offset + ATTRIBUTE_IX_VALUE_DATA];
int valueRaw = m_attributes[offset + ATTRIBUTE_IX_VALUE_STRING];
int valueType = mAttributes[offset + ATTRIBUTE_IX_VALUE_TYPE];
int valueData = mAttributes[offset + ATTRIBUTE_IX_VALUE_DATA];
int valueRaw = mAttributes[offset + ATTRIBUTE_IX_VALUE_STRING];
if (mAttrDecoder != null) {
try {
String stringBlockValue = valueRaw == -1 ? null : ResXmlEncoders.escapeXmlChars(m_strings.getString(valueRaw));
String stringBlockValue = valueRaw == -1 ? null : ResXmlEncoders.escapeXmlChars(mStringBlock.getString(valueRaw));
String resourceMapValue = mAttrDecoder.decodeFromResourceId(valueData);
String value = stringBlockValue;
@ -431,9 +431,9 @@ public class AXmlResourceParser implements XmlResourceParser {
@Override
public float getAttributeFloatValue(int index, float defaultValue) {
int offset = getAttributeOffset(index);
int valueType = m_attributes[offset + ATTRIBUTE_IX_VALUE_TYPE];
int valueType = mAttributes[offset + ATTRIBUTE_IX_VALUE_TYPE];
if (valueType == TypedValue.TYPE_FLOAT) {
int valueData = m_attributes[offset + ATTRIBUTE_IX_VALUE_DATA];
int valueData = mAttributes[offset + ATTRIBUTE_IX_VALUE_DATA];
return Float.intBitsToFloat(valueData);
}
return defaultValue;
@ -442,9 +442,9 @@ public class AXmlResourceParser implements XmlResourceParser {
@Override
public int getAttributeIntValue(int index, int defaultValue) {
int offset = getAttributeOffset(index);
int valueType = m_attributes[offset + ATTRIBUTE_IX_VALUE_TYPE];
int valueType = mAttributes[offset + ATTRIBUTE_IX_VALUE_TYPE];
if (valueType >= TypedValue.TYPE_FIRST_INT && valueType <= TypedValue.TYPE_LAST_INT) {
return m_attributes[offset + ATTRIBUTE_IX_VALUE_DATA];
return mAttributes[offset + ATTRIBUTE_IX_VALUE_DATA];
}
return defaultValue;
}
@ -457,9 +457,9 @@ public class AXmlResourceParser implements XmlResourceParser {
@Override
public int getAttributeResourceValue(int index, int defaultValue) {
int offset = getAttributeOffset(index);
int valueType = m_attributes[offset + ATTRIBUTE_IX_VALUE_TYPE];
int valueType = mAttributes[offset + ATTRIBUTE_IX_VALUE_TYPE];
if (valueType == TypedValue.TYPE_REFERENCE) {
return m_attributes[offset + ATTRIBUTE_IX_VALUE_DATA];
return mAttributes[offset + ATTRIBUTE_IX_VALUE_DATA];
}
return defaultValue;
}
@ -520,13 +520,11 @@ public class AXmlResourceParser implements XmlResourceParser {
@Override
public int getAttributeListValue(int index, String[] options, int defaultValue) {
// TODO implement
return 0;
}
@Override
public int getAttributeListValue(String namespace, String attribute, String[] options, int defaultValue) {
// TODO implement
return 0;
}
@ -587,8 +585,7 @@ public class AXmlResourceParser implements XmlResourceParser {
}
@Override
public void setProperty(String name, Object value)
throws XmlPullParserException {
public void setProperty(String name, Object value) throws XmlPullParserException {
throw new XmlPullParserException(E_NOT_SUPPORTED);
}
@ -598,34 +595,33 @@ public class AXmlResourceParser implements XmlResourceParser {
}
@Override
public void setFeature(String name, boolean value)
throws XmlPullParserException {
public void setFeature(String name, boolean value) throws XmlPullParserException {
throw new XmlPullParserException(E_NOT_SUPPORTED);
}
private int getAttributeOffset(int index) {
if (m_event != START_TAG) {
if (mEvent != START_TAG) {
throw new IndexOutOfBoundsException("Current event is not START_TAG.");
}
int offset = index * ATTRIBUTE_LENGTH;
if (offset >= m_attributes.length) {
if (offset >= mAttributes.length) {
throw new IndexOutOfBoundsException("Invalid attribute index (" + index + ").");
}
return offset;
}
private int findAttribute(String namespace, String attribute) {
if (m_strings == null || attribute == null) {
if (mStringBlock == null || attribute == null) {
return -1;
}
int name = m_strings.find(attribute);
int name = mStringBlock.find(attribute);
if (name == -1) {
return -1;
}
int uri = (namespace != null) ? m_strings.find(namespace) : -1;
for (int o = 0; o != m_attributes.length; o += ATTRIBUTE_LENGTH) {
if (name == m_attributes[o + ATTRIBUTE_IX_NAME]
&& (uri == -1 || uri == m_attributes[o + ATTRIBUTE_IX_NAMESPACE_URI])) {
int uri = (namespace != null) ? mStringBlock.find(namespace) : -1;
for (int o = 0; o != mAttributes.length; o += ATTRIBUTE_LENGTH) {
if (name == mAttributes[o + ATTRIBUTE_IX_NAME]
&& (uri == -1 || uri == mAttributes[o + ATTRIBUTE_IX_NAMESPACE_URI])) {
return o / ATTRIBUTE_LENGTH;
}
}
@ -633,127 +629,135 @@ public class AXmlResourceParser implements XmlResourceParser {
}
private void resetEventInfo() {
m_event = -1;
m_lineNumber = -1;
m_name = -1;
m_namespaceUri = -1;
m_attributes = null;
m_idAttribute = -1;
m_classAttribute = -1;
m_styleAttribute = -1;
mEvent = -1;
mLineNumber = -1;
mNameIndex = -1;
mNamespaceIndex = -1;
mAttributes = null;
mIdIndex = -1;
mClassIndex = -1;
mStyleIndex = -1;
}
private void doNext() throws IOException {
// Delayed initialization.
if (m_strings == null) {
m_reader.skipCheckInt(CHUNK_AXML_FILE, CHUNK_AXML_FILE_BROKEN);
if (mStringBlock == null) {
mIn.skipInt(); // XML Chunk AXML Type
mIn.skipInt(); // Chunk Size
// chunkSize
m_reader.skipInt();
m_strings = StringBlock.read(m_reader);
m_namespaces.increaseDepth();
m_operational = true;
mStringBlock = StringBlock.readWithChunk(mIn);
mNamespaces.increaseDepth();
isOperational = true;
}
if (m_event == END_DOCUMENT) {
if (mEvent == END_DOCUMENT) {
return;
}
int event = m_event;
int event = mEvent;
resetEventInfo();
while (true) {
if (m_decreaseDepth) {
m_decreaseDepth = false;
m_namespaces.decreaseDepth();
mNamespaces.decreaseDepth();
}
// Fake END_DOCUMENT event.
if (event == END_TAG && m_namespaces.getDepth() == 1 && m_namespaces.getCurrentCount() == 0) {
m_event = END_DOCUMENT;
if (event == END_TAG && mNamespaces.getDepth() == 1 && mNamespaces.getCurrentCount() == 0) {
mEvent = END_DOCUMENT;
break;
}
int chunkType;
if (event == START_DOCUMENT) {
// Fake event, see CHUNK_XML_START_TAG handler.
chunkType = CHUNK_XML_START_TAG;
chunkType = ARSCHeader.RES_XML_START_ELEMENT_TYPE;
} else {
chunkType = m_reader.readInt();
chunkType = mIn.readShort();
mIn.skipShort(); // headerSize
}
if (chunkType == CHUNK_RESOURCEIDS) {
int chunkSize = m_reader.readInt();
if (chunkType == ARSCHeader.RES_XML_RESOURCE_MAP_TYPE) {
int chunkSize = mIn.readInt();
if (chunkSize < 8 || (chunkSize % 4) != 0) {
throw new IOException("Invalid resource ids size (" + chunkSize + ").");
}
m_resourceIDs = m_reader.readIntArray(chunkSize / 4 - 2);
mResourceIds = mIn.readIntArray(chunkSize / 4 - 2);
continue;
}
if (chunkType < CHUNK_XML_FIRST || chunkType > CHUNK_XML_LAST) {
if (chunkType < ARSCHeader.RES_XML_FIRST_CHUNK_TYPE || chunkType > ARSCHeader.RES_XML_LAST_CHUNK_TYPE) {
throw new IOException("Invalid chunk type (" + chunkType + ").");
}
// Fake START_DOCUMENT event.
if (chunkType == CHUNK_XML_START_TAG && event == -1) {
m_event = START_DOCUMENT;
if (chunkType == ARSCHeader.RES_XML_START_ELEMENT_TYPE && event == -1) {
mEvent = START_DOCUMENT;
break;
}
// Common header.
/* chunkSize */m_reader.skipInt();
int lineNumber = m_reader.readInt();
/* 0xFFFFFFFF */m_reader.skipInt();
// Read remainder of ResXMLTree_node
mIn.skipInt(); // chunkSize
mLineNumber = mIn.readInt();
mIn.skipInt(); // Optional XML Comment
if (chunkType == CHUNK_XML_START_NAMESPACE || chunkType == CHUNK_XML_END_NAMESPACE) {
if (chunkType == CHUNK_XML_START_NAMESPACE) {
int prefix = m_reader.readInt();
int uri = m_reader.readInt();
m_namespaces.push(prefix, uri);
if (chunkType == ARSCHeader.RES_XML_START_NAMESPACE_TYPE || chunkType == ARSCHeader.RES_XML_END_NAMESPACE_TYPE) {
if (chunkType == ARSCHeader.RES_XML_START_NAMESPACE_TYPE) {
int prefix = mIn.readInt();
int uri = mIn.readInt();
mNamespaces.push(prefix, uri);
} else {
/* prefix */m_reader.skipInt();
/* uri */m_reader.skipInt();
m_namespaces.pop();
mIn.skipInt(); // prefix
mIn.skipInt(); // uri
mNamespaces.pop();
}
continue;
}
m_lineNumber = lineNumber;
if (chunkType == CHUNK_XML_START_TAG) {
m_namespaceUri = m_reader.readInt();
m_name = m_reader.readInt();
/* flags? */m_reader.skipInt();
int attributeCount = m_reader.readInt();
m_idAttribute = (attributeCount >>> 16) - 1;
attributeCount &= 0xFFFF;
m_classAttribute = m_reader.readInt();
m_styleAttribute = (m_classAttribute >>> 16) - 1;
m_classAttribute = (m_classAttribute & 0xFFFF) - 1;
m_attributes = m_reader.readIntArray(attributeCount * ATTRIBUTE_LENGTH);
for (int i = ATTRIBUTE_IX_VALUE_TYPE; i < m_attributes.length; ) {
m_attributes[i] = (m_attributes[i] >>> 24);
if (chunkType == ARSCHeader.RES_XML_START_ELEMENT_TYPE) {
mNamespaceIndex = mIn.readInt();
mNameIndex = mIn.readInt();
mIn.skipShort(); // attributeStart
int attributeSize = mIn.readShort();
int attributeCount = mIn.readShort();
mIdIndex = mIn.readShort();
mClassIndex = mIn.readShort();
mStyleIndex = mIn.readShort();
mAttributes = mIn.readIntArray(attributeCount * ATTRIBUTE_LENGTH);
for (int i = ATTRIBUTE_IX_VALUE_TYPE; i < mAttributes.length; ) {
mAttributes[i] = (mAttributes[i] >>> 24);
i += ATTRIBUTE_LENGTH;
}
m_namespaces.increaseDepth();
m_event = START_TAG;
int byteAttrSizeRead = (attributeCount * ATTRIBUTE_LENGTH) * 4;
int byteAttrSizeReported = (attributeSize * attributeCount);
// Check for misleading chunk sizes
if (byteAttrSizeRead < byteAttrSizeReported) {
int bytesToSkip = byteAttrSizeReported - byteAttrSizeRead;
mIn.skipBytes(bytesToSkip);
LOGGER.fine("Skipping " + bytesToSkip + " unknown bytes in attributes area.");
}
mNamespaces.increaseDepth();
mEvent = START_TAG;
break;
}
if (chunkType == CHUNK_XML_END_TAG) {
m_namespaceUri = m_reader.readInt();
m_name = m_reader.readInt();
m_event = END_TAG;
if (chunkType == ARSCHeader.RES_XML_END_ELEMENT_TYPE) {
mNamespaceIndex = mIn.readInt();
mNameIndex = mIn.readInt();
mEvent = END_TAG;
m_decreaseDepth = true;
break;
}
if (chunkType == CHUNK_XML_TEXT) {
m_name = m_reader.readInt();
/* ? */m_reader.skipInt();
/* ? */m_reader.skipInt();
m_event = TEXT;
if (chunkType == ARSCHeader.RES_XML_CDATA_TYPE) {
mNameIndex = mIn.readInt();
mIn.skipInt();
mIn.skipInt();
mEvent = TEXT;
break;
}
}
@ -765,40 +769,36 @@ public class AXmlResourceParser implements XmlResourceParser {
}
}
private ExtDataInput m_reader;
private ExtDataInput mIn;
private ResAttrDecoder mAttrDecoder;
private AndrolibException mFirstError;
private boolean m_operational = false;
private StringBlock m_strings;
private int[] m_resourceIDs;
private final NamespaceStack m_namespaces = new NamespaceStack();
private final String android_ns = "http://schemas.android.com/apk/res/android";
private boolean isOperational = false;
private StringBlock mStringBlock;
private int[] mResourceIds;
private final NamespaceStack mNamespaces = new NamespaceStack();
private boolean m_decreaseDepth;
// All values are essentially indices, e.g. m_name is an index of name in m_strings.
private int m_event;
private int m_lineNumber;
private int m_name;
private int m_namespaceUri;
private int[] m_attributes;
private int m_idAttribute;
private int m_classAttribute;
private int m_styleAttribute;
// All values are essentially indices, e.g. mNameIndex is an index of name in mStringBlock.
private int mEvent;
private int mLineNumber;
private int mNameIndex;
private int mNamespaceIndex;
private int[] mAttributes;
private int mIdIndex;
private int mClassIndex;
private int mStyleIndex;
private final static Logger LOGGER = Logger.getLogger(AXmlResourceParser.class.getName());
private static final String E_NOT_SUPPORTED = "Method is not supported.";
private static final int ATTRIBUTE_IX_NAMESPACE_URI = 0,
ATTRIBUTE_IX_NAME = 1, ATTRIBUTE_IX_VALUE_STRING = 2,
ATTRIBUTE_IX_VALUE_TYPE = 3, ATTRIBUTE_IX_VALUE_DATA = 4,
ATTRIBUTE_LENGTH = 5;
private static final int CHUNK_AXML_FILE = 0x00080003, CHUNK_AXML_FILE_BROKEN = 0x00080001,
CHUNK_RESOURCEIDS = 0x00080180, CHUNK_XML_FIRST = 0x00100100,
CHUNK_XML_START_NAMESPACE = 0x00100100,
CHUNK_XML_END_NAMESPACE = 0x00100101,
CHUNK_XML_START_TAG = 0x00100102, CHUNK_XML_END_TAG = 0x00100103,
CHUNK_XML_TEXT = 0x00100104, CHUNK_XML_LAST = 0x00100104;
// ResXMLTree_attribute
private static final int ATTRIBUTE_IX_NAMESPACE_URI = 0; // ns
private static final int ATTRIBUTE_IX_NAME = 1; // name
private static final int ATTRIBUTE_IX_VALUE_STRING = 2; // rawValue
private static final int ATTRIBUTE_IX_VALUE_TYPE = 3; // (size/res0/dataType)
private static final int ATTRIBUTE_IX_VALUE_DATA = 4; // data
private static final int ATTRIBUTE_LENGTH = 5;
private static final int PRIVATE_PKG_ID = 0x7F;
}

View File

@ -62,6 +62,8 @@ public class ResFileDecoder {
resFileMapping.put(inFilePath, outFilePath);
}
LOGGER.fine("Decoding file: " + inFilePath + " to: " + outFilePath);
try {
if (typeName.equals("raw")) {
decode(inDir, inFilePath, outDir, outFileName, "raw");

View File

@ -16,6 +16,7 @@
*/
package brut.androlib.res.decoder;
import brut.androlib.res.data.arsc.ARSCHeader;
import brut.androlib.res.xml.ResXmlEncoders;
import brut.util.ExtDataInput;
import com.google.common.annotations.VisibleForTesting;
@ -29,19 +30,15 @@ import java.util.List;
import java.util.logging.Logger;
public class StringBlock {
/**
* Reads whole (including chunk type) string block from stream. Stream must
* be at the chunk type.
* @param reader ExtDataInput
* @return StringBlock
*
* @throws IOException Parsing resources.arsc error
*/
public static StringBlock read(ExtDataInput reader) throws IOException {
reader.skipCheckChunkTypeInt(CHUNK_STRINGPOOL_TYPE, CHUNK_NULL_TYPE);
public static StringBlock readWithChunk(ExtDataInput reader) throws IOException {
reader.skipCheckShort(ARSCHeader.RES_STRING_POOL_TYPE);
reader.skipShort(); // headerSize
int chunkSize = reader.readInt();
return readWithoutChunk(reader, chunkSize);
}
public static StringBlock readWithoutChunk(ExtDataInput reader, int chunkSize) throws IOException {
// ResStringPool_header
int stringCount = reader.readInt();
int styleCount = reader.readInt();
@ -277,8 +274,5 @@ public class StringBlock {
private final CharsetDecoder CESU8_DECODER = Charset.forName("CESU8").newDecoder();
private static final Logger LOGGER = Logger.getLogger(StringBlock.class.getName());
// ResChunk_header = header.type (0x0001) + header.headerSize (0x001C)
private static final int CHUNK_STRINGPOOL_TYPE = 0x001C0001;
private static final int CHUNK_NULL_TYPE = 0x00000000;
private static final int UTF8_FLAG = 0x00000100;
}

View File

@ -0,0 +1,59 @@
/*
* Copyright (C) 2010 Ryszard Wiśniewski <brut.alll@gmail.com>
* Copyright (C) 2010 Connor Tumbleson <connor.tumbleson@gmail.com>
*
* 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
*
* https://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 brut.androlib.decode;
import brut.androlib.ApkDecoder;
import brut.androlib.BaseTest;
import brut.androlib.TestUtils;
import brut.directory.ExtFile;
import brut.common.BrutException;
import brut.util.OS;
import java.io.File;
import java.io.IOException;
import org.junit.*;
import static org.junit.Assert.*;
public class ProtectedApkTest extends BaseTest {
@BeforeClass
public static void beforeClass() throws Exception {
TestUtils.cleanFrameworkFile();
sTmpDir = new ExtFile(OS.createTempDirectory());
TestUtils.copyResourceDir(ProtectedApkTest.class, "decode/protected-chunks/", sTmpDir);
}
@AfterClass
public static void afterClass() throws BrutException {
OS.rmdir(sTmpDir);
}
@Test
public void checkIfDecodeWorksWithoutCrash() throws BrutException, IOException {
String apk = "protected-v1.apk";
// decode protected-v1.apk
ApkDecoder apkDecoder = new ApkDecoder(new File(sTmpDir + File.separator + apk));
sTestOrigDir = new ExtFile(sTmpDir + File.separator + apk + ".out");
File outDir = new File(sTmpDir + File.separator + apk + ".out");
apkDecoder.decode(outDir);
File stringsXml = new File(sTestOrigDir,"res/values/strings.xml");
assertTrue(stringsXml.isFile());
}
}

View File

@ -29,7 +29,7 @@ public class ExtDataInput extends DataInputDelegate {
public int[] readIntArray(int length) throws IOException {
int[] array = new int[length];
for(int i = 0; i < length; i++) {
for (int i = 0; i < length; i++) {
array[i] = readInt();
}
return array;
@ -39,36 +39,20 @@ public class ExtDataInput extends DataInputDelegate {
skipBytes(4);
}
public void skipCheckInt(int expected1, int expected2) throws IOException {
int got = readInt();
if (got != expected1 && got != expected2) {
throw new IOException(String.format(
"Expected: 0x%08x or 0x%08x, got: 0x%08x", expected1, expected2, got));
}
public void skipShort() throws IOException {
skipBytes(2);
}
public void skipCheckShort(short expected) throws IOException {
short got = readShort();
if (got != expected) {
throw new IOException(String.format(
"Expected: 0x%08x, got: 0x%08x", expected, got));
throw new IOException(String.format("Expected: 0x%08x, got: 0x%08x", expected, got));
}
}
public void skipCheckByte(byte expected) throws IOException {
byte got = readByte();
if (got != expected) {
throw new IOException(String.format(
"Expected: 0x%08x, got: 0x%08x", expected, got));
}
}
public void skipCheckChunkTypeInt(int expected, int possible) throws IOException {
int got = readInt();
if (got == possible || got < expected) {
skipCheckChunkTypeInt(expected, -1);
} else if (got != expected) {
throw new IOException(String.format("Expected: 0x%08x, got: 0x%08x", expected, got));
}
}
@ -89,10 +73,9 @@ public class ExtDataInput extends DataInputDelegate {
return total;
}
public String readNullEndedString(int length, boolean fixed)
throws IOException {
public String readNullEndedString(int length, boolean fixed) throws IOException {
StringBuilder string = new StringBuilder(16);
while(length-- != 0) {
while (length-- != 0) {
short ch = readShort();
if (ch == 0) {
break;