fix(res): improve resource table and config decoding (#2420)

This commit is contained in:
Skylot
2025-03-17 22:14:26 +00:00
parent b4f1021885
commit d1a3935c9e
2 changed files with 92 additions and 75 deletions
@@ -2,7 +2,12 @@ package jadx.core.xmlgen;
import java.io.IOException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class CommonBinaryParser extends ParserConstants {
private static final Logger LOG = LoggerFactory.getLogger(CommonBinaryParser.class);
protected ParserStream is;
protected BinaryXMLStrings parseStringPool() throws IOException {
@@ -12,10 +17,17 @@ public class CommonBinaryParser extends ParserConstants {
protected BinaryXMLStrings parseStringPoolNoType() throws IOException {
long start = is.getPos() - 2;
is.checkInt16(0x001c, "String pool header size not 0x001c");
int headerSize = is.readInt16();
if (headerSize != 0x1c) {
LOG.warn("Unexpected string pool header size: 0x{}, expected: 0x1C", Integer.toHexString(headerSize));
}
long size = is.readUInt32();
long chunkEnd = start + size;
return parseStringPoolNoSize(start, chunkEnd);
}
protected BinaryXMLStrings parseStringPoolNoSize(long start, long chunkEnd) throws IOException {
int stringCount = is.readInt32();
int styleCount = is.readInt32();
int flags = is.readInt32();
@@ -1,6 +1,7 @@
package jadx.core.xmlgen;
import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
@@ -102,41 +103,52 @@ public class ResTableBinaryParser extends CommonBinaryParser implements IResTabl
void decodeTableChunk() throws IOException {
is.checkInt16(RES_TABLE_TYPE, "Not a table chunk");
is.checkInt16(0x000c, "Unexpected table header size");
/* int size = */
is.readInt32();
int size = is.readInt32();
int pkgCount = is.readInt32();
strings = parseStringPool();
for (int i = 0; i < pkgCount; i++) {
parsePackage();
int pkgNum = 0;
while (is.getPos() < size) {
long chuckStart = is.getPos();
int type = is.readInt16();
int headerSize = is.readInt16();
long chunkSize = is.readUInt32();
long chunkEnd = chuckStart + chunkSize;
switch (type) {
case RES_NULL_TYPE:
// skip
break;
case RES_STRING_POOL_TYPE:
strings = parseStringPoolNoSize(chuckStart, chunkEnd);
break;
case RES_TABLE_PACKAGE_TYPE:
parsePackage(chuckStart, headerSize, chunkEnd);
pkgNum++;
break;
}
is.skipToPos(chunkEnd, "Skip to table chunk end");
}
if (pkgNum != pkgCount) {
LOG.warn("Unexpected package chunks, read: {}, expected: {}", pkgNum, pkgCount);
}
}
private PackageChunk parsePackage() throws IOException {
long start = is.getPos();
is.checkInt16(RES_TABLE_PACKAGE_TYPE, "Not a table chunk");
int headerSize = is.readInt16();
private void parsePackage(long pkgChunkStart, int headerSize, long pkgChunkEnd) throws IOException {
if (headerSize < 0x011c) {
die("Package header size too small");
return;
}
long size = is.readUInt32();
long endPos = start + size;
int id = is.readInt32();
String name = is.readString16Fixed(128);
long typeStringsOffset = start + is.readInt32();
/* int lastPublicType = */
is.readInt32();
long keyStringsOffset = start + is.readInt32();
/* int lastPublicKey = */
is.readInt32();
long typeStringsOffset = pkgChunkStart + is.readInt32();
int lastPublicType = is.readInt32();
long keyStringsOffset = pkgChunkStart + is.readInt32();
int lastPublicKey = is.readInt32();
if (headerSize >= 0x0120) {
/* int typeIdOffset = */
is.readInt32();
int typeIdOffset = is.readInt32();
}
is.skipToPos(start + headerSize, "package header end");
is.skipToPos(pkgChunkStart + headerSize, "package header end");
BinaryXMLStrings typeStrings = null;
if (typeStringsOffset != 0) {
@@ -152,7 +164,7 @@ public class ResTableBinaryParser extends CommonBinaryParser implements IResTabl
PackageChunk pkg = new PackageChunk(id, name, typeStrings, keyStrings);
resStorage.setAppPackage(name);
while (is.getPos() < endPos) {
while (is.getPos() < pkgChunkEnd) {
long chunkStart = is.getPos();
int type = is.readInt16();
LOG.trace("res package chunk start at {} type {}", chunkStart, type);
@@ -182,7 +194,6 @@ public class ResTableBinaryParser extends CommonBinaryParser implements IResTabl
LOG.warn("Unknown chunk type {} encountered at offset {}", type, chunkStart);
}
}
return pkg;
}
@SuppressWarnings("unused")
@@ -493,69 +504,59 @@ public class ResTableBinaryParser extends CommonBinaryParser implements IResTabl
private EntryConfig parseConfig() throws IOException {
long start = is.getPos();
int size = is.readInt32();
if (size < 28) {
throw new IOException("Config size < 28");
if (size < 4) {
throw new IOException("Config size < 4");
}
short mcc = (short) is.readInt16();
short mnc = (short) is.readInt16();
// Android zero fill this structure and only read the data present
var configData = new byte[Math.max(52, size - 4)];
is.readFully(configData, 0, size - 4);
var configIs = new ParserStream(new ByteArrayInputStream(configData));
char[] language = unpackLocaleOrRegion((byte) is.readInt8(), (byte) is.readInt8(), 'a');
char[] country = unpackLocaleOrRegion((byte) is.readInt8(), (byte) is.readInt8(), '0');
short mcc = (short) configIs.readInt16();
short mnc = (short) configIs.readInt16();
byte orientation = (byte) is.readInt8();
byte touchscreen = (byte) is.readInt8();
int density = is.readInt16();
char[] language = unpackLocaleOrRegion((byte) configIs.readInt8(), (byte) configIs.readInt8(), 'a');
char[] country = unpackLocaleOrRegion((byte) configIs.readInt8(), (byte) configIs.readInt8(), '0');
byte keyboard = (byte) is.readInt8();
byte navigation = (byte) is.readInt8();
byte inputFlags = (byte) is.readInt8();
byte grammaticalInflection = (byte) is.readInt8();
byte orientation = (byte) configIs.readInt8();
byte touchscreen = (byte) configIs.readInt8();
int density = configIs.readInt16();
short screenWidth = (short) is.readInt16();
short screenHeight = (short) is.readInt16();
byte keyboard = (byte) configIs.readInt8();
byte navigation = (byte) configIs.readInt8();
byte inputFlags = (byte) configIs.readInt8();
byte grammaticalInflection = (byte) configIs.readInt8();
short sdkVersion = (short) is.readInt16();
is.readInt16(); // minorVersion must always be 0
short screenWidth = (short) configIs.readInt16();
short screenHeight = (short) configIs.readInt16();
byte screenLayout = 0;
byte uiMode = 0;
short smallestScreenWidthDp = 0;
if (size >= 32) {
screenLayout = (byte) is.readInt8();
uiMode = (byte) is.readInt8();
smallestScreenWidthDp = (short) is.readInt16();
}
short sdkVersion = (short) configIs.readInt16();
configIs.readInt16(); // minorVersion must always be 0
short screenWidthDp = 0;
short screenHeightDp = 0;
if (size >= 36) {
screenWidthDp = (short) is.readInt16();
screenHeightDp = (short) is.readInt16();
}
byte screenLayout = (byte) configIs.readInt8();
byte uiMode = (byte) configIs.readInt8();
short smallestScreenWidthDp = (short) configIs.readInt16();
short screenWidthDp = (short) configIs.readInt16();
short screenHeightDp = (short) configIs.readInt16();
char[] localeScript = null;
char[] localeVariant = null;
if (size >= 48) {
localeScript = readScriptOrVariantChar(4).toCharArray();
localeVariant = readScriptOrVariantChar(8).toCharArray();
}
char[] localeScript = readScriptOrVariantChar(4, configIs).toCharArray();
char[] localeVariant = readScriptOrVariantChar(8, configIs).toCharArray();
byte screenLayout2 = 0;
byte colorMode = 0;
if (size >= 52) {
screenLayout2 = (byte) is.readInt8();
colorMode = (byte) is.readInt8();
is.readInt16(); // reserved padding
}
byte screenLayout2 = (byte) configIs.readInt8();
byte colorMode = (byte) configIs.readInt8();
configIs.readInt16(); // reserved padding
is.skipToPos(start + size, "Config skip trailing bytes");
is.checkPos(start + size, "Config skip trailing bytes");
return new EntryConfig(mcc, mnc, language, country,
orientation, touchscreen, density, keyboard, navigation,
inputFlags, grammaticalInflection, screenWidth, screenHeight, sdkVersion,
screenLayout, uiMode, smallestScreenWidthDp, screenWidthDp,
screenHeightDp, localeScript, localeVariant, screenLayout2,
screenHeightDp,
localeScript.length == 0 ? null : localeScript,
localeVariant.length == 0 ? null : localeVariant,
screenLayout2,
colorMode, false, size);
}
@@ -574,16 +575,20 @@ public class ResTableBinaryParser extends CommonBinaryParser implements IResTabl
}
private String readScriptOrVariantChar(int length) throws IOException {
long start = is.getPos();
return readScriptOrVariantChar(length, is);
}
private static String readScriptOrVariantChar(int length, ParserStream ps) throws IOException {
long start = ps.getPos();
StringBuilder sb = new StringBuilder(16);
for (int i = 0; i < length; i++) {
short ch = (short) is.readInt8();
short ch = (short) ps.readInt8();
if (ch == 0) {
break;
}
sb.append((char) ch);
}
is.skipToPos(start + length, "readScriptOrVariantChar");
ps.skipToPos(start + length, "readScriptOrVariantChar");
return sb.toString();
}