fix(res): improve resource table and config decoding (#2420)
This commit is contained in:
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user