I participated in the TCP1P ctf this past week and was intrigued by their set of mobile challenges which I mainly focused on and got first blood🩸on the challs I solved. On this writeup, I'll discuss the solutions for both solved and (may be a bit delayed) unsolved mobile challenges.
Been a while since I last played ctfs, but this seems like the first time I've seen mobile challenges deployed like this because normally Mobile challengest mostly revolved on reverse engineering the app or native lib. Hopefully, other ctfs will follow suit and implement more mobile exploitation challenges since they are very fun to do. For reference, the TCP1P team has released the infra + source code on their github repo.
Fig. 1 Main Dashboard
Fig. 2 Device Shell
Fig. 3 Web Emulator
While promising, there were still some stuff that can be improved regarding the mobile infra. For the purposes of this writeup, I'll replicate the challenges using my physical device (Xiaomi Redmi 9c).
During the ctf, we were given access to the vulnerable apk and an instance to run the exploit on. In solving the challenges, we were tasked to create an android application exploit to attack a vulnerable component within the target app. To mirror real-world scenarios, we should note that the device is operating in a non-rooted environment.
We load up intention.apk
into jadx-gui
and start our analysis by going over the AndroidManifest.xml
file which basically serves as an overview to the structure and behavior of the app.
As we can see with the screenshot above, there's an interesting activity called com.kuro.intention.FlagSender
that comes with the exported attribute set to true – this means that any application installed within the device will be able to call/start this activity.
The code for the FlagSender activity is actually straightforward. First, it opens the flag file from its internal files directory via openFileInput
and then sends its contents via a string intent to the calling activity through the setResult call.
In Android, the setResult method is used to send a result back from a secondary activity to its parent activity when the secondary activity has finished its operation. This result typically includes data or information relevant to the parent activity, and it allows for communication and data exchange between the two activities in the context of a user interaction, such as when one activity is started with the startActivityForResult method.
So the attack path here is clear, we need to create an exploit application that starts the FlagSender activity (given that it is exported) then retrieves the flag from the string extra returned by the setResult call.
The idea for the exploit is quite simple, we just start the activity, receive the intent, parse the flag data then place it into a textview using the default setContent template from compose. Since the FlagSender activity does not call finish(), we need to manually press the back button in order to return back to our calling (attacker) activity.
Upon reviewing the AndroidManifest file for the application, we only have one (1) activity in-scope which is the application's MainActivity.
Taking a look at it, we can see that after it checks if some permissions regarding managing/reading external storage has been set, it proceeds to call the openImage() method which performs a startActivityForResult
call using an implicit intent via the intent action android.intent.action.PICK
:
Now might be a good time to discuss that android intents are categorized as either explicit or implicit:
1. Explicit Intents
An explicit intent is one that you use to launch a specific app component, such as a particular activity or service in your app. Notice that the Intent()s being created specify which activity to open/use. For example:
2. Implicit Intents
An implicit intent specifies an action that can invoke any app on the device able to perform the action. Using an implicit intent is useful when your app cannot perform the action, but other apps probably can and you’d like the user to pick which app to use. For example:
Using an implicit intent can be dangerous, since these can be intercepted by malicious applications within the device. The risk is heightened when the intent itself contains sensitive information or if a vulnerable unsafely handles the data received from other applications.
In the case of imagery.apk
, the latter is evident given that on the onActivityResult
handler we see that it performs an openInputStream
call from its content provider using data that is received from the result of whichever application handled the startActivityForResult
call.
What this means is that a malicious application installed within the device could intercept the implicit intent using android.intent.action.PICK
then control the intent data parameter it returns in order to open any arbitrary files from within the imagery application's internal (/data/data/com.kuro.imagery) directory.
When creating the exploit code for the vulnerability, we need to take note of the following information:
android.intent.action.PICK
and specified a mime type of image/*
. We can also set the priority to 999
which isn't really important for this challenge, but on real-world engagements this could allow our attacker application to appear first on the list if a chooser dialog opens for implicit intents.This challenge chained multiple vulnerabilities which is why its pretty cool that I drew first blood on it.
When reviewing the manifest file above, we take note of the following components:
grantUriPermissions
attribute set to true. File paths are defined via @xml/provider_paths.The most interesting thing that stands out is the exported WebviewActivity which we'll analyze next:
As we know by now, an activity being set to exported means that it can be accessed by other applications within the app. This doesn't necessarily mean that being exported is a vulnerability, given that most applications export specific functionalities so that other applications can interact/share with their data. But in the case of webviews, yea you shouldn't let anyone open up arbitrary webviews as we can see in the pattern below:
Within the WebviewActivity's onCreate method, we can see that the webview opens up a url supplied by an external intent via the string extra "url". This in itself can already be classified as a vulnerability since unauthorized applications should not be able to start up arbitrary web pages. We can verify that we do have the capability to do so by performing the adb command:
But what makes this more interesting is that the webview client has some additional methods defined – javascript interfaces:
JavaScript interfaces in Android WebViews enable communication between JavaScript code running in a WebView and the Android application. They allow JavaScript to call Android methods and vice versa, facilitating interaction between web content and native Android functionality.
This means that if we can somehow open up an arbitrary url in the webview which contains javascript, we can easily interact with the code defined in the application's exposed interfaces. In this case, the js interfaces do the following:
Having the capability to supply an intent object and start the activity pointed to by the intent is a recipe for access to non-exported components. Oversecured has a pretty cool article to demonstrate the attack flow.
Looking back from the defined components in the android manifest, we might want to begin analyzing the non-exported PickerActivity now that we have a way to start arbitrary components.
Similar to the previous challenges, this one uses the same pattern of using an implicit intent, setting the intent data blindly from the received intent via getIntent().getData() before passing it as an extra to the new intent which is sent on the startActivityForResult call.
Additionally, we should review lines 18-19 from the code. Setting the 0x1 flag on the intent corresponds to granting the FLAG_GRANT_READ_URI_PERMISSION
on the data received from getData. This means that if we supply a file path from the netsight application's file provider, we can have read access to the files that we want.
For this, we want to review the @xml/provider_paths.xml
file which defines which directories or files could be shared by the content provider:
The internal_files path is misconfigured since it allows arbitrary access to files under .
which is the internal directory for the netsight application.
Now that we have most of the ingredients for our exploit chain, let's get cooking.
For the first stage of the exploit, we want the netsight application to open up an attacker-controlled webpage and then execute the accessDeeplink
javascript interface which includes our intent deeplink payload to start the non-exported PickerActivity.
You might ask: "Wait, no deeplinks were defined via the intent-filter stuff, what deeplink do we provide then?". In Android, we can specify deep links using the intent:// scheme. This scheme allows us to create Intent objects and supply intent data in a flexible and dynamic manner. I used the following code to generate what deeplink I would need to supply in order to call the PickerActivity and control the flag data extra:
Result from logcat:
From this point, I spun up a quick web instance via glitch which contained the following script.js
file:
After which, we send the payload to start the exported webview activity and open up the url we supply:
If everything goes smoothly, the netsight webview will basically call the following:
The picker activity will then perform a start activity using the implicit intent action android.intent.action.PICK
, so we need to setup another activity for our exploit app in order to intercept the call and the intent data that we need from it:
Since the PickerActivity from netsight will be sending an intent with the structure of content://com.tcpip.netsight.FileProvider
, I found out it was better to define an intent-filter this way in order to automatically intercept the intent.
Once the PickerActivity starts the activity, we should be ready to receive the intent then retrieve the contents of the file pointed to by the intent.data since we should already have read access to it:
The beauty in action:
I didn't solve these during the ctf but I did learn a lot from the research process and reviewing the intended solution afterwards. Currently, I haven't prepared a writeup yet but I should be able to finish it within the week. Follow me on my socials to receive updates :)