From 14265a7b706fc0c0a9f52b52405418e1b6557a36 Mon Sep 17 00:00:00 2001 From: Midori Kochiya Date: Thu, 29 Jun 2023 04:29:46 +0800 Subject: [PATCH] fix(res): parse strings only when they are referenced (#1926)(PR #1928) * Parse strings only when they are referenced * Fix style --- .../jadx/core/xmlgen/BinaryXMLParser.java | 6 +- .../jadx/core/xmlgen/BinaryXMLStrings.java | 104 ++++++++++++++++++ .../jadx/core/xmlgen/CommonBinaryParser.java | 73 ++---------- .../java/jadx/core/xmlgen/IResParser.java | 2 +- .../java/jadx/core/xmlgen/ResProtoParser.java | 6 +- .../java/jadx/core/xmlgen/ResTableParser.java | 36 +++--- .../jadx/core/xmlgen/entry/ValuesParser.java | 7 +- .../java/jadx/core/xmlgen/ResXmlGenTest.java | 8 +- 8 files changed, 149 insertions(+), 93 deletions(-) create mode 100644 jadx-core/src/main/java/jadx/core/xmlgen/BinaryXMLStrings.java diff --git a/jadx-core/src/main/java/jadx/core/xmlgen/BinaryXMLParser.java b/jadx-core/src/main/java/jadx/core/xmlgen/BinaryXMLParser.java index 1031994f9..c7231189e 100644 --- a/jadx-core/src/main/java/jadx/core/xmlgen/BinaryXMLParser.java +++ b/jadx-core/src/main/java/jadx/core/xmlgen/BinaryXMLParser.java @@ -45,7 +45,7 @@ public class BinaryXMLParser extends CommonBinaryParser { private final Map tagAttrDeobfNames = new HashMap<>(); private ICodeWriter writer; - private String[] strings; + private BinaryXMLStrings strings; private String currentTag = "ERROR"; private boolean firstElement; private ValuesParser valuesParser; @@ -387,8 +387,8 @@ public class BinaryXMLParser extends CommonBinaryParser { } private String getString(int strId) { - if (0 <= strId && strId < strings.length) { - return strings[strId]; + if (0 <= strId && strId < strings.size()) { + return strings.get(strId); } return "NOT_FOUND_STR_0x" + Integer.toHexString(strId); } diff --git a/jadx-core/src/main/java/jadx/core/xmlgen/BinaryXMLStrings.java b/jadx-core/src/main/java/jadx/core/xmlgen/BinaryXMLStrings.java new file mode 100644 index 000000000..7b9effe45 --- /dev/null +++ b/jadx-core/src/main/java/jadx/core/xmlgen/BinaryXMLStrings.java @@ -0,0 +1,104 @@ +package jadx.core.xmlgen; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +public class BinaryXMLStrings { + private final int stringCount; + + private final long stringsStart; + + private final ByteBuffer buffer; + + private final boolean isUtf8; + + // This cache include strings that have been overridden by the deobfuscator. + private final Map cache = new HashMap<>(); + + public BinaryXMLStrings() { + stringCount = 0; + stringsStart = 0; + buffer = ByteBuffer.allocate(0); + buffer.order(ByteOrder.LITTLE_ENDIAN); + isUtf8 = false; + } + + public BinaryXMLStrings(int stringCount, long stringsStart, byte[] buffer, boolean isUtf8) { + this.stringCount = stringCount; + this.stringsStart = stringsStart; + this.buffer = ByteBuffer.wrap(buffer); + this.buffer.order(ByteOrder.LITTLE_ENDIAN); + this.isUtf8 = isUtf8; + } + + public String get(int id) { + String cached = cache.get(id); + if (cached != null) { + return cached; + } + + long offset = stringsStart + buffer.getInt(id * 4); + String extracted; + if (isUtf8) { + extracted = extractString8(this.buffer.array(), (int) offset); + } else { + // don't trust specified string length, read until \0 + // stringsOffset can be same for different indexes + extracted = extractString16(this.buffer.array(), (int) offset); + } + cache.put(id, extracted); + return extracted; + } + + public void put(int id, String content) { + cache.put(id, content); + } + + public int size() { + return this.stringCount; + } + + private static String extractString8(byte[] strArray, int offset) { + if (offset >= strArray.length) { + return "STRING_DECODE_ERROR"; + } + int start = offset + skipStrLen8(strArray, offset); + int len = strArray[start++]; + if (len == 0) { + return ""; + } + if ((len & 0x80) != 0) { + len = (len & 0x7F) << 8 | strArray[start++] & 0xFF; + } + byte[] arr = Arrays.copyOfRange(strArray, start, start + len); + return new String(arr, ParserStream.STRING_CHARSET_UTF8); + } + + private static String extractString16(byte[] strArray, int offset) { + int len = strArray.length; + int start = offset + skipStrLen16(strArray, offset); + int end = start; + while (true) { + if (end + 1 >= len) { + break; + } + if (strArray[end] == 0 && strArray[end + 1] == 0) { + break; + } + end += 2; + } + byte[] arr = Arrays.copyOfRange(strArray, start, end); + return new String(arr, ParserStream.STRING_CHARSET_UTF16); + } + + private static int skipStrLen8(byte[] strArray, int offset) { + return (strArray[offset] & 0x80) == 0 ? 1 : 2; + } + + private static int skipStrLen16(byte[] strArray, int offset) { + return (strArray[offset + 1] & 0x80) == 0 ? 2 : 4; + } +} 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 41d9ad197..0eebf5b9e 100644 --- a/jadx-core/src/main/java/jadx/core/xmlgen/CommonBinaryParser.java +++ b/jadx-core/src/main/java/jadx/core/xmlgen/CommonBinaryParser.java @@ -1,17 +1,16 @@ package jadx.core.xmlgen; import java.io.IOException; -import java.util.Arrays; public class CommonBinaryParser extends ParserConstants { protected ParserStream is; - protected String[] parseStringPool() throws IOException { + protected BinaryXMLStrings parseStringPool() throws IOException { is.checkInt16(RES_STRING_POOL_TYPE, "String pool expected"); return parseStringPoolNoType(); } - protected String[] parseStringPoolNoType() throws IOException { + protected BinaryXMLStrings parseStringPoolNoType() throws IOException { long start = is.getPos() - 2; is.checkInt16(0x001c, "String pool header size not 0x001c"); long size = is.readUInt32(); @@ -23,68 +22,16 @@ public class CommonBinaryParser extends ParserConstants { long stringsStart = is.readInt32(); long stylesStart = is.readInt32(); - int[] stringsOffset = is.readInt32Array(stringCount); - int[] stylesOffset = is.readInt32Array(styleCount); - - is.skipToPos(start + stringsStart, "Expected strings start"); - String[] strings = new String[stringCount]; - byte[] strData = is.readInt8Array((int) (chunkEnd - is.getPos())); - if ((flags & UTF8_FLAG) != 0) { - // UTF-8 - for (int i = 0; i < stringCount; i++) { - strings[i] = extractString8(strData, stringsOffset[i]); - } - } else { - // UTF-16 - for (int i = 0; i < stringCount; i++) { - // don't trust specified string length, read until \0 - // stringsOffset can be same for different indexes - strings[i] = extractString16(strData, stringsOffset[i]); - } - } + // Correct the offset of actual strings, as the header is already read. + stringsStart = stringsStart - (is.getPos() - start); + byte[] buffer = is.readInt8Array((int) (chunkEnd - is.getPos())); is.checkPos(chunkEnd, "Expected strings pool end"); - return strings; - } - private static String extractString8(byte[] strArray, int offset) { - if (offset >= strArray.length) { - return "STRING_DECODE_ERROR"; - } - int start = offset + skipStrLen8(strArray, offset); - int len = strArray[start++]; - if (len == 0) { - return ""; - } - if ((len & 0x80) != 0) { - len = (len & 0x7F) << 8 | strArray[start++] & 0xFF; - } - byte[] arr = Arrays.copyOfRange(strArray, start, start + len); - return new String(arr, ParserStream.STRING_CHARSET_UTF8); - } - - private static String extractString16(byte[] strArray, int offset) { - int len = strArray.length; - int start = offset + skipStrLen16(strArray, offset); - int end = start; - while (true) { - if (end + 1 >= len) { - break; - } - if (strArray[end] == 0 && strArray[end + 1] == 0) { - break; - } - end += 2; - } - byte[] arr = Arrays.copyOfRange(strArray, start, end); - return new String(arr, ParserStream.STRING_CHARSET_UTF16); - } - - private static int skipStrLen8(byte[] strArray, int offset) { - return (strArray[offset] & 0x80) == 0 ? 1 : 2; - } - - private static int skipStrLen16(byte[] strArray, int offset) { - return (strArray[offset + 1] & 0x80) == 0 ? 2 : 4; + return new BinaryXMLStrings( + stringCount, + stringsStart, + buffer, + (flags & UTF8_FLAG) != 0); } protected void die(String message) throws IOException { diff --git a/jadx-core/src/main/java/jadx/core/xmlgen/IResParser.java b/jadx-core/src/main/java/jadx/core/xmlgen/IResParser.java index 5c890c15f..32301ec03 100644 --- a/jadx-core/src/main/java/jadx/core/xmlgen/IResParser.java +++ b/jadx-core/src/main/java/jadx/core/xmlgen/IResParser.java @@ -9,5 +9,5 @@ public interface IResParser { ResourceStorage getResStorage(); - String[] getStrings(); + BinaryXMLStrings getStrings(); } diff --git a/jadx-core/src/main/java/jadx/core/xmlgen/ResProtoParser.java b/jadx-core/src/main/java/jadx/core/xmlgen/ResProtoParser.java index 1a6aa569e..54286147d 100644 --- a/jadx-core/src/main/java/jadx/core/xmlgen/ResProtoParser.java +++ b/jadx-core/src/main/java/jadx/core/xmlgen/ResProtoParser.java @@ -39,7 +39,7 @@ public class ResProtoParser implements IResParser { public ResContainer decodeFiles(InputStream inputStream) throws IOException { decode(inputStream); - ValuesParser vp = new ValuesParser(new String[0], resStorage.getResourcesNames()); + ValuesParser vp = new ValuesParser(new BinaryXMLStrings(), resStorage.getResourcesNames()); ResXmlGen resGen = new ResXmlGen(resStorage, vp); ICodeInfo content = XmlGenUtils.makeXmlDump(root.makeCodeWriter(), resStorage); List xmlFiles = resGen.makeResourcesXml(); @@ -252,7 +252,7 @@ public class ResProtoParser implements IResParser { } @Override - public String[] getStrings() { - return new String[0]; + public BinaryXMLStrings getStrings() { + return new BinaryXMLStrings(); } } 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 751ae6588..7cace16d1 100644 --- a/jadx-core/src/main/java/jadx/core/xmlgen/ResTableParser.java +++ b/jadx-core/src/main/java/jadx/core/xmlgen/ResTableParser.java @@ -38,10 +38,10 @@ public class ResTableParser extends CommonBinaryParser implements IResParser { private static final class PackageChunk { private final int id; private final String name; - private final String[] typeStrings; - private final String[] keyStrings; + private final BinaryXMLStrings typeStrings; + private final BinaryXMLStrings keyStrings; - private PackageChunk(int id, String name, String[] typeStrings, String[] keyStrings) { + private PackageChunk(int id, String name, BinaryXMLStrings typeStrings, BinaryXMLStrings keyStrings) { this.id = id; this.name = name; this.typeStrings = typeStrings; @@ -56,11 +56,11 @@ public class ResTableParser extends CommonBinaryParser implements IResParser { return name; } - public String[] getTypeStrings() { + public BinaryXMLStrings getTypeStrings() { return typeStrings; } - public String[] getKeyStrings() { + public BinaryXMLStrings getKeyStrings() { return keyStrings; } } @@ -71,7 +71,7 @@ public class ResTableParser extends CommonBinaryParser implements IResParser { private final boolean useRawResName; private final RootNode root; private final ResourceStorage resStorage = new ResourceStorage(); - private String[] strings; + private BinaryXMLStrings strings; public ResTableParser(RootNode root) { this(root, false); @@ -137,12 +137,12 @@ public class ResTableParser extends CommonBinaryParser implements IResParser { is.readInt32(); } - String[] typeStrings = null; + BinaryXMLStrings typeStrings = null; if (typeStringsOffset != 0) { is.skipToPos(typeStringsOffset, "Expected typeStrings string pool"); typeStrings = parseStringPool(); } - String[] keyStrings = null; + BinaryXMLStrings keyStrings = null; if (keyStringsOffset != 0) { is.skipToPos(keyStringsOffset, "Expected keyStrings string pool"); keyStrings = parseStringPool(); @@ -185,23 +185,23 @@ public class ResTableParser extends CommonBinaryParser implements IResParser { return pkg; } - private void deobfKeyStrings(String[] keyStrings) { - int keysCount = keyStrings.length; + private void deobfKeyStrings(BinaryXMLStrings keyStrings) { + int keysCount = keyStrings.size(); if (root.getArgs().isRenamePrintable()) { for (int i = 0; i < keysCount; i++) { - String keyString = keyStrings[i]; + String keyString = keyStrings.get(i); if (!NameMapper.isAllCharsPrintable(keyString)) { - keyStrings[i] = makeNewKeyName(i); + keyStrings.put(i, makeNewKeyName(i)); } } } if (root.getArgs().isRenameValid()) { Set keySet = new HashSet<>(keysCount); for (int i = 0; i < keysCount; i++) { - String keyString = keyStrings[i]; + String keyString = keyStrings.get(i); boolean isNew = keySet.add(keyString); if (!isNew) { - keyStrings[i] = makeNewKeyName(i); + keyStrings.put(i, makeNewKeyName(i)); } } } @@ -274,7 +274,7 @@ public class ResTableParser extends CommonBinaryParser implements IResParser { EntryConfig config = parseConfig(); if (config.isInvalid) { - String typeName = pkg.getTypeStrings()[id - 1]; + String typeName = pkg.getTypeStrings().get(id - 1); LOG.warn("Invalid config flags detected: {}{}", typeName, config.getQualifiers()); } @@ -351,8 +351,8 @@ public class ResTableParser extends CommonBinaryParser implements IResParser { } int resRef = pkg.getId() << 24 | typeId << 16 | entryId; - String typeName = pkg.getTypeStrings()[typeId - 1]; - String origKeyName = pkg.getKeyStrings()[key]; + String typeName = pkg.getTypeStrings().get(typeId - 1); + String origKeyName = pkg.getKeyStrings().get(key); ResourceEntry newResEntry = new ResourceEntry(resRef, pkg.getName(), typeName, getResName(typeName, resRef, origKeyName), config); ResourceEntry prevResEntry = resStorage.searchEntryWithSameName(newResEntry); if (prevResEntry != null) { @@ -568,7 +568,7 @@ public class ResTableParser extends CommonBinaryParser implements IResParser { } @Override - public String[] getStrings() { + public BinaryXMLStrings getStrings() { return strings; } } diff --git a/jadx-core/src/main/java/jadx/core/xmlgen/entry/ValuesParser.java b/jadx-core/src/main/java/jadx/core/xmlgen/entry/ValuesParser.java index 9561e8779..b30f3dfff 100644 --- a/jadx-core/src/main/java/jadx/core/xmlgen/entry/ValuesParser.java +++ b/jadx-core/src/main/java/jadx/core/xmlgen/entry/ValuesParser.java @@ -11,6 +11,7 @@ import org.slf4j.LoggerFactory; import jadx.core.utils.android.TextResMapFile; import jadx.core.utils.exceptions.JadxRuntimeException; +import jadx.core.xmlgen.BinaryXMLStrings; import jadx.core.xmlgen.ParserConstants; import jadx.core.xmlgen.XmlGenUtils; @@ -19,10 +20,10 @@ public class ValuesParser extends ParserConstants { private static Map androidResMap; - private final String[] strings; + private final BinaryXMLStrings strings; private final Map resMap; - public ValuesParser(String[] strings, Map resMap) { + public ValuesParser(BinaryXMLStrings strings, Map resMap) { this.strings = strings; this.resMap = resMap; getAndroidResMap(); @@ -105,7 +106,7 @@ public class ValuesParser extends ParserConstants { case TYPE_NULL: return null; case TYPE_STRING: - return strings[data]; + return strings.get(data); case TYPE_INT_DEC: return Integer.toString(data); case TYPE_INT_HEX: diff --git a/jadx-core/src/test/java/jadx/core/xmlgen/ResXmlGenTest.java b/jadx-core/src/test/java/jadx/core/xmlgen/ResXmlGenTest.java index 3c44f88c1..e0a11366b 100644 --- a/jadx-core/src/test/java/jadx/core/xmlgen/ResXmlGenTest.java +++ b/jadx-core/src/test/java/jadx/core/xmlgen/ResXmlGenTest.java @@ -135,7 +135,9 @@ class ResXmlGenTest { re.setNamedValues(Lists.list()); resStorage.add(re); - ValuesParser vp = new ValuesParser(new String[] { "Jadx Decompiler App" }, resStorage.getResourcesNames()); + BinaryXMLStrings strings = new BinaryXMLStrings(); + strings.put(0, "Jadx Decompiler App"); + ValuesParser vp = new ValuesParser(strings, resStorage.getResourcesNames()); ResXmlGen resXmlGen = new ResXmlGen(resStorage, vp); List files = resXmlGen.makeResourcesXml(); @@ -155,7 +157,9 @@ class ResXmlGenTest { Lists.list(new RawNamedValue(16777216, new RawValue(3, 0)))); resStorage.add(re); - ValuesParser vp = new ValuesParser(new String[] { "Let's go" }, resStorage.getResourcesNames()); + BinaryXMLStrings strings = new BinaryXMLStrings(); + strings.put(0, "Let's go"); + ValuesParser vp = new ValuesParser(strings, resStorage.getResourcesNames()); ResXmlGen resXmlGen = new ResXmlGen(resStorage, vp); List files = resXmlGen.makeResourcesXml();