Skip to content

Commit

Permalink
ARSC/AXML Parser Rework (#3131)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
iBotPeaches committed Jul 12, 2023
1 parent 8634050 commit bdbe138
Show file tree
Hide file tree
Showing 10 changed files with 368 additions and 315 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -190,19 +190,20 @@ private void adjustPackageManifest(ResTable resTable, String filePath)
// 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);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -364,7 +364,7 @@ private void initPackageInfo() throws AndrolibException {
}

// 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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,21 +43,37 @@ public static ARSCHeader read(ExtDataInput in, CountingInputStream countIn) thro
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;
public final static short XML_TYPE_LIBRARY = 0x0203;
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;
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,7 @@
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 {
Expand All @@ -50,7 +47,7 @@ public static ARSCData decode(InputStream arscStream, boolean findFlagsOffsets,
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]));
Expand All @@ -74,19 +71,85 @@ private ARSCDecoder(InputStream arscStream, ResTable resTable, boolean storeFlag
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();

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;
}
}

nextChunk();
for (int i = 0; i < packageCount; i++) {
mTypeIdOffset = 0;
packages[i] = readTablePackage();
if (mPkg.getResSpecCount() > 0) {
addMissingResSpecs();
}
return packages;

// 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 {
Expand Down Expand Up @@ -121,38 +184,12 @@ private ResPackage readTablePackage() throws IOException, AndrolibException {
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;
}

Expand All @@ -168,8 +205,6 @@ private void readLibraryType() throws AndrolibException, IOException {
packageName = mIn.readNullEndedString(128, true);
LOGGER.info(String.format("Decoding Shared Library (%s), pkgId: %d", packageName, packageId));
}

nextChunk();
}

private void readStagedAliasSpec() throws IOException {
Expand All @@ -178,17 +213,13 @@ private void readStagedAliasSpec() throws IOException {
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 {
checkChunkType(ARSCHeader.XML_TYPE_OVERLAY);
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 {
Expand All @@ -199,48 +230,13 @@ private void readOverlayPolicySpec() throws AndrolibException, IOException {
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) {
Expand All @@ -250,6 +246,7 @@ private ResTypeSpec readSingleTableTypeSpec() throws AndrolibException, IOExcept
mIn.skipBytes(entryCount * 4); // flags
mTypeSpec = new ResTypeSpec(mTypeNames.getString(id - 1), mResTable, mPkg, id, entryCount);
mPkg.addType(mTypeSpec);

return mTypeSpec;
}

Expand Down Expand Up @@ -311,6 +308,12 @@ private ResType readTableType() throws IOException, AndrolibException {
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;
}

Expand Down Expand Up @@ -616,11 +619,6 @@ private void checkChunkType(int expectedType) throws AndrolibException {
}
}

private void nextChunkCheckType(int expectedType) throws IOException, AndrolibException {
nextChunk();
checkChunkType(expectedType);
}

private final ExtDataInput mIn;
private final ResTable mResTable;
private final CountingInputStream mCountIn;
Expand Down

0 comments on commit bdbe138

Please sign in to comment.