Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: support skipping unread header sizes of ResChunk #3180

Merged
merged 6 commits into from
Jul 23, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@

import java.io.EOFException;
import java.io.IOException;
import java.math.BigInteger;
import java.util.logging.Logger;

public class ARSCHeader {
public final short type;
Expand Down Expand Up @@ -48,6 +50,30 @@ public static ARSCHeader read(ExtDataInput in, CountingInputStream countIn) thro
return new ARSCHeader(type, in.readShort(), in.readInt(), start);
}

public void checkForUnreadHeader(ExtDataInput in, CountingInputStream countIn) throws IOException {
// Some applications lie about the reported size of their chunk header. Trusting the chunkSize is misleading
// So compare to what we actually read in the header vs reported and skip the rest.
// However, this runs after each chunk and not every chunk reading has a specific distinction between the
// header and the body.
int actualHeaderSize = countIn.getCount() - this.startPosition;
int exceedingSize = this.headerSize - actualHeaderSize;
if (exceedingSize > 0) {
byte[] buf = new byte[exceedingSize];
in.readFully(buf);
BigInteger exceedingBI = new BigInteger(1, buf);

if (exceedingBI.equals(BigInteger.ZERO)) {
LOGGER.fine(String.format("Chunk header size (%d), read (%d), but exceeding bytes are all zero.",
this.headerSize, actualHeaderSize
));
} else {
LOGGER.warning(String.format("Chunk header size (%d), read (%d). Exceeding bytes: 0x%X.",
this.headerSize, actualHeaderSize, exceedingBI
));
}
}
}

public void skipChunk(ExtDataInput in) throws IOException {
in.skipBytes(chunkSize - headerSize);
}
Expand Down Expand Up @@ -76,4 +102,6 @@ public void skipChunk(ExtDataInput in) throws IOException {
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;

private static final Logger LOGGER = Logger.getLogger(ARSCHeader.class.getName());
}
Original file line number Diff line number Diff line change
Expand Up @@ -137,11 +137,15 @@ private void readStringPoolChunk() throws IOException, AndrolibException {
private void readTableChunk() throws IOException, AndrolibException {
checkChunkType(ARSCHeader.RES_TABLE_TYPE);
mIn.skipInt(); // packageCount

mHeader.checkForUnreadHeader(mIn, mCountIn);
}

private void readUnknownChunk() throws IOException, AndrolibException {
checkChunkType(ARSCHeader.RES_NULL_TYPE);

mHeader.checkForUnreadHeader(mIn, mCountIn);

LOGGER.warning("Skipping unknown chunk data of size " + mHeader.chunkSize);
mHeader.skipChunk(mIn);
}
Expand Down Expand Up @@ -178,6 +182,8 @@ private ResPackage readTablePackage() throws IOException, AndrolibException {
LOGGER.warning("Please report this application to Apktool for a fix: https://github.com/iBotPeaches/Apktool/issues/1728");
}

mHeader.checkForUnreadHeader(mIn, mCountIn);

mTypeNames = StringBlock.readWithChunk(mIn);
mSpecNames = StringBlock.readWithChunk(mIn);

Expand All @@ -194,6 +200,8 @@ private void readLibraryType() throws AndrolibException, IOException {
int packageId;
String packageName;

mHeader.checkForUnreadHeader(mIn, mCountIn);

for (int i = 0; i < libraryCount; i++) {
packageId = mIn.readInt();
packageName = mIn.readNullEndedString(128, true);
Expand All @@ -204,6 +212,8 @@ private void readLibraryType() throws AndrolibException, IOException {
private void readStagedAliasSpec() throws IOException {
int count = mIn.readInt();

mHeader.checkForUnreadHeader(mIn, mCountIn);

for (int i = 0; i < count; i++) {
LOGGER.fine(String.format("Skipping staged alias stagedId (%h) finalId: %h", mIn.readInt(), mIn.readInt()));
}
Expand All @@ -213,6 +223,9 @@ private void readOverlaySpec() throws AndrolibException, IOException {
checkChunkType(ARSCHeader.XML_TYPE_OVERLAY);
String name = mIn.readNullEndedString(256, true);
String actor = mIn.readNullEndedString(256, true);

mHeader.checkForUnreadHeader(mIn, mCountIn);

LOGGER.fine(String.format("Overlay name: \"%s\", actor: \"%s\")", name, actor));
}

Expand All @@ -221,6 +234,8 @@ private void readOverlayPolicySpec() throws AndrolibException, IOException {
mIn.skipInt(); // policyFlags
int count = mIn.readInt();

mHeader.checkForUnreadHeader(mIn, mCountIn);

for (int i = 0; i < count; i++) {
LOGGER.fine(String.format("Skipping overlay (%h)", mIn.readInt()));
}
Expand All @@ -237,7 +252,9 @@ private ResTypeSpec readTableSpecType() throws AndrolibException, IOException {
mFlagsOffsets.add(new FlagsOffset(mCountIn.getCount(), entryCount));
}

mIn.skipBytes(entryCount * 4); // flags
mHeader.checkForUnreadHeader(mIn, mCountIn);

mIn.skipBytes(entryCount * 4); // flags
mTypeSpec = new ResTypeSpec(mTypeNames.getString(id - 1), mResTable, mPkg, id, entryCount);
mPkg.addType(mTypeSpec);

Expand All @@ -255,18 +272,12 @@ private ResType readTableType() throws IOException, AndrolibException {
int typeFlags = mIn.readByte();
mIn.skipBytes(2); // reserved
int entryCount = mIn.readInt();
int entriesStart = mIn.readInt();
mMissingResSpecMap = new LinkedHashMap<>();
mIn.skipInt(); // entriesStart

mMissingResSpecMap = new LinkedHashMap<>();
ResConfigFlags flags = readConfigFlags();
int position = (mHeader.startPosition + entriesStart) - (entryCount * 4);

// For some APKs there is a disconnect between the reported size of Configs
// If we find a mismatch skip those bytes.
if (position != mCountIn.getCount()) {
LOGGER.warning("Invalid data detected. Skipping: " + (position - mCountIn.getCount()) + " byte(s)");
mIn.skipBytes(position - mCountIn.getCount());
}
mHeader.checkForUnreadHeader(mIn, mCountIn);

if ((typeFlags & 0x01) != 0) {
LOGGER.fine("Sparse type flags detected: " + mTypeSpec.getName());
Expand Down