From 959b6de063a59849b2d1c5a1a78f8db29c40b1f0 Mon Sep 17 00:00:00 2001 From: Connor Tumbleson Date: Fri, 6 Oct 2023 07:22:02 -0400 Subject: [PATCH] fix: support COMPACT/OFFSET16 packed resources. (#3372) * fix: support COMPACT/OFFSET16 * fix: properly read specNamesId from compact resources * fix: properly read OFFSET16 in entries * test: add assertions for compact/offset16 sample * refactor: extract flags out of private functions --- .../androlib/res/decoder/ARSCDecoder.java | 58 +++++++++------ .../src/test/java/brut/androlib/BaseTest.java | 12 ++++ .../androlib/decode/CompactResourceTest.java | 67 ++++++++++++++++++ .../resources/decode/issue3366/issue3366.apk | Bin 0 -> 39067 bytes 4 files changed, 117 insertions(+), 20 deletions(-) create mode 100644 brut.apktool/apktool-lib/src/test/java/brut/androlib/decode/CompactResourceTest.java create mode 100644 brut.apktool/apktool-lib/src/test/resources/decode/issue3366/issue3366.apk diff --git a/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/decoder/ARSCDecoder.java b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/decoder/ARSCDecoder.java index a92de989..75d2fe62 100644 --- a/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/decoder/ARSCDecoder.java +++ b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/decoder/ARSCDecoder.java @@ -280,21 +280,21 @@ public class ARSCDecoder { mHeader.checkForUnreadHeader(mIn); + boolean isOffset16 = (typeFlags & TABLE_TYPE_FLAG_OFFSET16) != 0; + boolean isSparse = (typeFlags & TABLE_TYPE_FLAG_SPARSE) != 0; + // Be sure we don't poison mResTable by marking the application as sparse // Only flag the ResTable as sparse if the main package is not loaded. - if ((typeFlags & TABLE_TYPE_FLAG_SPARSE) != 0 && !mResTable.isMainPkgLoaded()) { + if (isSparse && !mResTable.isMainPkgLoaded()) { mResTable.setSparseResources(true); } - if ((typeFlags & TABLE_TYPE_FLAG_OFFSET16) != 0) { - LOGGER.warning("Please report this application to Apktool for a fix: https://github.com/iBotPeaches/Apktool/issues/3367"); - throw new AndrolibException("Unexpected TYPE_FLAG_OFFSET16"); - } - HashMap entryOffsetMap = new LinkedHashMap<>(); for (int i = 0; i < entryCount; i++) { - if ((typeFlags & TABLE_TYPE_FLAG_SPARSE) != 0) { + if (isSparse) { entryOffsetMap.put(mIn.readUnsignedShort(), mIn.readUnsignedShort()); + } else if (isOffset16) { + entryOffsetMap.put(i, mIn.readUnsignedShort()); } else { entryOffsetMap.put(i, mIn.readInt()); } @@ -310,11 +310,12 @@ public class ARSCDecoder { } mType = flags.isInvalid && !mKeepBroken ? null : mPkg.getOrCreateConfig(flags); + int noEntry = isOffset16 ? NO_ENTRY_OFFSET16 : NO_ENTRY; for (int i : entryOffsetMap.keySet()) { mResId = (mResId & 0xffff0000) | i; int offset = entryOffsetMap.get(i); - if (offset == NO_ENTRY) { + if (offset == noEntry) { mMissingResSpecMap.put(mResId, typeId); continue; } @@ -347,25 +348,35 @@ public class ARSCDecoder { private EntryData readEntryData() throws IOException, AndrolibException { short size = mIn.readShort(); - if (size < 0) { - throw new AndrolibException("Entry size is under 0 bytes."); - } - short flags = mIn.readShort(); - int specNamesId = mIn.readInt(); - if (specNamesId == NO_ENTRY) { - return null; - } boolean isComplex = (flags & ENTRY_FLAG_COMPLEX) != 0; boolean isCompact = (flags & ENTRY_FLAG_COMPACT) != 0; - if (isCompact) { - LOGGER.warning("Please report this application to Apktool for a fix: https://github.com/iBotPeaches/Apktool/issues/3366"); - throw new AndrolibException("Unexpected entry type: compact"); + if (size < 0 && !isCompact) { + throw new AndrolibException("Entry size is under 0 bytes and not compactly packed."); + } + + int specNamesId = mIn.readInt(); + if (specNamesId == NO_ENTRY && !isCompact) { + return null; + } + + // #3366 - In a compactly packed entry, the key index is the size & type is higher 8 bits on flags. + // We assume a size of 8 bytes for compact entries and the specNamesId is the data itself encoded. + ResValue value; + if (isCompact) { + byte type = (byte) ((flags >> 8) & 0xFF); + value = readCompactValue(type, specNamesId); + + // To keep code below happy - we know if compact that the size has the key index encoded. + specNamesId = size; + } else if (isComplex) { + value = readComplexEntry(); + } else { + value = readValue(); } - ResValue value = isComplex ? readComplexEntry() : readValue(); // #2824 - In some applications the res entries are duplicated with the 2nd being malformed. // AOSP skips this, so we will do the same. if (value == null) { @@ -443,6 +454,12 @@ public class ARSCDecoder { return factory.bagFactory(parent, items, mTypeSpec); } + private ResIntBasedValue readCompactValue(byte type, int data) throws AndrolibException { + return type == TypedValue.TYPE_STRING + ? mPkg.getValueFactory().factory(mTableStrings.getHTML(data), data) + : mPkg.getValueFactory().factory(type, data, null); + } + private ResIntBasedValue readValue() throws IOException, AndrolibException { int size = mIn.readShort(); if (size < 8) { @@ -686,6 +703,7 @@ public class ARSCDecoder { private static final int KNOWN_CONFIG_BYTES = 64; private static final int NO_ENTRY = 0xFFFFFFFF; + private static final int NO_ENTRY_OFFSET16 = 0xFFFF; private static final Logger LOGGER = Logger.getLogger(ARSCDecoder.class.getName()); } diff --git a/brut.apktool/apktool-lib/src/test/java/brut/androlib/BaseTest.java b/brut.apktool/apktool-lib/src/test/java/brut/androlib/BaseTest.java index acc8785c..12d3a351 100644 --- a/brut.apktool/apktool-lib/src/test/java/brut/androlib/BaseTest.java +++ b/brut.apktool/apktool-lib/src/test/java/brut/androlib/BaseTest.java @@ -22,6 +22,7 @@ import brut.directory.ExtFile; import brut.directory.FileDirectory; import org.custommonkey.xmlunit.*; import org.w3c.dom.Document; +import org.w3c.dom.Element; import org.xml.sax.SAXException; import javax.xml.parsers.DocumentBuilder; @@ -155,6 +156,17 @@ public class BaseTest { } } + protected static int getStringEntryCount(Document doc, String key) { + int count = 0; + Element resources = doc.getDocumentElement(); + for (int i = 0; i < resources.getChildNodes().getLength(); i++) { + if (resources.getChildNodes().item(i).getNodeName().equals(key)) { + count++; + } + } + return count; + } + protected static ExtFile sTmpDir; protected static ExtFile sTestOrigDir; protected static ExtFile sTestNewDir; diff --git a/brut.apktool/apktool-lib/src/test/java/brut/androlib/decode/CompactResourceTest.java b/brut.apktool/apktool-lib/src/test/java/brut/androlib/decode/CompactResourceTest.java new file mode 100644 index 00000000..5b4ad679 --- /dev/null +++ b/brut.apktool/apktool-lib/src/test/java/brut/androlib/decode/CompactResourceTest.java @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2010 Ryszard Wiśniewski + * Copyright (C) 2010 Connor Tumbleson + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package brut.androlib.decode; + +import brut.androlib.*; +import brut.directory.ExtFile; +import brut.common.BrutException; +import brut.util.OS; +import java.io.File; +import java.io.IOException; + +import org.junit.*; +import org.w3c.dom.Document; +import org.xml.sax.SAXException; + +import javax.xml.parsers.ParserConfigurationException; + +import static org.junit.Assert.*; + +public class CompactResourceTest extends BaseTest { + + @BeforeClass + public static void beforeClass() throws Exception { + TestUtils.cleanFrameworkFile(); + sTmpDir = new ExtFile(OS.createTempDirectory()); + TestUtils.copyResourceDir(CompactResourceTest.class, "decode/issue3366/", sTmpDir); + } + + @AfterClass + public static void afterClass() throws BrutException { + OS.rmdir(sTmpDir); + } + + @Test + public void checkIfDecodeSucceeds() throws BrutException, IOException, ParserConfigurationException, SAXException { + String apk = "issue3366.apk"; + File testApk = new File(sTmpDir, apk); + + // decode issue3366.apk + ApkDecoder apkDecoder = new ApkDecoder(testApk); + sTestOrigDir = new ExtFile(sTmpDir + File.separator + apk + ".out"); + + File outDir = new File(sTmpDir + File.separator + apk + ".out"); + apkDecoder.decode(outDir); + + Document doc = loadDocument(new File(sTestOrigDir + "/res/values/strings.xml")); + assertEquals(1002, getStringEntryCount(doc, "string")); + + Config config = Config.getDefaultConfig(); + LOGGER.info("Building duplicatedex.apk..."); + new ApkBuilder(config, sTestOrigDir).build(testApk); + } +} diff --git a/brut.apktool/apktool-lib/src/test/resources/decode/issue3366/issue3366.apk b/brut.apktool/apktool-lib/src/test/resources/decode/issue3366/issue3366.apk new file mode 100644 index 0000000000000000000000000000000000000000..0bccfb8b24b3459176351f830ed279c44e005a66 GIT binary patch literal 39067 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 C`}Cjy literal 0 HcmV?d00001