diff --git a/jadx-core/src/main/java/jadx/core/xmlgen/CommonBinaryParser.java b/jadx-core/src/main/java/jadx/core/xmlgen/CommonBinaryParser.java index 0eebf5b9e..a5f9f735f 100644 --- a/jadx-core/src/main/java/jadx/core/xmlgen/CommonBinaryParser.java +++ b/jadx-core/src/main/java/jadx/core/xmlgen/CommonBinaryParser.java @@ -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(); diff --git a/jadx-core/src/main/java/jadx/core/xmlgen/ResTableBinaryParser.java b/jadx-core/src/main/java/jadx/core/xmlgen/ResTableBinaryParser.java index bf3970d32..86c742491 100644 --- a/jadx-core/src/main/java/jadx/core/xmlgen/ResTableBinaryParser.java +++ b/jadx-core/src/main/java/jadx/core/xmlgen/ResTableBinaryParser.java @@ -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(); }