Use the tree to jump between collections without leaving the reader.

archive Select writeup Open tree
HackTheBox/Challenges/Challenge Protected.en.md READ_ONLY

Challenge Protected

This challenge consists of analyzing an Android /data partition dump from a device running the GalleryVault app to recover a hidden media file and the solution consists of querying SQLite databases to locate the file, statically analyzing the decompiled APK with Jadx, and performing a Known-Plaintext Attack (KPA) to extract the encryption key, break the custom XOR obfuscation, and assemble the final image containing the flag.

The provided files consist of an Android file system dump. A preliminary inspection of the data directory reveals various standard Android folders.

$ cd data
$ ls
adb      app-ephemeral  bootchart     fonts               local.prop    misc_ce      per_boot        rollback-history           system_ce    user_de
anr      app-lib        cache         gsi                 lost+found    misc_de      preloads        rollback-observer          system_de    vendor
apex     app-private    dalvik-cache  gsi_persistent_data media         nfc          property        server_configurable_flags  tombstones   vendor_ce
app      app-staging    data          incremental         mediadrm      ota          resource-cache  ss                         system       unencrypted  vendor_de
app-asec backup        drm           local               misc          ota_package  rollback        system                     user

An unencrypted folder is present but contains only keys and references.

$ ls unencrypted
key  mode  per_boot_ref  ref

Searching for installed APKs yields several applications of interest.

$ find -name base.apk 2>/dev/null
./app/~~8Ot3xFnHxIA5G1EBv2XkAA==/com.android.vending-0WnXQTRao1TRfgOYf6N2rw==/base.apk
./app/~~kGGaXfYOgnywjhPpaMVKbQ==/org.thoughtcrime.securesms-fKTywNyaYqPs30rWNVPWtQ==/base.apk
./app/~~xQBJ50iw1oO3bdl3q0vg_Q==/com.google.android.apps.restore-DUZq-5l_c-H-Ehpgh7WTPw==/base.apk
./app/~~EbACqxIPu-027FzlAQAAxw==/com.google.android.contactkeys-hB1aQm1LDeaT148PuUXjzw==/base.apk
./app/~~lDpCJWRbjfx7U3-X05iUCg==/com.google.android.gms-hh6YJN96J7LdG7A22eBKMg==/base.apk
./app/~~a7jcwJHUu9joGQrhYMPkDg==/com.google.ar.core-lhnIYYSmiYFHA2bR0lulxQ==/base.apk
./app/~~YtqSpVnlFnQ-CZBF-Js7HQ==/com.topjohnwu.magisk-S6_izVj57Zo74r4BVN8xbA==/base.apk
./app/~~aSe9Hqb8bP-Dh3DCicv9Qg==/notes.notepad.checklist.calendar.todolist.notebook-Y5KhcAnQ73h6JFGlMYvP1w==/base.apk
./app/~~yGbAQeKpuoLQ_p40HsRbog==/com.google.android.trichromelibrary_677813638-bMi4oXsZdav-Z3Dj_DOmqg==/base.apk
./app/~~2qXqwf1y-IeO0sr8TIHMgw==/com.thinkyeah.galleryvault-lqXfwbOnDTEzGpHYF5CHpA==/base.apk
./app/~~50gTwEyIu6K-QI0D1Snj1Q==/com.google.android.youtube-QnYH4lT8sSigEfVvzUxsVA==/base.apk
./app/~~Bve9RB9NcOrvaXbJeavNWQ==/com.android.chrome-An0s-R6NITvoziJXdBz7uQ==/base.apk
./app/~~txG5PGKmjIicn0p8yjg3MQ==/com.passmark.osfextract-jwbcOR1VUQhPr5C6jxyAjg==/base.apk
./app/~~o_1cVZLkLcKrOUcCdQy5DQ==/com.google.android.apps.youtube.music-CWcfzUMG92jhKEdRyQHBpQ==/base.apk

The presence of Magisk indicates a rooted device, while OSFExtract was likely used to acquire the file system dump. Gallery Vault, an app used for hiding media files, stands out as a prime target for investigation. The application's database file can be explored using SQLite3.

$ sqlite3 data/com.thinkyeah.galleryvault/databases/galleryvault.db
SQLite version 3.53.3 2026-06-26 20:14:12
Enter ".help" for usage hints.

Listing the tables reveals a file_v1 table which holds metadata for hidden files.

sqlite> .tables
android_metadata     cloud_transfer_items          export_unhidden_history       file_v1                         recycle_bin_file_view       tab_group
break_in_report      download_and_encrypt_view     file_folder_revision          folder_v1                       recycle_bin_folder_info     tab_group_with_tab_view
browser_history      download_task                 file_system_sync              folder_with_cover_file_view     recycle_bin_v1              web_url
change_actions       encrypt_after_download_v1     file_system_sync_file_add     misc_data                       tab

Querying the file_v1 table uncovers a hidden PNG file named flag.png associated with a specific UUID.

sqlite> select * from file_v1;
โ•ญโ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ
โ”‚_idโ”‚  uuid   โ”‚profile_โ”‚  name  โ”‚folder_iโ”‚file_typโ”‚mime_typโ”‚original_pโ”‚image_orโ”‚image_wiโ”‚image_heโ”‚video_duโ”‚encrypt_โ”‚added_tiโ”‚file_last_โ”‚file_sizโ”‚sourceโ”‚completeโ”‚storage_โ”‚file_sorโ”‚
โ”‚   โ”‚         โ”‚   id   โ”‚        โ”‚   d    โ”‚   e    โ”‚   e    โ”‚   ath    โ”‚ientatioโ”‚  dth   โ”‚  ight  โ”‚ ration โ”‚ state  โ”‚ me_utc โ”‚modifie...โ”‚   e    โ”‚      โ”‚ _state โ”‚  type  โ”‚t_index โ”‚
โ”‚   โ”‚         โ”‚        โ”‚        โ”‚        โ”‚        โ”‚        โ”‚          โ”‚   n    โ”‚        โ”‚        โ”‚        โ”‚        โ”‚        โ”‚          โ”‚        โ”‚      โ”‚        โ”‚        โ”‚        โ”‚
โ•žโ•โ•โ•โ•ชโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ชโ•โ•โ•โ•โ•โ•โ•โ•โ•ชโ•โ•โ•โ•โ•โ•โ•โ•โ•ชโ•โ•โ•โ•โ•โ•โ•โ•โ•ชโ•โ•โ•โ•โ•โ•โ•โ•โ•ชโ•โ•โ•โ•โ•โ•โ•โ•โ•ชโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ชโ•โ•โ•โ•โ•โ•โ•โ•โ•ชโ•โ•โ•โ•โ•โ•โ•โ•โ•ชโ•โ•โ•โ•โ•โ•โ•โ•โ•ชโ•โ•โ•โ•โ•โ•โ•โ•โ•ชโ•โ•โ•โ•โ•โ•โ•โ•โ•ชโ•โ•โ•โ•โ•โ•โ•โ•โ•ชโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ชโ•โ•โ•โ•โ•โ•โ•โ•โ•ชโ•โ•โ•โ•โ•โ•โ•ชโ•โ•โ•โ•โ•โ•โ•โ•โ•ชโ•โ•โ•โ•โ•โ•โ•โ•โ•ชโ•โ•โ•โ•โ•โ•โ•โ•โ•ก
โ”‚  1โ”‚b238f4cd-โ”‚       1โ”‚flag.pngโ”‚      11โ”‚       0โ”‚image/  โ”‚/storage/ โ”‚       0โ”‚     564โ”‚     568โ”‚       0โ”‚       0โ”‚17423990โ”‚1742399018โ”‚  787165โ”‚NULL  โ”‚       0โ”‚       1โ”‚       0โ”‚
โ”‚   โ”‚f79f-4e19โ”‚        โ”‚        โ”‚        โ”‚        โ”‚png     โ”‚emulated/0โ”‚        โ”‚        โ”‚        โ”‚        โ”‚        โ”‚   78763โ”‚       000โ”‚        โ”‚      โ”‚        โ”‚        โ”‚        โ”‚
โ”‚   โ”‚-be3b-   โ”‚        โ”‚        โ”‚        โ”‚        โ”‚        โ”‚/Pictures/โ”‚        โ”‚        โ”‚        โ”‚        โ”‚        โ”‚        โ”‚          โ”‚        โ”‚      โ”‚        โ”‚        โ”‚        โ”‚
โ”‚   โ”‚068b7add4โ”‚        โ”‚        โ”‚        โ”‚        โ”‚        โ”‚flag.png  โ”‚        โ”‚        โ”‚        โ”‚        โ”‚        โ”‚        โ”‚          โ”‚        โ”‚      โ”‚        โ”‚        โ”‚        โ”‚
โ”‚   โ”‚d85      โ”‚        โ”‚        โ”‚        โ”‚        โ”‚        โ”‚          โ”‚        โ”‚        โ”‚        โ”‚        โ”‚        โ”‚        โ”‚          โ”‚        โ”‚      โ”‚        โ”‚        โ”‚        โ”‚
โ•ฐโ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ

Searching the filesystem for this UUID locates the physical file.

$ find -name "*7add4d85" 2>/dev/null
./media/0/.galleryvault_DoNotDelete_1742398726/files/b2/b238f4cd-f79f-4e19-be3b-068b7add4d85

Viewing the image directly fails.

$ kitty +kitten icat ./media/0/.galleryvault_DoNotDelete_1742398726/files/b2/b238f4cd-f79f-4e19-be3b-068b7add4d85

This indicates the file is encrypted or obfuscated, necessitating a deeper analysis of the Gallery Vault application. Decompiling the APK with JADX allows for static analysis of the source code. Inspecting the FileTailOperatorV1 class reveals a custom encryption routine.

public final void a(File file, File file2, JSONObject jSONObject, byte[] bArr, boolean z10, long j10, long j11) throws IOException {
            ...
            a aVar = new a(file2, 0L, j10);
            ...
                    for (int i7 = 0; i7 < i3; i7++) {
                        long j13 = ((long) i7) + j12;
                        bArr2[i7] = (byte) (bArr2[i7] ^ ((byte) (bArr[(int) (j13 % ((long) bArr.length))] ^ ((byte) j13))));
                    }
            ...
}

The application reads a portion of the original file, iterates over its bytes, and applies an XOR cipher using a key, the length of the key, and the byte index. Furthermore, the ThinkSecurity class demonstrates how the encryption key is derived from a PIN.

public static SecretKey e(String str) {
    String strSubstring;
        ...
        if (str.length() < 16) {
            StringBuilder sb = new StringBuilder();
            for (int i3 = 0; i3 < 16 - str.length(); i3++) {
                sb.append("0");
            }
            strSubstring = str + sb.toString();
        } else {
            strSubstring = str.substring(0, 16);
        }
        return SecretKeyFactory.getInstance("DES").generateSecret(new DESKeySpec(strSubstring.getBytes()));
    } catch (Exception unused) {
        return null;
    }
}

The application uses the user's PIN to generate a DES key, adding zero-padding if the PIN is under 16 characters or truncating it if it exceeds 16 characters.

With the encryption mechanism understood, a custom script can be crafted to reverse the process. The script needs to parse the file structure, discarding the decoy PNG header, and isolating the unencrypted body up to the >>tyfs>> signature. The original 2803-byte header, encrypted with a 4-byte key and the XOR formula EncryptedByte[i] = PlaintextByte[i] ^ Key[i % 4] ^ (i & 0xFF), is located immediately after this signature. Given that the original file is known to be a PNG, a Known-Plaintext Attack (KPA) can be executed using the PNG magic bytes (89 50 4e 47) on the first 4 bytes of the encrypted header to mathematically extract the 4-byte key.

$ cat exploit.py
#!/usr/bin/env python3

import sys

file_path = './data/media/0/' \
            '.galleryvault_DoNotDelete_1742398726/' \
            'files/b2/b238f4cd-f79f-4e19-be3b-068b7add4d85'

with open(file_path, 'rb') as f:
        data = f.read()

idx = data.find(b'>>tyfs>>')
if idx == -1:
    print("Signature not found")
    sys.exit(1)

enc_hdr = data[idx + 8 : idx + 8 + 2803]
unenc_body = data[2803 : idx]

png_hdr = bytes.fromhex('89504E47')
key = bytearray()
for i in range(4):
    key.append(enc_hdr[i] ^ png_hdr[i] ^ i)

print(f"4 byte key recovered: {key.hex()}")

dec_hdr = bytearray()
for i in range(2803):
    dec_hdr.append(enc_hdr[i] ^ key[i % 4] ^ (i & 0xFF))

with open('flag.png', 'wb') as f:
    f.write(dec_hdr)
    f.write(unenc_body)

print("File saved as flag.png")

Executing the script recovers the encryption key and reconstructs the image.

$ python3 exploit.py
4 byte key recovered: 2eb8f950
File saved as flag.png

Viewing the recovered image reveals the flag.

$ kitty +kitten icat flag.png
HTB{FLAG}