fix(res): parse strings only when they are referenced (#1926)(PR #1928)

* Parse strings only when they are referenced

* Fix style
This commit is contained in:
Midori Kochiya
2023-06-29 04:29:46 +08:00
committed by GitHub
parent 4467f9118f
commit 14265a7b70
8 changed files with 149 additions and 93 deletions
@@ -45,7 +45,7 @@ public class BinaryXMLParser extends CommonBinaryParser {
private final Map<String, String> 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);
}
@@ -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<Integer, String> 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;
}
}
@@ -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 {
@@ -9,5 +9,5 @@ public interface IResParser {
ResourceStorage getResStorage();
String[] getStrings();
BinaryXMLStrings getStrings();
}
@@ -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<ResContainer> 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();
}
}
@@ -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<String> 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;
}
}
@@ -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<Integer, String> androidResMap;
private final String[] strings;
private final BinaryXMLStrings strings;
private final Map<Integer, String> resMap;
public ValuesParser(String[] strings, Map<Integer, String> resMap) {
public ValuesParser(BinaryXMLStrings strings, Map<Integer, String> 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:
@@ -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<ResContainer> 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<ResContainer> files = resXmlGen.makeResourcesXml();