diff --git a/.dockerignore b/.dockerignore deleted file mode 100644 index 45cf84fd..00000000 --- a/.dockerignore +++ /dev/null @@ -1,3 +0,0 @@ -./docker -./github -*.md diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 3df731fd..34fc409f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -72,12 +72,12 @@ jobs: java: [ 8, 11, 17, 21 ] steps: - uses: actions/checkout@v4 - - uses: actions/setup-java@v3 + - uses: actions/setup-java@v4 with: distribution: 'zulu' java-version: ${{ matrix.java }} - name: Build and test - uses: gradle/gradle-build-action@v2.9.0 + uses: gradle/gradle-build-action@v2.10.0 with: arguments: build shadowJar proguard @@ -91,12 +91,12 @@ jobs: - uses: actions/checkout@v4 with: fetch-depth: 0 - - uses: actions/setup-java@v3 + - uses: actions/setup-java@v4 with: distribution: 'zulu' java-version: 17 - name: Build - uses: gradle/gradle-build-action@v2.9.0 + uses: gradle/gradle-build-action@v2.10.0 with: dependency-graph: generate-and-submit arguments: build shadowJar proguard diff --git a/.github/workflows/docker-beta.yml b/.github/workflows/docker-beta.yml deleted file mode 100644 index 665fb067..00000000 --- a/.github/workflows/docker-beta.yml +++ /dev/null @@ -1,46 +0,0 @@ -name: Build and push docker (beta) - -on: - push: - branches: - - master - -env: - REGISTRY: ghcr.io - IMAGE_NAME: ibotpeaches/apktool - -jobs: - - build_and_push: - name: Build and push - runs-on: ubuntu-latest - permissions: - contents: read - packages: write - steps: - - uses: actions/checkout@v4 - - - name: Log in to the Container registry - uses: docker/login-action@v3 - with: - registry: ghcr.io - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Get hash - id: hash - run: echo "APKTOOL_HASH=$(echo $GITHUB_SHA | cut -c 1-6)" >> $GITHUB_ENV - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - - name: Build and push Docker image - uses: docker/build-push-action@v5 - with: - context: . - platforms: linux/amd64 - file: ./docker/Dockerfile - push: true - tags: | - ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ env.APKTOOL_HASH }} - ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:snapshot diff --git a/.github/workflows/docker-release.yml b/.github/workflows/docker-release.yml deleted file mode 100644 index 179de780..00000000 --- a/.github/workflows/docker-release.yml +++ /dev/null @@ -1,48 +0,0 @@ -name: Build and push docker (release) - -on: - release: - types: [published] - -env: - REGISTRY: ghcr.io - IMAGE_NAME: ibotpeaches/apktool - -jobs: - build_and_push: - name: Build and push - runs-on: ubuntu-latest - permissions: - contents: read - packages: write - steps: - - uses: actions/checkout@v4 - - - name: Log in to the Container registry - uses: docker/login-action@v3 - with: - registry: ghcr.io - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Get hash - id: hash - run: echo "APKTOOL_HASH=$(echo $GITHUB_SHA | cut -c 1-6)" >> $GITHUB_ENV - - - name: Get release tag - id: tag - run: echo "APKTOOL_TAG=$(echo ${GITHUB_REF:-no-tag} |sed 's|refs/tags/||g')" >> $GITHUB_ENV - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - - name: Build and push Docker image - uses: docker/build-push-action@v5 - with: - context: . - platforms: linux/amd64 - file: ./docker/Dockerfile - push: true - tags: | - ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ env.APKTOOL_TAG }} - ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index 592a77cb..bb7aef32 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -17,7 +17,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - uses: actions/setup-java@v3 + - uses: actions/setup-java@v4 with: distribution: 'zulu' java-version: 17 diff --git a/README.md b/README.md index 7d26c9f8..b60a497a 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,6 @@ If you discover a security vulnerability within Apktool, please send an e-mail t - [Downloads Mirror](https://connortumbleson.com/apktool/) - [How to Build](https://ibotpeaches.github.io/Apktool/build/) - [Documentation](https://ibotpeaches.github.io/Apktool/documentation/) -- [Use in Docker](./docker/README.md) - [Bug Reports](https://github.com/iBotPeaches/Apktool/issues) - [Changelog/Information](https://ibotpeaches.github.io/Apktool/changes/) - [XDA Post](https://forum.xda-developers.com/t/util-dec-2-2020-apktool-tool-for-reverse-engineering-apk-files.1755243/) diff --git a/brut.apktool/apktool-lib/src/main/java/brut/androlib/AaptInvoker.java b/brut.apktool/apktool-lib/src/main/java/brut/androlib/AaptInvoker.java index 56d48098..b8385c66 100644 --- a/brut.apktool/apktool-lib/src/main/java/brut/androlib/AaptInvoker.java +++ b/brut.apktool/apktool-lib/src/main/java/brut/androlib/AaptInvoker.java @@ -183,6 +183,9 @@ public class AaptInvoker { // TODO: Add this back, once AAPT2 from platform-tools 34.0.4 is stable // cmd.add("--no-compile-sdk-metadata"); + // #3427 - Ignore stricter parsing during aapt2 + cmd.add("--warn-manifest-validation"); + if (mApkInfo.sparseResources) { cmd.add("--enable-sparse-encoding"); } diff --git a/brut.apktool/apktool-lib/src/main/java/brut/androlib/ApkBuilder.java b/brut.apktool/apktool-lib/src/main/java/brut/androlib/ApkBuilder.java index 39eecdb6..34febb7f 100644 --- a/brut.apktool/apktool-lib/src/main/java/brut/androlib/ApkBuilder.java +++ b/brut.apktool/apktool-lib/src/main/java/brut/androlib/ApkBuilder.java @@ -482,7 +482,7 @@ public class ApkBuilder { File inputFile; try { - inputFile = new File(unknownFileDir, BrutIO.sanitizeUnknownFile(unknownFileDir, unknownFileInfo.getKey())); + inputFile = new File(unknownFileDir, BrutIO.sanitizeFilepath(unknownFileDir, unknownFileInfo.getKey())); } catch (RootUnknownFileException | InvalidUnknownFileException | TraversalUnknownFileException exception) { LOGGER.warning(String.format("Skipping file %s (%s)", unknownFileInfo.getKey(), exception.getMessage())); continue; diff --git a/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/ResourcesDecoder.java b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/ResourcesDecoder.java index 4cfbd085..5ab890e6 100644 --- a/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/ResourcesDecoder.java +++ b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/ResourcesDecoder.java @@ -159,12 +159,12 @@ public class ResourcesDecoder { decoders.setDecoder("xml", new XmlPullStreamDecoder(axmlParser, getResXmlSerializer())); ResFileDecoder fileDecoder = new ResFileDecoder(decoders); - Directory in, out; + Directory in, out, outRes; try { out = new FileDirectory(outDir); in = mApkInfo.getApkFile().getDirectory(); - out = out.createDir("res"); + outRes = out.createDir("res"); } catch (DirectoryException ex) { throw new AndrolibException(ex); } @@ -174,14 +174,14 @@ public class ResourcesDecoder { LOGGER.info("Decoding file-resources..."); for (ResResource res : pkg.listFiles()) { - fileDecoder.decode(res, in, out, mResFileMapping); + fileDecoder.decode(res, in, outRes, mResFileMapping); } LOGGER.info("Decoding values */* XMLs..."); for (ResValuesFile valuesFile : pkg.listValuesFiles()) { - generateValuesFile(valuesFile, out, xmlSerializer); + generateValuesFile(valuesFile, outRes, xmlSerializer); } - generatePublicXml(pkg, out, xmlSerializer); + generatePublicXml(pkg, outRes, xmlSerializer); } AndrolibException decodeError = axmlParser.getFirstError(); diff --git a/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/data/value/ResValueFactory.java b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/data/value/ResValueFactory.java index 052621e0..1d20c79f 100644 --- a/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/data/value/ResValueFactory.java +++ b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/data/value/ResValueFactory.java @@ -32,9 +32,7 @@ public class ResValueFactory { public ResScalarValue factory(int type, int value, String rawValue) throws AndrolibException { switch (type) { case TypedValue.TYPE_NULL: - if (value == TypedValue.DATA_NULL_UNDEFINED) { // Special case $empty as explicitly defined empty value - return new ResStringValue(null, value); - } else if (value == TypedValue.DATA_NULL_EMPTY) { + if (value == TypedValue.DATA_NULL_EMPTY) { return new ResEmptyValue(value, rawValue, type); } return new ResReferenceValue(mPackage, 0, null); 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 9e0dcf2a..24bfe964 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 @@ -275,7 +275,7 @@ public class ARSCDecoder { int typeFlags = mIn.readByte(); mIn.skipBytes(2); // reserved int entryCount = mIn.readInt(); - mIn.skipInt(); // entriesStart + int entriesStart = mIn.readInt(); ResConfigFlags flags = readConfigFlags(); @@ -313,6 +313,13 @@ public class ARSCDecoder { mType = flags.isInvalid && !mKeepBroken ? null : mPkg.getOrCreateConfig(flags); int noEntry = isOffset16 ? NO_ENTRY_OFFSET16 : NO_ENTRY; + // #3428 - In some applications the res entries are padded for alignment. + int entriesStartAligned = mHeader.startPosition + entriesStart; + if (mIn.position() < entriesStartAligned) { + long bytesSkipped = mIn.skip(entriesStartAligned - mIn.position()); + LOGGER.fine("Skipping: " + bytesSkipped + " byte(s) to align with ResTable_entry start."); + } + for (int i : entryOffsetMap.keySet()) { mResId = (mResId & 0xffff0000) | i; int offset = entryOffsetMap.get(i); diff --git a/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/decoder/ResFileDecoder.java b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/decoder/ResFileDecoder.java index 9bab7c98..979d89a8 100644 --- a/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/decoder/ResFileDecoder.java +++ b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/decoder/ResFileDecoder.java @@ -25,6 +25,7 @@ import brut.androlib.res.data.value.ResFileValue; import brut.directory.DirUtil; import brut.directory.Directory; import brut.directory.DirectoryException; +import brut.util.BrutIO; import java.io.*; import java.util.Map; @@ -44,8 +45,15 @@ public class ResFileDecoder { ResFileValue fileValue = (ResFileValue) res.getValue(); String inFilePath = fileValue.toString(); String inFileName = fileValue.getStrippedPath(); - String outResName = res.getFilePath(); String typeName = res.getResSpec().getType().getName(); + String outResName = res.getFilePath(); + + if (BrutIO.detectPossibleDirectoryTraversal(outResName)) { + outResName = inFileName; + LOGGER.warning(String.format( + "Potentially malicious file path: %s, using instead %s", res.getFilePath(), outResName + )); + } String ext = null; String outFileName; diff --git a/brut.apktool/apktool-lib/src/main/resources/prebuilt/linux/aapt2 b/brut.apktool/apktool-lib/src/main/resources/prebuilt/linux/aapt2 index 4ba1e9fc..8e11eba0 100755 Binary files a/brut.apktool/apktool-lib/src/main/resources/prebuilt/linux/aapt2 and b/brut.apktool/apktool-lib/src/main/resources/prebuilt/linux/aapt2 differ diff --git a/brut.apktool/apktool-lib/src/main/resources/prebuilt/linux/aapt2_64 b/brut.apktool/apktool-lib/src/main/resources/prebuilt/linux/aapt2_64 index a27b4b45..f0006f4c 100755 Binary files a/brut.apktool/apktool-lib/src/main/resources/prebuilt/linux/aapt2_64 and b/brut.apktool/apktool-lib/src/main/resources/prebuilt/linux/aapt2_64 differ diff --git a/brut.apktool/apktool-lib/src/main/resources/prebuilt/macosx/aapt2_64 b/brut.apktool/apktool-lib/src/main/resources/prebuilt/macosx/aapt2_64 index c9747ae5..bd8655f0 100755 Binary files a/brut.apktool/apktool-lib/src/main/resources/prebuilt/macosx/aapt2_64 and b/brut.apktool/apktool-lib/src/main/resources/prebuilt/macosx/aapt2_64 differ diff --git a/brut.apktool/apktool-lib/src/main/resources/prebuilt/macosx/aapt_64 b/brut.apktool/apktool-lib/src/main/resources/prebuilt/macosx/aapt_64 index 8c2b9ee4..77de7b24 100755 Binary files a/brut.apktool/apktool-lib/src/main/resources/prebuilt/macosx/aapt_64 and b/brut.apktool/apktool-lib/src/main/resources/prebuilt/macosx/aapt_64 differ diff --git a/brut.apktool/apktool-lib/src/main/resources/prebuilt/windows/aapt2.exe b/brut.apktool/apktool-lib/src/main/resources/prebuilt/windows/aapt2.exe index f8b25f07..46f118db 100755 Binary files a/brut.apktool/apktool-lib/src/main/resources/prebuilt/windows/aapt2.exe and b/brut.apktool/apktool-lib/src/main/resources/prebuilt/windows/aapt2.exe differ diff --git a/brut.apktool/apktool-lib/src/main/resources/prebuilt/windows/aapt2_64.exe b/brut.apktool/apktool-lib/src/main/resources/prebuilt/windows/aapt2_64.exe index 0c8690e4..584e971d 100755 Binary files a/brut.apktool/apktool-lib/src/main/resources/prebuilt/windows/aapt2_64.exe and b/brut.apktool/apktool-lib/src/main/resources/prebuilt/windows/aapt2_64.exe differ diff --git a/brut.apktool/apktool-lib/src/test/java/brut/androlib/aapt1/BuildAndDecodeTest.java b/brut.apktool/apktool-lib/src/test/java/brut/androlib/aapt1/BuildAndDecodeTest.java index 316b9644..2faf5cf6 100644 --- a/brut.apktool/apktool-lib/src/test/java/brut/androlib/aapt1/BuildAndDecodeTest.java +++ b/brut.apktool/apktool-lib/src/test/java/brut/androlib/aapt1/BuildAndDecodeTest.java @@ -194,6 +194,11 @@ public class BuildAndDecodeTest extends BaseTest { compareXmlFiles("res/xml/references.xml"); } + @Test + public void xmlAccessibilityTest() throws BrutException { + compareXmlFiles("res/xml/accessibility_service_config.xml"); + } + @Test public void xmlXsdFileTest() throws BrutException { compareXmlFiles("res/xml/ww_box_styles_schema.xsd"); diff --git a/brut.apktool/apktool-lib/src/test/java/brut/androlib/aapt2/BuildAndDecodeTest.java b/brut.apktool/apktool-lib/src/test/java/brut/androlib/aapt2/BuildAndDecodeTest.java index a0aa8048..14014adc 100644 --- a/brut.apktool/apktool-lib/src/test/java/brut/androlib/aapt2/BuildAndDecodeTest.java +++ b/brut.apktool/apktool-lib/src/test/java/brut/androlib/aapt2/BuildAndDecodeTest.java @@ -71,6 +71,11 @@ public class BuildAndDecodeTest extends BaseTest { assertTrue(sTestNewDir.isDirectory()); } + @Test + public void valuesColorsTest() throws BrutException { + compareValuesFiles("values/colors.xml"); + } + @Test public void valuesStringsTest() throws BrutException { compareValuesFiles("values/strings.xml"); @@ -144,6 +149,11 @@ public class BuildAndDecodeTest extends BaseTest { compareXmlFiles("res/xml/ww_box_styles_schema.xsd"); } + @Test + public void xmlAccessibilityTest() throws BrutException { + compareXmlFiles("res/xml/accessibility_service_config.xml"); + } + @Test public void multipleDexTest() throws BrutException, IOException { compareBinaryFolder("/smali_classes2", false); diff --git a/brut.apktool/apktool-lib/src/test/java/brut/androlib/decode/ResourceDirectoryTraversalTest.java b/brut.apktool/apktool-lib/src/test/java/brut/androlib/decode/ResourceDirectoryTraversalTest.java new file mode 100644 index 00000000..44ec664c --- /dev/null +++ b/brut.apktool/apktool-lib/src/test/java/brut/androlib/decode/ResourceDirectoryTraversalTest.java @@ -0,0 +1,62 @@ +/* + * 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.ApkDecoder; +import brut.androlib.BaseTest; +import brut.androlib.Config; +import brut.androlib.TestUtils; +import brut.common.BrutException; +import brut.directory.ExtFile; +import brut.util.OS; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; + +import java.io.File; +import java.io.IOException; + +import static org.junit.Assert.assertTrue; + +public class ResourceDirectoryTraversalTest extends BaseTest { + + @BeforeClass + public static void beforeClass() throws Exception { + TestUtils.cleanFrameworkFile(); + sTmpDir = new ExtFile(OS.createTempDirectory()); + TestUtils.copyResourceDir(ResourceDirectoryTraversalTest.class, "decode/arbitrary-write/", sTmpDir); + } + + @AfterClass + public static void afterClass() throws BrutException { + OS.rmdir(sTmpDir); + } + + @Test + public void checkIfMaliciousRawFileIsDisassembledProperly() throws BrutException, IOException { + String apk = "GHSA-2hqv-2xv4-5h5w.apk"; + + Config config = Config.getDefaultConfig(); + config.forceDelete = true; + ApkDecoder apkDecoder = new ApkDecoder(config, new File(sTmpDir + File.separator + apk)); + File outDir = new File(sTmpDir + File.separator + apk + ".out"); + apkDecoder.decode(outDir); + + File pocTestFile = new File(outDir,"res/raw/poc"); + assertTrue(pocTestFile.exists()); + } +} diff --git a/brut.apktool/apktool-lib/src/test/java/brut/androlib/util/UnknownDirectoryTraversalTest.java b/brut.apktool/apktool-lib/src/test/java/brut/androlib/util/UnknownDirectoryTraversalTest.java index ad22e9d0..269b3fc0 100644 --- a/brut.apktool/apktool-lib/src/test/java/brut/androlib/util/UnknownDirectoryTraversalTest.java +++ b/brut.apktool/apktool-lib/src/test/java/brut/androlib/util/UnknownDirectoryTraversalTest.java @@ -51,7 +51,7 @@ public class UnknownDirectoryTraversalTest extends BaseTest { @Test public void validFileTest() throws IOException, BrutException { - String validFilename = BrutIO.sanitizeUnknownFile(sTmpDir, "file"); + String validFilename = BrutIO.sanitizeFilepath(sTmpDir, "file"); assertEquals(validFilename, "file"); File validFile = new File(sTmpDir, validFilename); @@ -60,18 +60,18 @@ public class UnknownDirectoryTraversalTest extends BaseTest { @Test(expected = TraversalUnknownFileException.class) public void invalidBackwardFileTest() throws IOException, BrutException { - BrutIO.sanitizeUnknownFile(sTmpDir, "../file"); + BrutIO.sanitizeFilepath(sTmpDir, "../file"); } @Test(expected = RootUnknownFileException.class) public void invalidRootFileTest() throws IOException, BrutException { String rootLocation = OSDetection.isWindows() ? "C:/" : File.separator; - BrutIO.sanitizeUnknownFile(sTmpDir, rootLocation + "file"); + BrutIO.sanitizeFilepath(sTmpDir, rootLocation + "file"); } @Test(expected = InvalidUnknownFileException.class) public void noFilePassedTest() throws IOException, BrutException { - BrutIO.sanitizeUnknownFile(sTmpDir, ""); + BrutIO.sanitizeFilepath(sTmpDir, ""); } @Test(expected = TraversalUnknownFileException.class) @@ -83,12 +83,12 @@ public class UnknownDirectoryTraversalTest extends BaseTest { invalidPath = "..\\..\\app.exe"; } - BrutIO.sanitizeUnknownFile(sTmpDir, invalidPath); + BrutIO.sanitizeFilepath(sTmpDir, invalidPath); } @Test public void validDirectoryFileTest() throws IOException, BrutException { - String validFilename = BrutIO.sanitizeUnknownFile(sTmpDir, "dir" + File.separator + "file"); + String validFilename = BrutIO.sanitizeFilepath(sTmpDir, "dir" + File.separator + "file"); assertEquals("dir" + File.separator + "file", validFilename); } } diff --git a/brut.apktool/apktool-lib/src/test/resources/aapt1/testapp/AndroidManifest.xml b/brut.apktool/apktool-lib/src/test/resources/aapt1/testapp/AndroidManifest.xml index 70c90ff4..3ffded5a 100644 --- a/brut.apktool/apktool-lib/src/test/resources/aapt1/testapp/AndroidManifest.xml +++ b/brut.apktool/apktool-lib/src/test/resources/aapt1/testapp/AndroidManifest.xml @@ -2,7 +2,12 @@ - + + + + diff --git a/brut.apktool/apktool-lib/src/test/resources/aapt1/testapp/res/xml/accessibility_service_config.xml b/brut.apktool/apktool-lib/src/test/resources/aapt1/testapp/res/xml/accessibility_service_config.xml new file mode 100644 index 00000000..29894547 --- /dev/null +++ b/brut.apktool/apktool-lib/src/test/resources/aapt1/testapp/res/xml/accessibility_service_config.xml @@ -0,0 +1,3 @@ + diff --git a/brut.apktool/apktool-lib/src/test/resources/aapt2/testapp/AndroidManifest.xml b/brut.apktool/apktool-lib/src/test/resources/aapt2/testapp/AndroidManifest.xml index 5c1e6296..444556f3 100644 --- a/brut.apktool/apktool-lib/src/test/resources/aapt2/testapp/AndroidManifest.xml +++ b/brut.apktool/apktool-lib/src/test/resources/aapt2/testapp/AndroidManifest.xml @@ -1,6 +1,12 @@ - + + + + + diff --git a/brut.apktool/apktool-lib/src/test/resources/aapt2/testapp/res/values/colors.xml b/brut.apktool/apktool-lib/src/test/resources/aapt2/testapp/res/values/colors.xml new file mode 100644 index 00000000..e78c33e0 --- /dev/null +++ b/brut.apktool/apktool-lib/src/test/resources/aapt2/testapp/res/values/colors.xml @@ -0,0 +1,7 @@ + + + #ff123456 + @android:color/white + #00000000 + @null + diff --git a/brut.apktool/apktool-lib/src/test/resources/aapt2/testapp/res/xml/accessibility_service_config.xml b/brut.apktool/apktool-lib/src/test/resources/aapt2/testapp/res/xml/accessibility_service_config.xml new file mode 100644 index 00000000..29894547 --- /dev/null +++ b/brut.apktool/apktool-lib/src/test/resources/aapt2/testapp/res/xml/accessibility_service_config.xml @@ -0,0 +1,3 @@ + diff --git a/brut.apktool/apktool-lib/src/test/resources/decode/arbitrary-write/GHSA-2hqv-2xv4-5h5w.apk b/brut.apktool/apktool-lib/src/test/resources/decode/arbitrary-write/GHSA-2hqv-2xv4-5h5w.apk new file mode 100644 index 00000000..cc177ce3 Binary files /dev/null and b/brut.apktool/apktool-lib/src/test/resources/decode/arbitrary-write/GHSA-2hqv-2xv4-5h5w.apk differ diff --git a/brut.j.dir/src/main/java/brut/directory/DirUtil.java b/brut.j.dir/src/main/java/brut/directory/DirUtil.java index bfc1e0a1..906fdac2 100644 --- a/brut.j.dir/src/main/java/brut/directory/DirUtil.java +++ b/brut.j.dir/src/main/java/brut/directory/DirUtil.java @@ -89,7 +89,7 @@ public class DirUtil { } else if (!in.containsDir(fileName) && !in.containsFile(fileName)) { // Skip copies of directories/files not found. } else { - String cleanedFilename = BrutIO.sanitizeUnknownFile(out, fileName); + String cleanedFilename = BrutIO.sanitizeFilepath(out, fileName); if (! cleanedFilename.isEmpty()) { File outFile = new File(out, cleanedFilename); //noinspection ResultOfMethodCallIgnored diff --git a/brut.j.dir/src/main/java/brut/directory/FileDirectory.java b/brut.j.dir/src/main/java/brut/directory/FileDirectory.java index 554900bd..8a9f9915 100644 --- a/brut.j.dir/src/main/java/brut/directory/FileDirectory.java +++ b/brut.j.dir/src/main/java/brut/directory/FileDirectory.java @@ -119,7 +119,7 @@ public class FileDirectory extends AbstractDirectory { } } - private File getDir() { + public File getDir() { return mDir; } } diff --git a/brut.j.dir/src/main/java/brut/directory/ZipUtils.java b/brut.j.dir/src/main/java/brut/directory/ZipUtils.java index 3e719900..446dee64 100644 --- a/brut.j.dir/src/main/java/brut/directory/ZipUtils.java +++ b/brut.j.dir/src/main/java/brut/directory/ZipUtils.java @@ -59,8 +59,8 @@ public class ZipUtils { throws BrutException, IOException { for (final File file : folder.listFiles()) { if (file.isFile()) { - final String cleanedPath = BrutIO.sanitizeUnknownFile(folder, file.getPath().substring(prefixLength)); - final ZipEntry zipEntry = new ZipEntry(BrutIO.normalizePath(cleanedPath)); + final String cleanedPath = BrutIO.sanitizeFilepath(folder, file.getPath().substring(prefixLength)); + final ZipEntry zipEntry = new ZipEntry(BrutIO.adaptSeparatorToUnix(cleanedPath)); // aapt binary by default takes in parameters via -0 arsc to list extensions that shouldn't be // compressed. We will replicate that behavior diff --git a/brut.j.util/src/main/java/brut/util/BrutIO.java b/brut.j.util/src/main/java/brut/util/BrutIO.java index ede9a853..e56867dc 100644 --- a/brut.j.util/src/main/java/brut/util/BrutIO.java +++ b/brut.j.util/src/main/java/brut/util/BrutIO.java @@ -74,8 +74,8 @@ public class BrutIO { return crc; } - public static String sanitizeUnknownFile(final File directory, final String entry) throws IOException, BrutException { - if (entry.length() == 0) { + public static String sanitizeFilepath(final File directory, final String entry) throws IOException, BrutException { + if (entry.isEmpty()) { throw new InvalidUnknownFileException("Invalid Unknown File"); } @@ -94,7 +94,11 @@ public class BrutIO { return canonicalEntryPath.substring(canonicalDirPath.length()); } - public static String normalizePath(String path) { + public static boolean detectPossibleDirectoryTraversal(String entry) { + return entry.contains("../") || entry.contains("/..") || entry.contains("..\\") || entry.contains("\\.."); + } + + public static String adaptSeparatorToUnix(String path) { char separator = File.separatorChar; if (separator != '/') { diff --git a/build.gradle.kts b/build.gradle.kts index 268ff87c..b521e7a3 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,7 +1,7 @@ import java.io.ByteArrayOutputStream -val version = "2.9.1" -val suffix = "SNAPSHOT" +val version = "2.9.3" +val suffix = "" // Strings embedded into the build. var gitRevision by extra("") diff --git a/docker/Dockerfile b/docker/Dockerfile deleted file mode 100644 index a4bc12ae..00000000 --- a/docker/Dockerfile +++ /dev/null @@ -1,62 +0,0 @@ -# BUILDER -# ===================================================== -FROM ibm-semeru-runtimes:open-17-jdk-jammy AS builder - -COPY . /build -WORKDIR /build -RUN \ - # Apktool - ./gradlew build shadowJar proguard - -RUN \ - # Relocate for easier copying - JAR=$(find /build/brut.apktool/apktool-cli/build/libs/apktool-*.jar |grep -v cli-all) &&\ - mv ${JAR} /build/apktool.jar - -# BASE -# ===================================================== -FROM ibm-semeru-runtimes:open-17-jre-jammy AS base - -# Latest version as of 2023.10.01 -# Ref: https://developer.android.com/studio/index.html#command-line-tools-only -ARG COMMAND_LINE_TOOLS_VERSION=10406996 - -ARG ANDROID_SDK_ROOT=/opt/android-sdk - -COPY --from=builder /build/apktool.jar /usr/local/bin/apktool.jar -COPY --from=builder /build/scripts/linux/apktool /usr/local/bin/apktool - -RUN \ - # Update - apt-get update &&\ - \ - # Apktool final setup - chmod +x /usr/local/bin/apktool /usr/local/bin/apktool.jar &&\ - \ - # Android SDK - apt-get install -y --no-install-recommends \ - ca-certificates \ - curl \ - zipalign \ - git \ - openssl \ - wget \ - unzip \ - sdkmanager &&\ - curl -o /tmp/tools.zip https://dl.google.com/android/repository/commandlinetools-linux-${COMMAND_LINE_TOOLS_VERSION}_latest.zip &&\ - mkdir -p ${ANDROID_SDK_ROOT}/cmdline-tools &&\ - unzip -q /tmp/tools.zip -d ${ANDROID_SDK_ROOT}/cmdline-tools &&\ - mv ${ANDROID_SDK_ROOT}/cmdline-tools/cmdline-tools ${ANDROID_SDK_ROOT}/cmdline-tools/latest &&\ - rm -v /tmp/tools.zip &&\ - mkdir -p /root/.android/ && touch /root/.android/repositories.cfg &&\ - yes | sdkmanager --licenses &&\ - export BUILD_TOOLS_VERSION=$(sdkmanager --list |grep build-tools |grep -v rc |awk '{print $1}' |sed 's/build-tools;//g' |sort |tail -n1) &&\ - sdkmanager --install "build-tools;${BUILD_TOOLS_VERSION}" &&\ - ln -s ${ANDROID_SDK_ROOT}/build-tools/${BUILD_TOOLS_VERSION} /opt/bin &&\ - \ - # Cleanup - rm -rf /var/lib/apt/lists/* - -ENV PATH ${PATH}:/opt/bin - -CMD ["/usr/local/bin/apktool"] \ No newline at end of file diff --git a/docker/README.md b/docker/README.md deleted file mode 100644 index 50effc03..00000000 --- a/docker/README.md +++ /dev/null @@ -1,27 +0,0 @@ -# Apktool in Docker -We provide an easy way to leverage `apktool`, along with common Android tools such as `zipalign` and `apksigner`, all from within Docker. - -## Building the Docker image -To use the image, pull the pre-built image or build one from the repo root with the included Dockerfile: -```bash -docker pull ghcr.io/ibotpeaches/apktool:latest -# OR -docker build -t apktool:local -f docker/Dockerfile . -``` - -## Using the Docker image -The best way to use the image is to create aliases to run the internal commands. Replace `ghcr.io/ibotpeaches/apktool:latest` with `apktool:local` if you have built the image locally. -```bash -alias apktool="docker run --rm -ti --name=apktool -v \"${PWD}:${PWD}\" -w \"${PWD}\" ghcr.io/ibotpeaches/apktool:latest apktool" -alias zipalign="docker run --rm -ti --name=zipalign -v \"${PWD}:${PWD}\" -w \"${PWD}\" ghcr.io/ibotpeaches/apktool:latest zipalign" -alias apksigner="docker run --rm -ti --name=apksigner -v \"${PWD}:${PWD}\" -w \"${PWD}\" ghcr.io/ibotpeaches/apktool:latest apksigner" -``` - -## Running the commands -You can then utilize these commands as you would if they were natively installed: -```bash -apktool d My.apk -o MyFolder -apktool b MyFolder -o MyNew.apk -zipalign -p -f 4 MyNew.apk MyNewAligned.apk -apksigner sign --ks My.keystore MyNewAligned.apk -``` diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index e50d79d5..d08e532a 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,12 +1,12 @@ [versions] baksmali = "3.0.3" -commons_io = "2.15.0" +commons_io = "2.15.1" commons_cli = "1.6.0" -commons_lang3 = "3.13.0" +commons_lang3 = "3.14.0" commons_text = "1.11.0" guava = "32.0.1-jre" junit = "4.13.2" -proguard = "7.4.0" +proguard = "7.4.1" shadow = "8.1.1" smali = "3.0.3" xmlpull = "1.1.4c"