Apktool infers resource files' output path according to their resource names which can be manipulated by attacker to place files at desired location on the system Apktool runs on
From 50f90be4de46781e2015eac671f1fa8355bb8b87 Mon Sep 17 00:00:00 2001
From: 0x33c0unt <26827438+0x33c0unt@users.noreply.github.com>
Date: Sun, 17 Dec 2023 22:26:32 +0100
Subject: [PATCH] Extract resource files according to their original path
inside of the APK
---
.../brut/androlib/res/ResourcesDecoder.java | 8 ++--
.../androlib/res/decoder/ResFileDecoder.java | 47 +++++--------------
2 files changed, 17 insertions(+), 38 deletions(-)
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 a9507b6b..976e5885 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
@@ -154,12 +154,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,9 +174,9 @@ public class ResourcesDecoder {
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/decoder/ResFileDecoder.java b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/decoder/ResFileDecoder.java
index 9bab7c98..d4956f6d 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
@@ -42,92 +42,71 @@ public class ResFileDecoder {
throws AndrolibException {
ResFileValue fileValue = (ResFileValue) res.getValue();
- String inFilePath = fileValue.toString();
+ String filePath = fileValue.toString();
String inFileName = fileValue.getStrippedPath();
- String outResName = res.getFilePath();
String typeName = res.getResSpec().getType().getName();
-
String ext = null;
- String outFileName;
int extPos = inFileName.lastIndexOf(".");
- if (extPos == -1) {
- outFileName = outResName;
- } else {
+ if (extPos != -1) {
ext = inFileName.substring(extPos).toLowerCase();
- outFileName = outResName + ext;
- }
-
- String outFilePath = "res/" + outFileName;
- if (!inFilePath.equals(outFilePath)) {
- resFileMapping.put(inFilePath, outFilePath);
}
-
- LOGGER.fine("Decoding file " + inFilePath + " to " + outFilePath);
+ LOGGER.fine("Decoding file " + filePath);
try {
if (typeName.equals("raw")) {
- decode(inDir, inFilePath, outDir, outFileName, "raw");
+ decode(inDir, filePath, outDir, filePath, "raw");
return;
}
if (typeName.equals("font") && !".xml".equals(ext)) {
- decode(inDir, inFilePath, outDir, outFileName, "raw");
+ decode(inDir, filePath, outDir, filePath, "raw");
return;
}
if (typeName.equals("drawable") || typeName.equals("mipmap")) {
if (inFileName.toLowerCase().endsWith(".9" + ext)) {
- outFileName = outResName + ".9" + ext;
-
- // check for htc .r.9.png
- if (inFileName.toLowerCase().endsWith(".r.9" + ext)) {
- outFileName = outResName + ".r.9" + ext;
- }
-
// check for raw 9patch images
for (String extension : RAW_9PATCH_IMAGE_EXTENSIONS) {
if (inFileName.toLowerCase().endsWith("." + extension)) {
- copyRaw(inDir, outDir, inFilePath, outFileName);
+ copyRaw(inDir, outDir, filePath, filePath);
return;
}
}
-
// check for xml 9 patches which are just xml files
if (inFileName.toLowerCase().endsWith(".xml")) {
- decode(inDir, inFilePath, outDir, outFileName, "xml");
+ decode(inDir, filePath, outDir, filePath, "xml");
return;
}
try {
- decode(inDir, inFilePath, outDir, outFileName, "9patch");
+ decode(inDir, filePath, outDir, filePath, "9patch");
return;
} catch (CantFind9PatchChunkException ex) {
LOGGER.log(Level.WARNING, String.format(
"Cant find 9patch chunk in file: \"%s\". Renaming it to *.png.", inFileName
), ex);
- outDir.removeFile(outFileName);
- outFileName = outResName + ext;
+ outDir.removeFile(filePath);
}
}
// check for raw image
for (String extension : RAW_IMAGE_EXTENSIONS) {
if (inFileName.toLowerCase().endsWith("." + extension)) {
- copyRaw(inDir, outDir, inFilePath, outFileName);
+ copyRaw(inDir, outDir, filePath, filePath);
return;
}
}
if (!".xml".equals(ext)) {
- decode(inDir, inFilePath, outDir, outFileName, "raw");
+ decode(inDir, filePath, outDir, filePath, "raw");
return;
}
}
- decode(inDir, inFilePath, outDir, outFileName, "xml");
+ decode(inDir, filePath, outDir, filePath, "xml");
} catch (RawXmlEncounteredException ex) {
// If we got an error to decode XML, lets assume the file is in raw format.
// This is a large assumption, that might increase runtime, but will save us for situations where
// XSD files are AXML`d on aapt1, but left in plaintext in aapt2.
- decode(inDir, inFilePath, outDir, outFileName, "raw");
+ decode(inDir, filePath, outDir, filePath, "raw");
} catch (AndrolibException ex) {
LOGGER.log(Level.SEVERE, String.format(
"Could not decode file, replacing by FALSE value: %s",
--
2.39.3 (Apple Git-145)
Summary
Apktool infers resource files' output path according to their resource names which can be manipulated by attacker to place files at desired location on the system Apktool runs on
Details
Apktool infers resource files' output path according to their resource names ([output-dir]/res/[type]/[resource-name]+[ext of (resource-file)] )
E.g. a resource named "foo" with path of "res/raw/bar", is extracted to res/raw/foo
But resource name is never sanitized, therefore altering the resource name from "foo" to "../../../../../../../../../../../../tmp/poc" will end up placing "res/raw/bar" file to /tmp/poc in linux systems, but vulnerability exists in windows as well, didn't check for macOS but it seems generic
PoC
Impact
*Either user name is known or cwd is under user folder (note that an apk may contain 0xFFFF (65535) raw resources, this allows attacker to brute-force his target file)
Vulnerable code
Patch
Credits