In this challenge, we are tasked to exploit a path traversal vulnerability within a "Document Viewer" application then escalate the attack by leveraging a dynamic code loading scenario to perform remote code execution (RCE).
During the challenge, we are given a corellium instance to connect to – this is where we will be performing the exploit. But of course we want to test it locally so what we can do is to:
For static analysis, we can utilize tools such as jadx in order to review the decompiled source code. Then for dynamic analysis we can create our custom frida snippets.
During the initial analysis of an application, what we want to take a look first is on the AndroidManifest file since this xml file defines the components running behind the app which includes stuff such as activities, content providers, receivers, etc.
For the manifest file of document-viewer.apk, we can see that it only defines one (1) exported activity which has intent filters set to process deeplinks that open pdf files via http/s or the file:// protocol. We can create our test intents to this activity by performing the following adb command:
What we want to do next is to review the code on MainActivity, specifically the onCreate method since this serves as the starting point for an activity's lifecycle.
So there are a few things to take note of here. Other than the usual setContentView stuff which inflates the UI and sets some listener stuff for buttons the app calls three methods: handleIntent
, loadProLibrary
, and initProFeatures
in particular order.
The code for handleIntent parses if the received intent uses the android.intent.action.VIEW
action and that the intent data is not empty. If it passes this check, it calls the copyFileFromUri
method from the CopyUtil
class then continues with renderPdf
. Since there's nothing interesting with renderPdf, we will continue with analyzing how the copy logic for the PDF files received is implemented.
Now this is where it gets interesting. The uri parameter comes from the intent.data that we provide and if we follow the flow we can see that a new file will be created on the Downloads directory (/storage/emulated/0/Downloads
) then uses the getLastPathSegment() result as the filename.
This is dangerous since the getLastPathSegment
method retrieves the decoded last segment in the path which means that if we provide a path like /..%2f..%2f..%2f..%2ftest
it will return ../../../../test
thus highlighting our directory traversal attack path. To demonstrate the behavior, I created a custom frida snippet to monitor when the copyFileFromUri method is called and what file(s) are created then tested it by sending a sample intent with our controlled input data.
If we review the results from the screenshot below, we can see that our intent data uri triggers a file creation using the following filename: /storage/emulated/0/Download/../../../../../directory-traversal-baby
. This means that we have the capability to control the filename and the directory to where we will write data.
Now that we have confirmed that we have arbitrary file write using the path traversal vulnerability, the question next is what do we want to (over)write. This is where we will need to continue analysis on the loadProLibrary
method.
So what this method does it it loads an architecture-specific version of the docviewer_pro.so from its directory then proceeds to load the native library via the call to System.load
.
getLastPathSegment
path traversal vulnerability in order to write our malicious .so binary into the /data/data/com.mobilehackinglab.documentviewer/files/native-libararies/<device-arch>
directory so that when the app loads the native lib on the call to loadProLibrary
then our payload will get executed thus allowing us to achieve RCE on the system.
To serve the files that I'll be needing for my exploit I spun up a quick digital ocean droplet:
First we need to re-confirm that we can write arbitrary files into the document viewer application's private directory (/data/data/com.mobilehackinglab.documentviewer).
The adb payload above starts the exported component to load our attacker-controlled intent data then attempts to write the test-write file hosted on our droplet into the vulnerable application's internal directory which as we can see on the result screenshot below has been successful.
If we take a step back and re-review the onCreate code from the MainActivity, we notice the following block:
The intended technique was to implement the initProFeatures() method in the native library payload so that it gets called upon succesful loading of the native lib (check the loadProLibrary method code a few paragraphs above) .
But ehhh why wait for a specific method to be called when you can just override the JNI_OnLoad method which automatically gets called when the native lib is loaded. So I continued to start a new native android studio project and developed the following code to execute our reverse shell payload:
might need to adjust vid quality hehe