Mobile Analysis⚓︎
Objective⚓︎
Find the missing child's name from the debug version of the Android app for Silver. Find a different child's name from the release version of the app for Gold.
Silver⚓︎
Given file: SantaSwipe.apk
Being unfamiliar with Android, I asked ChatGPT the most likely places within the file structure to store database content. With this in mind, I opened this file in Android Studio and navigated to smali
> com
> norhtpole
> DatabaseHelper
where I could see the list of children being populated. Then I ran the app in the enumlator and just manually compared what showed up in the running app to what was in the DatabaseHelper and it turns out a girl from Alabama was missing from the running app.
Gold Solution⚓︎
Given file: SantaSwipeSecure.aab
Opening the .aab file in Android Studio, we immediately notice the different file structure. If we look in the base directory, we can find the classes.dex files that are likely where our content resides. Double-clicking the first classes.dex file gives us it's file structure, which starts to look a little more familiar from the .apk debug file. Clicking com
> northpole
> santaswipe
> DatabaseHelper
navigates us to the content that is of interest.
onCreate and onUpdate are the terms we're keen to examine further since those are where our original data ended up being used to create the content. If you right-click the onCreate entry and select Show Bytecode
, this will allow us to view the details.
onCreate's Bytecode
.method public onCreate(Landroid/database/sqlite/SQLiteDatabase;)V .registers 3
const-string v0, "db"
invoke-static {p1, v0}, Lkotlin/jvm/internal/Intrinsics;->checkNotNullParameter(Ljava/lang/Object;Ljava/lang/String;)V
.line 36
const-string v0, "CREATE TABLE IF NOT EXISTS NiceList (Item TEXT);"
invoke-virtual {p1, v0}, Landroid/database/sqlite/SQLiteDatabase;->execSQL(Ljava/lang/String;)V
.line 37
const-string v0, "CREATE TABLE IF NOT EXISTS NaughtyList (Item TEXT);"
invoke-virtual {p1, v0}, Landroid/database/sqlite/SQLiteDatabase;->execSQL(Ljava/lang/String;)V
.line 38
const-string v0, "CREATE TABLE IF NOT EXISTS NormalList (Item TEXT);"
invoke-virtual {p1, v0}, Landroid/database/sqlite/SQLiteDatabase;->execSQL(Ljava/lang/String;)V
.line 39
const-string v0, "IVrt+9Zct4oUePZeQqFwyhBix8cSCIxtsa+lJZkMNpNFBgoHeJlwp73l2oyEh1Y6AfqnfH7gcU9Yfov6u70cUA2/OwcxVt7Ubdn0UD2kImNsclEQ9M8PpnevBX3mXlW2QnH8+Q+SC7JaMUc9CIvxB2HYQG2JujQf6skpVaPAKGxfLqDj+2UyTAVLoeUlQjc18swZVtTQO7Zwe6sTCYlrw7GpFXCAuI6Ex29gfeVIeB7pK7M4kZGy3OIaFxfTdevCoTMwkoPvJuRupA6ybp36vmLLMXaAWsrDHRUbKfE6UKvGoC9d5vqmKeIO9elASuagxjBJ"
invoke-direct {p0, v0}, Lcom/northpole/santaswipe/DatabaseHelper;->decryptData(Ljava/lang/String;)Ljava/lang/String;
move-result-object v0
invoke-virtual {p1, v0}, Landroid/database/sqlite/SQLiteDatabase;->execSQL(Ljava/lang/String;)V
.line 42
invoke-direct {p0, p1}, Lcom/northpole/santaswipe/DatabaseHelper;->insertInitialData(Landroid/database/sqlite/SQLiteDatabase;)V
return-void
.end method
We can see that there is clearly some data encryption going on. Having a look at the Bytecode of the entire DatabaseHelper
provides us with even more information about the encryption being used. The key elements are:
DatabaseHelper⚓︎
Encryption Key and IV Extraction⚓︎
.field private final encryptionKey:[B
.field private final iv:[B
.field private final secretKeySpec:Ljavax/crypto/spec/SecretKeySpec;
.method public constructor <init>(Landroid/content/Context;)V
.line 28
invoke-static {v0, v1}, Landroid/util/Base64;->decode(Ljava/lang/String;I)[B
move-result-object v0
iput-object v0, p0, Lcom/northpole/santaswipe/DatabaseHelper;->encryptionKey:[B
.line 29
invoke-static {p1, v1}, Landroid/util/Base64;->decode(Ljava/lang/String;I)[B
move-result-object p1
iput-object p1, p0, Lcom/northpole/santaswipe/DatabaseHelper;->iv:[B
.line 31
new-instance p1, Ljavax/crypto/spec/SecretKeySpec;
const-string v1, "AES"
invoke-direct {p1, v0, v1}, Ljavax/crypto/spec/SecretKeySpec;-><init>([BLjava/lang/String;)V
iput-object p1, p0, Lcom/northpole/santaswipe/DatabaseHelper;->secretKeySpec:Ljavax/crypto/spec/SecretKeySpec;
Decryption Method⚓︎
.method private final decryptData(Ljava/lang/String;)Ljava/lang/String;
const-string v0, "AES/GCM/NoPadding"
invoke-static {v0}, Ljavax/crypto/Cipher;->getInstance(Ljava/lang/String;)Ljavax/crypto/Cipher;
move-result-object v0
.line 336
new-instance v1, Ljavax/crypto/spec/GCMParameterSpec;
iget-object v2, p0, Lcom/northpole/santaswipe/DatabaseHelper;->iv:[B
const/16 v3, 0x80
invoke-direct {v1, v3, v2}, Ljavax/crypto/spec/GCMParameterSpec;-><init>(I[B)V
iget-object v2, p0, Lcom/northpole/santaswipe/DatabaseHelper;->secretKeySpec:Ljavax/crypto/spec/SecretKeySpec;
invoke-virtual {v0, 2, v2, v1}, Ljavax/crypto/Cipher;->init(ILjava/security/Key;Ljava/security/spec/AlgorithmParameterSpec;)V
invoke-static {p1, 0}, Landroid/util/Base64;->decode(Ljava/lang/String;I)[B
move-result-object p1
invoke-virtual {v0, p1}, Ljavax/crypto/Cipher;->doFinal([B)[B
move-result-object p1
new-instance v0, Ljava/lang/String;
sget-object v1, Lkotlin/text/Charsets;->UTF_8:Ljava/nio/charset/Charset;
invoke-direct {v0, p1, v1}, Ljava/lang/String;-><init>([BLjava/nio/charset/Charset;)V
return-object v0
.end method
Inserting Initial Data⚓︎
.method private final insertInitialData(Landroid/database/sqlite/SQLiteDatabase;)V
const/16 v0, 0x10e
new-array v0, v0, [Ljava/lang/String;
const/4 v1, 0x0
const-string v2, "L2HD1a45w7EtSN41J7kx/hRgPwR8lDBg9qUicgz1qhRgSg=="
aput-object v2, v0, v1
const/4 v1, 0x1
const-string v2, "IWna1u1qu/4LUNVrbpd8riZ+w9oZNN1sPRS2ujQpMqAAt114Yw=="
aput-object v2, v0, v1
// Additional encrypted strings...
return-void
.end method
At this point, I switch from Android Studio to CLI, because I need different tools to extract and view resources so we can actually find the encryption key and initialization vector.
java -jar bundletool-all-1.17.2.jar build-apks --bundle=SantaSwipeSecure.aab --output=SantaSwipeSecure.apks --mode=universal
unzip SantaSwipeSecure.apks -d extracted_apks
unzip extracted_apks/universal.apk -d extracted_universal_apk
apktool d extracted_apks/universal.apk -o decompiled_apk
cat decompiled_apk/res/values/strings.xml | grep -i "<string name=\"ek\""
cat decompiled_apk/res/values/strings.xml | grep -i "<string name=\"iv\""
<string name="ek">rmDJ1wJ7ZtKy3lkLs6X9bZ2Jvpt6jL6YWiDsXtgjkXw=</string>
<string name="iv">Q2hlY2tNYXRlcml4</string>
For GCM encryption, there's a GCM tag involved and we know ours is 16 bytes (128 bits) from the earlier DatabaseHelper Bytecode.
I head back over to ChatGPT to draft a script that will decrypt the encrypted data with the key, IV, and GCM tag information we need.
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.backends import default_backend
import base64
# Base64-encoded values
encrypted_data_b64 = "IVrt+9Zct4oUePZeQqFwyhBix8cSCIxtsa+lJZkMNpNFBgoHeJlwp73l2oyEh1Y6AfqnfH7gcU9Yfov6u70cUA2/OwcxVt7Ubdn0UD2kImNsclEQ9M8PpnevBX3mXlW2QnH8+Q+SC7JaMUc9CIvxB2HYQG2JujQf6skpVaPAKGxfLqDj+2UyTAVLoeUlQjc18swZVtTQO7Zwe6sTCYlrw7GpFXCAuI6Ex29gfeVIeB7pK7M4kZGy3OIaFxfTdevCoTMwkoPvJuRupA6ybp36vmLLMXaAWsrDHRUbKfE6UKvGoC9d5vqmKeIO9elASuagxjBJ"
key_b64 = "rmDJ1wJ7ZtKy3lkLs6X9bZ2Jvpt6jL6YWiDsXtgjkXw="
iv_b64 = "Q2hlY2tNYXRlcml4" # Example IV from earlier
# Decode the Base64-encoded values
encrypted_data = base64.b64decode(encrypted_data_b64)
key = base64.b64decode(key_b64)
iv = base64.b64decode(iv_b64)
# Separate the ciphertext and the GCM tag (last 16 bytes are the tag)
ciphertext = encrypted_data[:-16]
gcm_tag = encrypted_data[-16:]
# Initialize the AES decryption using GCM mode
backend = default_backend()
cipher = Cipher(algorithms.AES(key), modes.GCM(iv, gcm_tag), backend=backend)
decryptor = cipher.decryptor()
# Perform the decryption
try:
decrypted_data = decryptor.update(ciphertext) + decryptor.finalize()
print("Decrypted data:", decrypted_data.decode("utf-8"))
except Exception as e:
print("Decryption failed:", str(e))
python3 decrypt.py
Decrypted data: CREATE TRIGGER DeleteIfInsertedSpecificValue
AFTER INSERT ON NormalList
FOR EACH ROW
BEGIN
DELETE FROM NormalList WHERE Item = 'KGfb0vd4u/4EWMN0bp035hRjjpMiL4NQurjgHIQHNaRaDnIYbKQ9JusGaa1aAkGEVV8=';
END;
So now we just need to take that Base64 Ciphertext that was deleted and run that back through the same decrypt script we created!
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.backends import default_backend
import base64
# Base64-encoded values
encrypted_data_b64 = "KGfb0vd4u/4EWMN0bp035hRjjpMiL4NQurjgHIQHNaRaDnIYbKQ9JusGaa1aAkGEVV8="
key_b64 = "rmDJ1wJ7ZtKy3lkLs6X9bZ2Jvpt6jL6YWiDsXtgjkXw="
iv_b64 = "Q2hlY2tNYXRlcml4"
# Decode the Base64-encoded values
encrypted_data = base64.b64decode(encrypted_data_b64)
key = base64.b64decode(key_b64)
iv = base64.b64decode(iv_b64)
# Separate the ciphertext and the GCM tag (last 16 bytes are the tag)
ciphertext = encrypted_data[:-16]
gcm_tag = encrypted_data[-16:]
# Initialize the AES decryption using GCM mode
backend = default_backend()
cipher = Cipher(algorithms.AES(key), modes.GCM(iv, gcm_tag), backend=backend)
decryptor = cipher.decryptor()
# Perform the decryption
try:
decrypted_data = decryptor.update(ciphertext) + decryptor.finalize()
print("Decrypted data:", decrypted_data.decode("utf-8"))
except Exception as e:
print("Decryption failed:", str(e))
python3 decrypt.py
Answer
Decrypted data: Joshua, Birmingham, United Kingdom Poor Joshua!
If we put in the first name that we got from our script into the Objectives field for Mobile Analysis, the Gold Achievement is awarded!