Skip to content

Commit d1a3935

Browse files
committed
fix(res): improve resource table and config decoding (#2420)
1 parent b4f1021 commit d1a3935

File tree

2 files changed

+92
-75
lines changed

2 files changed

+92
-75
lines changed

jadx-core/src/main/java/jadx/core/xmlgen/CommonBinaryParser.java

+13-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,12 @@
22

33
import java.io.IOException;
44

5+
import org.slf4j.Logger;
6+
import org.slf4j.LoggerFactory;
7+
58
public class CommonBinaryParser extends ParserConstants {
9+
private static final Logger LOG = LoggerFactory.getLogger(CommonBinaryParser.class);
10+
611
protected ParserStream is;
712

813
protected BinaryXMLStrings parseStringPool() throws IOException {
@@ -12,10 +17,17 @@ protected BinaryXMLStrings parseStringPool() throws IOException {
1217

1318
protected BinaryXMLStrings parseStringPoolNoType() throws IOException {
1419
long start = is.getPos() - 2;
15-
is.checkInt16(0x001c, "String pool header size not 0x001c");
20+
int headerSize = is.readInt16();
21+
if (headerSize != 0x1c) {
22+
LOG.warn("Unexpected string pool header size: 0x{}, expected: 0x1C", Integer.toHexString(headerSize));
23+
}
1624
long size = is.readUInt32();
1725
long chunkEnd = start + size;
1826

27+
return parseStringPoolNoSize(start, chunkEnd);
28+
}
29+
30+
protected BinaryXMLStrings parseStringPoolNoSize(long start, long chunkEnd) throws IOException {
1931
int stringCount = is.readInt32();
2032
int styleCount = is.readInt32();
2133
int flags = is.readInt32();

jadx-core/src/main/java/jadx/core/xmlgen/ResTableBinaryParser.java

+79-74
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package jadx.core.xmlgen;
22

33
import java.io.BufferedInputStream;
4+
import java.io.ByteArrayInputStream;
45
import java.io.IOException;
56
import java.io.InputStream;
67
import java.util.ArrayList;
@@ -102,41 +103,52 @@ public ResContainer decodeFiles() {
102103
void decodeTableChunk() throws IOException {
103104
is.checkInt16(RES_TABLE_TYPE, "Not a table chunk");
104105
is.checkInt16(0x000c, "Unexpected table header size");
105-
/* int size = */
106-
is.readInt32();
106+
int size = is.readInt32();
107107
int pkgCount = is.readInt32();
108108

109-
strings = parseStringPool();
110-
for (int i = 0; i < pkgCount; i++) {
111-
parsePackage();
109+
int pkgNum = 0;
110+
while (is.getPos() < size) {
111+
long chuckStart = is.getPos();
112+
int type = is.readInt16();
113+
int headerSize = is.readInt16();
114+
long chunkSize = is.readUInt32();
115+
long chunkEnd = chuckStart + chunkSize;
116+
switch (type) {
117+
case RES_NULL_TYPE:
118+
// skip
119+
break;
120+
121+
case RES_STRING_POOL_TYPE:
122+
strings = parseStringPoolNoSize(chuckStart, chunkEnd);
123+
break;
124+
125+
case RES_TABLE_PACKAGE_TYPE:
126+
parsePackage(chuckStart, headerSize, chunkEnd);
127+
pkgNum++;
128+
break;
129+
}
130+
is.skipToPos(chunkEnd, "Skip to table chunk end");
131+
}
132+
if (pkgNum != pkgCount) {
133+
LOG.warn("Unexpected package chunks, read: {}, expected: {}", pkgNum, pkgCount);
112134
}
113135
}
114136

115-
private PackageChunk parsePackage() throws IOException {
116-
long start = is.getPos();
117-
is.checkInt16(RES_TABLE_PACKAGE_TYPE, "Not a table chunk");
118-
int headerSize = is.readInt16();
137+
private void parsePackage(long pkgChunkStart, int headerSize, long pkgChunkEnd) throws IOException {
119138
if (headerSize < 0x011c) {
120139
die("Package header size too small");
140+
return;
121141
}
122-
long size = is.readUInt32();
123-
long endPos = start + size;
124-
125142
int id = is.readInt32();
126143
String name = is.readString16Fixed(128);
127-
128-
long typeStringsOffset = start + is.readInt32();
129-
/* int lastPublicType = */
130-
is.readInt32();
131-
long keyStringsOffset = start + is.readInt32();
132-
/* int lastPublicKey = */
133-
is.readInt32();
134-
144+
long typeStringsOffset = pkgChunkStart + is.readInt32();
145+
int lastPublicType = is.readInt32();
146+
long keyStringsOffset = pkgChunkStart + is.readInt32();
147+
int lastPublicKey = is.readInt32();
135148
if (headerSize >= 0x0120) {
136-
/* int typeIdOffset = */
137-
is.readInt32();
149+
int typeIdOffset = is.readInt32();
138150
}
139-
is.skipToPos(start + headerSize, "package header end");
151+
is.skipToPos(pkgChunkStart + headerSize, "package header end");
140152

141153
BinaryXMLStrings typeStrings = null;
142154
if (typeStringsOffset != 0) {
@@ -152,7 +164,7 @@ private PackageChunk parsePackage() throws IOException {
152164
PackageChunk pkg = new PackageChunk(id, name, typeStrings, keyStrings);
153165
resStorage.setAppPackage(name);
154166

155-
while (is.getPos() < endPos) {
167+
while (is.getPos() < pkgChunkEnd) {
156168
long chunkStart = is.getPos();
157169
int type = is.readInt16();
158170
LOG.trace("res package chunk start at {} type {}", chunkStart, type);
@@ -182,7 +194,6 @@ private PackageChunk parsePackage() throws IOException {
182194
LOG.warn("Unknown chunk type {} encountered at offset {}", type, chunkStart);
183195
}
184196
}
185-
return pkg;
186197
}
187198

188199
@SuppressWarnings("unused")
@@ -493,69 +504,59 @@ private RawValue parseValue() throws IOException {
493504
private EntryConfig parseConfig() throws IOException {
494505
long start = is.getPos();
495506
int size = is.readInt32();
496-
if (size < 28) {
497-
throw new IOException("Config size < 28");
507+
if (size < 4) {
508+
throw new IOException("Config size < 4");
498509
}
499510

500-
short mcc = (short) is.readInt16();
501-
short mnc = (short) is.readInt16();
511+
// Android zero fill this structure and only read the data present
512+
var configData = new byte[Math.max(52, size - 4)];
513+
is.readFully(configData, 0, size - 4);
514+
var configIs = new ParserStream(new ByteArrayInputStream(configData));
502515

503-
char[] language = unpackLocaleOrRegion((byte) is.readInt8(), (byte) is.readInt8(), 'a');
504-
char[] country = unpackLocaleOrRegion((byte) is.readInt8(), (byte) is.readInt8(), '0');
516+
short mcc = (short) configIs.readInt16();
517+
short mnc = (short) configIs.readInt16();
505518

506-
byte orientation = (byte) is.readInt8();
507-
byte touchscreen = (byte) is.readInt8();
508-
int density = is.readInt16();
519+
char[] language = unpackLocaleOrRegion((byte) configIs.readInt8(), (byte) configIs.readInt8(), 'a');
520+
char[] country = unpackLocaleOrRegion((byte) configIs.readInt8(), (byte) configIs.readInt8(), '0');
509521

510-
byte keyboard = (byte) is.readInt8();
511-
byte navigation = (byte) is.readInt8();
512-
byte inputFlags = (byte) is.readInt8();
513-
byte grammaticalInflection = (byte) is.readInt8();
522+
byte orientation = (byte) configIs.readInt8();
523+
byte touchscreen = (byte) configIs.readInt8();
524+
int density = configIs.readInt16();
514525

515-
short screenWidth = (short) is.readInt16();
516-
short screenHeight = (short) is.readInt16();
526+
byte keyboard = (byte) configIs.readInt8();
527+
byte navigation = (byte) configIs.readInt8();
528+
byte inputFlags = (byte) configIs.readInt8();
529+
byte grammaticalInflection = (byte) configIs.readInt8();
517530

518-
short sdkVersion = (short) is.readInt16();
519-
is.readInt16(); // minorVersion must always be 0
531+
short screenWidth = (short) configIs.readInt16();
532+
short screenHeight = (short) configIs.readInt16();
520533

521-
byte screenLayout = 0;
522-
byte uiMode = 0;
523-
short smallestScreenWidthDp = 0;
524-
if (size >= 32) {
525-
screenLayout = (byte) is.readInt8();
526-
uiMode = (byte) is.readInt8();
527-
smallestScreenWidthDp = (short) is.readInt16();
528-
}
534+
short sdkVersion = (short) configIs.readInt16();
535+
configIs.readInt16(); // minorVersion must always be 0
529536

530-
short screenWidthDp = 0;
531-
short screenHeightDp = 0;
532-
if (size >= 36) {
533-
screenWidthDp = (short) is.readInt16();
534-
screenHeightDp = (short) is.readInt16();
535-
}
537+
byte screenLayout = (byte) configIs.readInt8();
538+
byte uiMode = (byte) configIs.readInt8();
539+
short smallestScreenWidthDp = (short) configIs.readInt16();
540+
short screenWidthDp = (short) configIs.readInt16();
541+
short screenHeightDp = (short) configIs.readInt16();
536542

537-
char[] localeScript = null;
538-
char[] localeVariant = null;
539-
if (size >= 48) {
540-
localeScript = readScriptOrVariantChar(4).toCharArray();
541-
localeVariant = readScriptOrVariantChar(8).toCharArray();
542-
}
543+
char[] localeScript = readScriptOrVariantChar(4, configIs).toCharArray();
544+
char[] localeVariant = readScriptOrVariantChar(8, configIs).toCharArray();
543545

544-
byte screenLayout2 = 0;
545-
byte colorMode = 0;
546-
if (size >= 52) {
547-
screenLayout2 = (byte) is.readInt8();
548-
colorMode = (byte) is.readInt8();
549-
is.readInt16(); // reserved padding
550-
}
546+
byte screenLayout2 = (byte) configIs.readInt8();
547+
byte colorMode = (byte) configIs.readInt8();
548+
configIs.readInt16(); // reserved padding
551549

552-
is.skipToPos(start + size, "Config skip trailing bytes");
550+
is.checkPos(start + size, "Config skip trailing bytes");
553551

554552
return new EntryConfig(mcc, mnc, language, country,
555553
orientation, touchscreen, density, keyboard, navigation,
556554
inputFlags, grammaticalInflection, screenWidth, screenHeight, sdkVersion,
557555
screenLayout, uiMode, smallestScreenWidthDp, screenWidthDp,
558-
screenHeightDp, localeScript, localeVariant, screenLayout2,
556+
screenHeightDp,
557+
localeScript.length == 0 ? null : localeScript,
558+
localeVariant.length == 0 ? null : localeVariant,
559+
screenLayout2,
559560
colorMode, false, size);
560561
}
561562

@@ -574,16 +575,20 @@ private char[] unpackLocaleOrRegion(byte in0, byte in1, char base) {
574575
}
575576

576577
private String readScriptOrVariantChar(int length) throws IOException {
577-
long start = is.getPos();
578+
return readScriptOrVariantChar(length, is);
579+
}
580+
581+
private static String readScriptOrVariantChar(int length, ParserStream ps) throws IOException {
582+
long start = ps.getPos();
578583
StringBuilder sb = new StringBuilder(16);
579584
for (int i = 0; i < length; i++) {
580-
short ch = (short) is.readInt8();
585+
short ch = (short) ps.readInt8();
581586
if (ch == 0) {
582587
break;
583588
}
584589
sb.append((char) ch);
585590
}
586-
is.skipToPos(start + length, "readScriptOrVariantChar");
591+
ps.skipToPos(start + length, "readScriptOrVariantChar");
587592
return sb.toString();
588593
}
589594

0 commit comments

Comments
 (0)