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 // compare resources.arsc package name to the one present in AndroidManifest
ResPackage resPackage = resTable.getCurrentResPackage(); ResPackage resPackage = resTable.getCurrentResPackage();
String pkgOriginal = resPackage.getName(); String pkgOriginal = resPackage.getName();
String packageRenamed = resTable.getPackageRenamed(); String pkgRenamed = resTable.getPackageRenamed();
resTable.setPackageId(resPackage.getId()); resTable.setPackageId(resPackage.getId());
resTable.setPackageOriginal(pkgOriginal); resTable.setPackageOriginal(pkgOriginal);
// 1) Check if pkgOriginal is null (empty resources.arsc) // 1) Check if pkgOriginal is null (empty resources.arsc)
// 2) Check if pkgOriginal === mPackageRenamed // 2) Check if pkgRenamed is null
// 3) Check if pkgOriginal is ignored via IGNORED_PACKAGES // 3) Check if pkgOriginal === mPackageRenamed
if (pkgOriginal == null || pkgOriginal.equalsIgnoreCase(packageRenamed) // 4) Check if pkgOriginal is ignored via IGNORED_PACKAGES
if (pkgOriginal == null || pkgRenamed == null || pkgOriginal.equalsIgnoreCase(pkgRenamed)
|| (Arrays.asList(IGNORED_PACKAGES).contains(pkgOriginal))) { || (Arrays.asList(IGNORED_PACKAGES).contains(pkgOriginal))) {
LOGGER.info("Regular manifest package..."); LOGGER.info("Regular manifest package...");
} else { } 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); 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 // 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.renameManifestPackage = renamed;
} }
mApkInfo.packageInfo.forcedPackageId = String.valueOf(id); mApkInfo.packageInfo.forcedPackageId = String.valueOf(id);

View File

@ -43,16 +43,22 @@ public class ARSCHeader {
try { try {
type = in.readShort(); type = in.readShort();
} catch (EOFException ex) { } 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); return new ARSCHeader(type, in.readShort(), in.readInt(), start);
} }
public final static short TYPE_NONE = -1; public void skipChunk(ExtDataInput in) throws IOException {
public final static short TYPE_STRING_POOL = 0x0001; in.skipBytes(chunkSize - headerSize);
public final static short TYPE_TABLE = 0x0002; }
public final static short TYPE_XML = 0x0003;
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_PACKAGE = 0x0200;
public final static short XML_TYPE_TYPE = 0x0201; public final static short XML_TYPE_TYPE = 0x0201;
public final static short XML_TYPE_SPEC_TYPE = 0x0202; 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 = 0x0204;
public final static short XML_TYPE_OVERLAY_POLICY = 0x0205; public final static short XML_TYPE_OVERLAY_POLICY = 0x0205;
public final static short XML_TYPE_STAGED_ALIAS = 0x0206; 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.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.math.BigInteger; import java.math.BigInteger;
import java.util.ArrayList; import java.util.*;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.logging.Logger; import java.util.logging.Logger;
public class ARSCDecoder { public class ARSCDecoder {
@ -50,7 +47,7 @@ public class ARSCDecoder {
throws AndrolibException { throws AndrolibException {
try { try {
ARSCDecoder decoder = new ARSCDecoder(arscStream, resTable, findFlagsOffsets, keepBroken); ARSCDecoder decoder = new ARSCDecoder(arscStream, resTable, findFlagsOffsets, keepBroken);
ResPackage[] pkgs = decoder.readTableHeader(); ResPackage[] pkgs = decoder.readResourceTable();
return new ARSCData(pkgs, decoder.mFlagsOffsets == null return new ARSCData(pkgs, decoder.mFlagsOffsets == null
? null ? null
: decoder.mFlagsOffsets.toArray(new FlagsOffset[0])); : decoder.mFlagsOffsets.toArray(new FlagsOffset[0]));
@ -74,19 +71,85 @@ public class ARSCDecoder {
mKeepBroken = keepBroken; mKeepBroken = keepBroken;
} }
private ResPackage[] readTableHeader() throws IOException, AndrolibException { private ResPackage[] readResourceTable() throws IOException, AndrolibException {
nextChunkCheckType(ARSCHeader.TYPE_TABLE); Set<ResPackage> pkgs = new LinkedHashSet<>();
int packageCount = mIn.readInt(); ResTypeSpec typeSpec;
mTableStrings = StringBlock.read(mIn); chunkLoop:
ResPackage[] packages = new ResPackage[packageCount]; for (;;) {
nextChunk();
nextChunk(); switch (mHeader.type) {
for (int i = 0; i < packageCount; i++) { case ARSCHeader.RES_NULL_TYPE:
mTypeIdOffset = 0; readUnknownChunk();
packages[i] = readTablePackage(); 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 { 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"); LOGGER.warning("Please report this application to Apktool for a fix: https://github.com/iBotPeaches/Apktool/issues/1728");
} }
mTypeNames = StringBlock.read(mIn); mTypeNames = StringBlock.readWithChunk(mIn);
mSpecNames = StringBlock.read(mIn); mSpecNames = StringBlock.readWithChunk(mIn);
mResId = id << 24; mResId = id << 24;
mPkg = new ResPackage(mResTable, id, name); 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; return mPkg;
} }
@ -168,8 +205,6 @@ public class ARSCDecoder {
packageName = mIn.readNullEndedString(128, true); packageName = mIn.readNullEndedString(128, true);
LOGGER.info(String.format("Decoding Shared Library (%s), pkgId: %d", packageName, packageId)); LOGGER.info(String.format("Decoding Shared Library (%s), pkgId: %d", packageName, packageId));
} }
nextChunk();
} }
private void readStagedAliasSpec() throws IOException { private void readStagedAliasSpec() throws IOException {
@ -178,8 +213,6 @@ public class ARSCDecoder {
for (int i = 0; i < count; i++) { for (int i = 0; i < count; i++) {
LOGGER.fine(String.format("Skipping staged alias stagedId (%h) finalId: %h", mIn.readInt(), mIn.readInt())); LOGGER.fine(String.format("Skipping staged alias stagedId (%h) finalId: %h", mIn.readInt(), mIn.readInt()));
} }
nextChunk();
} }
private void readOverlaySpec() throws AndrolibException, IOException { private void readOverlaySpec() throws AndrolibException, IOException {
@ -187,8 +220,6 @@ public class ARSCDecoder {
String name = mIn.readNullEndedString(256, true); String name = mIn.readNullEndedString(256, true);
String actor = mIn.readNullEndedString(256, true); String actor = mIn.readNullEndedString(256, true);
LOGGER.fine(String.format("Overlay name: \"%s\", actor: \"%s\")", name, actor)); LOGGER.fine(String.format("Overlay name: \"%s\", actor: \"%s\")", name, actor));
nextChunk();
} }
private void readOverlayPolicySpec() throws AndrolibException, IOException { private void readOverlayPolicySpec() throws AndrolibException, IOException {
@ -199,48 +230,13 @@ public class ARSCDecoder {
for (int i = 0; i < count; i++) { for (int i = 0; i < count; i++) {
LOGGER.fine(String.format("Skipping overlay (%h)", mIn.readInt())); LOGGER.fine(String.format("Skipping overlay (%h)", mIn.readInt()));
} }
nextChunk();
} }
private void readTableTypeSpec() throws AndrolibException, IOException { private ResTypeSpec readTableSpecType() 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 {
checkChunkType(ARSCHeader.XML_TYPE_SPEC_TYPE); checkChunkType(ARSCHeader.XML_TYPE_SPEC_TYPE);
int id = mIn.readUnsignedByte(); int id = mIn.readUnsignedByte();
mIn.skipBytes(3); mIn.skipBytes(1); // reserved0
mIn.skipBytes(2); // reserved1
int entryCount = mIn.readInt(); int entryCount = mIn.readInt();
if (mFlagsOffsets != null) { if (mFlagsOffsets != null) {
@ -250,6 +246,7 @@ public class ARSCDecoder {
mIn.skipBytes(entryCount * 4); // flags mIn.skipBytes(entryCount * 4); // flags
mTypeSpec = new ResTypeSpec(mTypeNames.getString(id - 1), mResTable, mPkg, id, entryCount); mTypeSpec = new ResTypeSpec(mTypeNames.getString(id - 1), mResTable, mPkg, id, entryCount);
mPkg.addType(mTypeSpec); mPkg.addType(mTypeSpec);
return mTypeSpec; return mTypeSpec;
} }
@ -311,6 +308,12 @@ public class ARSCDecoder {
readEntry(readEntryData()); 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; 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 ExtDataInput mIn;
private final ResTable mResTable; private final ResTable mResTable;
private final CountingInputStream mCountIn; private final CountingInputStream mCountIn;

View File

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

View File

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

View File

@ -16,6 +16,7 @@
*/ */
package brut.androlib.res.decoder; package brut.androlib.res.decoder;
import brut.androlib.res.data.arsc.ARSCHeader;
import brut.androlib.res.xml.ResXmlEncoders; import brut.androlib.res.xml.ResXmlEncoders;
import brut.util.ExtDataInput; import brut.util.ExtDataInput;
import com.google.common.annotations.VisibleForTesting; import com.google.common.annotations.VisibleForTesting;
@ -29,19 +30,15 @@ import java.util.List;
import java.util.logging.Logger; import java.util.logging.Logger;
public class StringBlock { public class StringBlock {
public static StringBlock readWithChunk(ExtDataInput reader) throws IOException {
/** reader.skipCheckShort(ARSCHeader.RES_STRING_POOL_TYPE);
* Reads whole (including chunk type) string block from stream. Stream must reader.skipShort(); // headerSize
* 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);
int chunkSize = reader.readInt(); int chunkSize = reader.readInt();
return readWithoutChunk(reader, chunkSize);
}
public static StringBlock readWithoutChunk(ExtDataInput reader, int chunkSize) throws IOException {
// ResStringPool_header // ResStringPool_header
int stringCount = reader.readInt(); int stringCount = reader.readInt();
int styleCount = reader.readInt(); int styleCount = reader.readInt();
@ -277,8 +274,5 @@ public class StringBlock {
private final CharsetDecoder CESU8_DECODER = Charset.forName("CESU8").newDecoder(); private final CharsetDecoder CESU8_DECODER = Charset.forName("CESU8").newDecoder();
private static final Logger LOGGER = Logger.getLogger(StringBlock.class.getName()); 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; 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 { public int[] readIntArray(int length) throws IOException {
int[] array = new int[length]; int[] array = new int[length];
for(int i = 0; i < length; i++) { for (int i = 0; i < length; i++) {
array[i] = readInt(); array[i] = readInt();
} }
return array; return array;
@ -39,36 +39,20 @@ public class ExtDataInput extends DataInputDelegate {
skipBytes(4); skipBytes(4);
} }
public void skipCheckInt(int expected1, int expected2) throws IOException { public void skipShort() throws IOException {
int got = readInt(); skipBytes(2);
if (got != expected1 && got != expected2) {
throw new IOException(String.format(
"Expected: 0x%08x or 0x%08x, got: 0x%08x", expected1, expected2, got));
}
} }
public void skipCheckShort(short expected) throws IOException { public void skipCheckShort(short expected) throws IOException {
short got = readShort(); short got = readShort();
if (got != expected) { if (got != expected) {
throw new IOException(String.format( throw new IOException(String.format("Expected: 0x%08x, got: 0x%08x", expected, got));
"Expected: 0x%08x, got: 0x%08x", expected, got));
} }
} }
public void skipCheckByte(byte expected) throws IOException { public void skipCheckByte(byte expected) throws IOException {
byte got = readByte(); byte got = readByte();
if (got != expected) { 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)); throw new IOException(String.format("Expected: 0x%08x, got: 0x%08x", expected, got));
} }
} }
@ -89,10 +73,9 @@ public class ExtDataInput extends DataInputDelegate {
return total; return total;
} }
public String readNullEndedString(int length, boolean fixed) public String readNullEndedString(int length, boolean fixed) throws IOException {
throws IOException {
StringBuilder string = new StringBuilder(16); StringBuilder string = new StringBuilder(16);
while(length-- != 0) { while (length-- != 0) {
short ch = readShort(); short ch = readShort();
if (ch == 0) { if (ch == 0) {
break; break;