From 77892f41ec29ff6b0cc579584cf1e334e389c3a8 Mon Sep 17 00:00:00 2001 From: Jan S Date: Thu, 23 Mar 2023 18:30:29 +0100 Subject: [PATCH] fix(res): parsing of sparse RES_TABLE_TYPE_TYPE and RES_TABLE_TYPE_STAGED_ALIAS chunks (#1806 #1803)(PR #1807) --- .../java/jadx/core/xmlgen/ResTableParser.java | 70 +++++++++++++++---- 1 file changed, 55 insertions(+), 15 deletions(-) diff --git a/jadx-core/src/main/java/jadx/core/xmlgen/ResTableParser.java b/jadx-core/src/main/java/jadx/core/xmlgen/ResTableParser.java index 6a7adb87c..751ae6588 100644 --- a/jadx-core/src/main/java/jadx/core/xmlgen/ResTableParser.java +++ b/jadx-core/src/main/java/jadx/core/xmlgen/ResTableParser.java @@ -4,7 +4,9 @@ import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.HashSet; +import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.regex.Matcher; @@ -174,8 +176,8 @@ public class ResTableParser extends CommonBinaryParser implements IResParser { throw new IOException( String.format("Encountered unsupported chunk type RES_TABLE_TYPE_OVERLAY_POLICY at offset 0x%x ", chunkStart)); case RES_TABLE_TYPE_STAGED_ALIAS: // 0x0206 - throw new IOException( - String.format("Encountered unsupported chunk type TYPE_STAGED_ALIAS at offset 0x%x ", chunkStart)); + parseStagedAliasChunk(chunkStart); + break; default: LOG.warn("Unknown chunk type {} encountered at offset {}", type, chunkStart); } @@ -245,6 +247,13 @@ public class ResTableParser extends CommonBinaryParser implements IResParser { } } + /** + * Parse an ResTable_type (except for the 2 bytes uint16_t + * from ResChunk_header). + * + * @see ResourceTypes.h + */ private void parseTypeChunk(long start, PackageChunk pkg) throws IOException { /* int headerSize = */ is.readInt16(); @@ -252,9 +261,13 @@ public class ResTableParser extends CommonBinaryParser implements IResParser { long chunkSize = is.readUInt32(); long chunkEnd = start + chunkSize; + // The type identifier this chunk is holding. Type IDs start at 1 (corresponding + // to the value of the type bits in a resource identifier). 0 is invalid. int id = is.readInt8(); - is.checkInt8(0, "type chunk, res0"); - is.checkInt16(0, "type chunk, res1"); + int flags = is.readInt8(); // 0 or 1 + boolean flagSparse = (flags == 1); + + is.checkInt16(0, "type chunk, reserved"); int entryCount = is.readInt32(); long entriesStart = start + is.readInt32(); @@ -265,21 +278,30 @@ public class ResTableParser extends CommonBinaryParser implements IResParser { LOG.warn("Invalid config flags detected: {}{}", typeName, config.getQualifiers()); } - int[] entryIndexes = new int[entryCount]; - for (int i = 0; i < entryCount; i++) { - entryIndexes[i] = is.readInt32(); + Map entryOffsetMap = new LinkedHashMap<>(entryCount); + if (flagSparse) { + for (int i = 0; i < entryCount; i++) { + entryOffsetMap.put(is.readInt16(), is.readInt16()); + } + } else { + for (int i = 0; i < entryCount; i++) { + entryOffsetMap.put(i, is.readInt32()); + } } is.checkPos(entriesStart, "Expected entry start"); - for (int i = 0; i < entryCount; i++) { - if (entryIndexes[i] != NO_ENTRY) { + int processed = 0; + for (int index : entryOffsetMap.keySet()) { + int offset = entryOffsetMap.get(index); + if (offset != NO_ENTRY) { if (is.getPos() >= chunkEnd) { // Certain resource obfuscated apps like com.facebook.orca have more entries defined // than actually fit into the chunk size -> ignore the remaining entries - LOG.warn("End of chunk reached - ignoring remaining {} entries", entryCount - i); + LOG.warn("End of chunk reached - ignoring remaining {} entries", entryCount - processed); break; } - parseEntry(pkg, id, i, config.getQualifiers()); + parseEntry(pkg, id, index, config.getQualifiers()); } + processed++; } if (chunkEnd > is.getPos()) { // Skip remaining unknown data in this chunk (e.g. type 8 entries") @@ -292,16 +314,34 @@ public class ResTableParser extends CommonBinaryParser implements IResParser { private void parseOverlayTypeChunk(long chunkStart) throws IOException { LOG.trace("parsing overlay type chunk starting at offset {}", chunkStart); - int headerSize = is.readInt16(); // usually 1032 bytes + // read ResTable_overlayable_header + /* headerSize = */ is.readInt16(); // usually 1032 bytes int chunkSize = is.readInt32(); // e.g. 1056 bytes long expectedEndPos = chunkStart + chunkSize; - String name = is.readString16Fixed(128); // 256 bytes - String actor = is.readString16Fixed(128); // 256 bytes + String name = is.readString16Fixed(256); // 512 bytes + String actor = is.readString16Fixed(256); // 512 bytes LOG.trace("Overlay header data: name={} actor={}", name, actor); - // the other data in the chunk header and body is unknown + // skip: ResTable_overlayable_policy_header + ResTable_ref * x is.skipToPos(expectedEndPos, "overlay chunk end"); } + private void parseStagedAliasChunk(long chunkStart) throws IOException { + // read ResTable_staged_alias_header + LOG.trace("parsing staged alias chunk starting at offset {}", chunkStart); + /* headerSize = */ is.readInt16(); + int chunkSize = is.readInt32(); + long expectedEndPos = chunkStart + chunkSize; + int count = is.readInt32(); + + for (int i = 0; i < count; i++) { + // read ResTable_staged_alias_entry + int stagedResId = is.readInt32(); + int finalizedResId = is.readInt32(); + LOG.debug("Staged alias: stagedResId {} finalizedResId {}", stagedResId, finalizedResId); + } + is.skipToPos(expectedEndPos, "staged alias chunk end"); + } + private void parseEntry(PackageChunk pkg, int typeId, int entryId, String config) throws IOException { int size = is.readInt16(); int flags = is.readInt16();