 Hi everybody, my name is Kyle Banak. You might know me as B3Nak on Twitter, and I'm super stoked to be presenting my Android Exploitation Talk here at DefCon Red Team Village. Try saying that three times fast. I also want to thank Joseph and Omar for inviting me. I'm a full-time security researcher, and in my spare time, I really enjoy testing Android applications. This talk is in targeting any specific skill level, and I hope all skill levels find the information in this talk useful. Also, I'll be focusing on the application level vulnerabilities and the Android ecosystem. So let's get the party started. Right off the bat, I just want to show all the exciting things we have in store today. Here's my Xmime map for Android attack vectors. We're going to be covering a little bit of each of these main subjects. Everything from exported components, web views, deep links, third-party SDKs, third-party plugins, APIs, insecure cryptography, and insecure data and file storage. I'll be focusing more on deep links just because I feel like this is an area that isn't researched as much, and the possibilities for deep links are honestly endless because they can be chained with all kinds of other different vulnerabilities. They behave the same way as exported activities in a way where they take users directly to an activity, and they're honestly just a ton of fun, and I'm excited to share my research with you. Also, this Xmime map will be available after the talk. All right. Everybody has a methodology, so I want to go over my Android methodology a little bit before we get to the fun stuff. First is the reconnaissance phase. During this phase, I'm going to try to detect which framework is in use, whether it's native, flutter, or react native. If there's any exported activities, what if there's any custom deep link schemas or regular deep link schemas? If there is any third-party plugin integrations or SDK integrations, as well as API keys. So this is pretty easily done with the Android manifest.xml because the Android manifest.xml is basically a blueprint for the entire application. And I'll show you what key phrases that I look for, key entries, in Android manifest.xml, as far as figuring out what framework is in use. Next is preparation phase. So using an objection, you can bypass most application, most native and react native applications SSL. Unfortunately, it doesn't work for flutter because flutter uses a different SSL library called born SSL. I will also show you what I use for split ABKs because every once in a while, you'll run into a split ABK. Next is attack vectors. And this is what I'm going to spend most of my time on. I have an example of WebView XSS for native and flutter as well as some deep link examples. There's all kinds of awesome deep link shenanigans to be had in combinations with other vulnerabilities. Next is Unicode collisions. I haven't found any write-ups yet that show that Android apps are vulnerable to Unicode collisions, but they definitely are because Java methods and Kotlin methods both have Unicode mapping collisions. And I will show examples of that. Lastly, validation bypass via flutter route. And that's just an interesting implementation error that can lead to random results. Now let's talk about the ecosystems. It's important to understand the ecosystem of the different frameworks just to kind of figure out where you're going to go next as far as explaining the application or trying to find other bugs that are worth a bounty. Native apps, the code base will be mostly Java or Kotlin with shared object files if the NDK is in use. They're easier to decompile because there's like a ton of tools out there that decompiles the code into readable format. It's easier to read and statically analyze after it's decompiled. And it imports Android X libraries. Those Android X libraries are the newer libraries for native applications currently. Flutter uses Dart. It's created with cross-platform compatibility in mind. So when you install Flutter, you're installing this huge tool set where you code it once and you can deploy it basically to any platform. Production binaries are not easy to reverse engineer because it's compiled to a .so file. The debug APKs create a bin file which is easy to find the source code. It's just as easy as a grep command. And then you get the Dart source back. The APKs tend to be bulkier unless you optimize the deployment to specific architectures. For React Native, these applications are mostly written in JavaScript than rendered with native activities. And a lot of the plugins are under the com directory. Moving on to detecting native Flutter and React applications. For native, usually there's a lot of Android manifest entries. And most of the activities are easy to find in the com directory. I do this a lot with JADX where I'll find a path in the manifest and just trace it to the decompiled source code. For Flutter, Flutter has Flutter.embedding entry in the Android manifest. There's also a folder called io.flutter in Flutter applications. There will be shared object files in the lib directory. In the release, there's libflutter.so and libapp.so. If it's a debug Flutter application, you'll find in the Flutter assets directory kernel blob.bin. And this is the file where you can grep for the Dart source code. But it's not very likely you'll find one of these in production. For React Native, the entry com.facebook react in androidmanifest.xml is a key indicator that React Native is in use, as well as a dev settings activity. In the assets folder, you'll find an index Android bundle file most of the time. And this is all the JavaScript code that hooks into the Native activities. For the NDK and JNI, for the apps that use these, there's a shared object created in the lib directory. You will find JNI calls in those shared object files. And then that will, for sure, tell you that the NDK is in use. And you can decompile these shared object files with Gijia or Dari2 and stuff like that, using an objection for SSL pending bypasses. So I use this script to automate the process of pulling the APK off of my device, to my computer renaming it, and opening JATX on the fly for a quick static source code analysis. Then after I have the APK in that same directory, I'll use Objection to patch the APK. And what Objection does is it creates another APK with an agent loaded. And it tries to inject that agent into a launcher activity. So when you launch that patched APK, it'll pause execution. And when you use Objection Connect, you'll connect to that gadget. And from there, you can bypass SSL pending. If it's a split APK, then you can use patch-apk.py to patch the split APK and then save it as whatever name you want to. And that Python script actually runs on top of Objection. So if you have Objection installed, you're good to go. And this works on React Native and native apps. Demo time. So what I have here is an app that I own. And I'm going to show you how to do this for the most part. On the app, I own just so I don't make anybody angry. So what I'll do is, so what I have is that script is alias to auto APK. And then all I have to do is put in the name of the app. And then from here, it'll do the rest of the work for me. Pulls it, puts in the directory I'm in. And then all I have to do is use my other alias to command to patch it. So this will install the gadget version I specified. While it's doing that, I can uninstall this version of the app because I have it automated to the point where it'll install the patch version on my device. And you can customize this Objection patch APK command, however you like. Because sometimes the process will work without skip resources and skip native libs. And sometimes there's entries and Android manifests that throw off the process and cause errors and stuff. So it all depends on the application. Looks like it's done loading the agent, I mean, the free to gadget. It re-signs the APK and puts it back in APK format and installs the APK on the device. So since this APK doesn't have an SSL component to it, it won't freeze it on startup. But production apps will stop execution because it will target the launcher activity. Bypassing Flutter SSL pinning. So this is an entirely different beast in itself. So Flutter uses Dart programming, I should say, the Dart programming language, which has its own key store. And the SSL libraries are completely different. It uses boring SSL to handle all of the SSL related functions. There's two main ways to bypass SSL pinning locally. And that's to find the SSL verification function in libflutter.so and change the response to true with a free to script. If the plugin is used, then it's way easier to hook a plugin that is using SSL pinning than it is to decompile libflutter.so, find the offset to this exact method. Trust me, I was trying to find the offset for a specific architecture, and it wasn't super easy. And I also have problems with compatibility with VMs. So I'm going to show the second plugin bypass method. And later on, maybe in a YouTube video, I'll show the first one. And I want to say thank you to Drowan Becker's for creating the free to script and finding the Java method being used for SSL pinning in this particular plugin. So running this command with the free to script waits for the method to execute. And then once it executes, it changes the response and route to true. And I will show you exactly what I mean by that. So I have this exercise program into Android that has the SSL pinning plugin. And so if we use, so if you use this command, free to dash u dash f, this is actually in this one. This app loading the script. This will restart the app, I believe, because it injects the thread. So once the app is restarted, you'll see it doesn't matter what I put in for these. But just for demo purposes, it intercepts the pinning request every single time. And just to show you what happens without this, I'll exit out real quick. And I'll go back to the activity. So without running the script, it actually does what it's supposed to do. So I put that in there. Whatever fingerprint, it'll fail with the Java exception. There's that. Technical details of the free to script. So this is, from my understanding, the free API calls in use are Java use and Java perform. And Java use wraps the library in an object that can be used to modify calls to that library during runtime and return what is specified in the function. Java perform ensures the call is attached to the current running thread for the function disable pinning. And I believe that's why it restarts the application when you load the script. Third-party SDK and API exploitation. I can't emphasize enough the importance of reviewing third-party documentation to discover potential bugs. So demo apps make great POCs. And almost all third-party services have demo apps. There has been so many. There has been numerous times where I'll be looking into a third-party integration and there's a demo app that shows how to quickly access the API. But it doesn't check if it's an official app, if it's the official production app that's accessing it. So it's basically just a doorway into the production app. And it's even better if you can disclose sense of data via some API call after you've SSL pinned the application. Mobile endpoints sometimes have separate APIs, like completely different ones that aren't designed the same exact way. And there could be different server headers or there could be calls without a CSRF tokens. There could be calls with just one authentication header for all the calls. And sometimes you can even find the token and the Android app and use it to potentially elevate privileges and disclose more data. So there could be a default one that's just set to the user's account. But then you can find another token that you can use in strings.xml or something like that. And lastly, I've found this in an engagement where an s3 bucket keys were in strings.xml. And I could upload and read the data in that bucket. I can see what files there were and bug. So there's stack traces, all kinds of different files in that s3 bucket. And all I needed to do was add the keys in the bucket name to AWS Clive, the command line. Yeah, so there's all kinds of stuff that you can find as far as API, exploitation, and third party SDK. And there are some third party integrations that you can actually overwrite certain parameters and perform link poisoning, which leads to spear texting. I like to call it because I've literally been able to change the links in text messages to go wherever I wanted and not where. But it was under that company's name. External file storage. So external file storage is essentially public on that device. And any app can access external file storage. Also, it's important to note that when you delete an app and that app saves data to external file storage, it doesn't disappear off that phone. So a third party app could scrape data from external file storage. Literally, these directories and upload them to wherever they want. Hypothetically, I would like to note that external storage behavior changes in API 29 plus because they scope it per user and per app. And shared storage has to be specified in order for files and data to be truly public. So it's a lot more stricter in the newer APIs. And these environment.getExternalStorage calls can easily be automated into a recon script. Native WebViewExcess. So WebViewExcess can be chained with deep links. So this can turn a quote unquote self-excess into a reflected excess. The identifiers for vulnerable web views are similar between native and Flutter apps. By default, native and Flutter apps aren't susceptible to excess unless JavaScript is enabled. So for example, I have examples here for Java, Collin, and Flutter. They all look pretty similar. And as you can see, JavaScript is enabled. And the other main part is that user input isn't escaped. Flutter WebViewExcess via plug-in. The official Flutter WebView plug-in doesn't support alerting functions, but the development one does. And that's the interesting part is that the plug-in ecosystem kind of introduces vulnerabilities in a similar way that Wordpress environment does. And the way the values are handled is slightly different because the Dart programming language is used. So firstly, the form value is transferred to a WebView with a navigator push material page route. Secondly, the WebView, where the value is going, has with JavaScript true set. And lastly, the user input value isn't escaped. And it's executed with eval JavaScript, which is built into the development plug-in. And now it's demo time. So I have an exercise for this, too. Let's just say this application doesn't filter or escape what the user puts in as a username. And I almost put that backwards. So you can put whatever you want for the password. After the page loads, let's go to the profile. And by the bing, by the boom. We have stored XSS. So then you can see here, so if you see eval JavaScript in a Flutter app, as well as JavaScript sets a true, it's definitely vulnerable if the development plug-in is in use. Native deep links. Deep links are specified in the Android manifest. And they can have a custom or a web schema. Also, you can tell which activities have deep links by the intent filters. In the example below, there's a scheme of flag 13 and a host of RCE assigned to RCE activity. What this means is that a deep link will send a user directly to RCE activity as long as those conditions are met. And I'll show you exactly what I mean by that. So if I use this ADB shell example test command on Android Android, it'll send me directly to that activity. And that's the exact definition of a deep link is it sends a user directly to a certain part of an Android application. Flutter deep links. So Flutter deep links are implemented via the plug-in unilinks. And they are handled pretty much the same way as native applications where the plug-in intercepts the deep link and then routes the user to wherever the deep link router for the application wants or is it however it's implemented, it'll send the user to that specific area. The Android manifest also has specific parts of that application that's assigned to the deep link. And the main thing is they just look a lot different. The implementations just look a lot different because of the Dart programming language and the async calls. Native deep link to reflected XSS. This is where the fun starts. So we're going to use a deep link and a vulnerable web view to basically create reflected XSS in a mobile application. So there's a couple things that preconditions that we need to have. It's a custom schema. So that way we know it's going to the mobile app. And the web view needs to have JavaScript enabled. And the example below shows that JavaScript is enabled for the web view. And as long as the intent doesn't equal null, it's going to load the data from the query parameter totally secure and load it into that web view. And it's demo time. So the way you make deep link POCs is with HTML files. So I'll just show you that it's just the same exact thing as a hyperlink, except for deep links or hyperlinks for mobile apps, basically, that take users directly to a specific part of an application. And then I'm going to push it to my device with ADB push, the deep link, file name, and for simplicity, just stcard download. So now if I go to downloads and open the file, you can use any browser you want. It'll load the HTML file. I'm just going to zoom in, click on this, load in the web view, and it'll execute the JavaScript. And this is the vulnerable web view I used. This will also be a part of one of my other examples. Native deep link to LFI. Depending on how the deep link is implemented, it's definitely possible to include files on the device that aren't meant to be included. I have actually found a deep link LFI and was awarded for it. So that inspired me to create a scenario where this is actually possible. It's more likely that production apps will use a file provider or a PDF reader to do this. But still, for POC purposes, this is pretty fun. As you can see from the code example below, the intent is sending a parameter called totally secure LFI, which is what we want to intercept and what we want to take advantage of to read files from the assets directory. So it's going to read whatever file we put as that parameter, totally secure LFI, and log it to the text view. It's demo time. So I have a totally separate POC for the LFI that I will show everybody real fast. As you can see, it's the same deal as a hyperlink, except for it has the custom schema of XSS, the host of whatever, and the parameter of totally secure LFI, which equals the file we want to load into the web view. We'll push this file once again to our device, to SD card download. Now we'll open the deep link. Actually, let's close this and go back to the files, open it in the browser. It will load the contents in the web view. More fun with deep links. So I was curious if I could get remote code execution or command execution in general with a custom go binary. Turns out I was able to get code execution and remote code execution via deep link with this specific scenario. So first I establish the process, and I get the runtime of the custom go binary, which is in the files directory of the application. The intent param is the go binary name, and the binary param is the command that the binary has programmed as an option. Next I make sure that I define a buffered reader to read the output for the command, and also a string builder so I can put the command output into the string builder, and then the buffer reader for each line will use the string builder to append the output to it. Well, until the process finishes with process wait for, then once the process finishes, the string builder, also the log variable, will draw or print all the strings to the text view, which is defined as TV. First I implemented this in Java, and then I figured out how to do it in Kotlin. The entire process was a lot of fun, and I'm really excited to share it. Without further ado, it's demo time. So first I thought it'd be really cool just to see what the output should be. So let me exit out of this. You can connect to AVM and phones via ADB once USB debugging is enabled and establish a shell. So I'll show you how to do this. Using ADB shell, we can navigate to this application's files directory where the binaries are, and then the binaries that execute the commands is the narnia.x86 underscore 64, because that's the architecture for this VM. So if I want to execute this binary, as long as I have permissions to execute it, I can just do that, and it says the parameter is needed. I remember programming a help parameter. So the commands available are test1, rick, test2, potato, and test3. So let's try test1, it's treasure. OK, cool. Test2, underscore, test3, so treasure planet. And just for fun, let's do what rick is. So rick is definitely the riproll YouTube video, because you have to have some fun while programming CTF exercises. So now that we know what the expected output is, let's try to execute commands via deep links. Clear the terminal, and I will show you my POC HTML file. So as you can see, this particular activity has the schema flag13, a host of RCE, the parameters binary, and param test1. Well, so the parameter called param is actually the binary command. And so how to be able to make a deep link POC, I will show you exactly where to look in the application itself. So if I open Android manifest for Android Android right here. So the RCE activity is the one that we're looking for. So this is how, if you were looking to make deep link POCs, you would look for activities that have this intent filter. And then you can find out what the scheme is and what the host is. Now that I know what the scheme is and the host is, it's very valuable to see also what the activity has, because that could also disclose the params involved for your, and it could disclose what params you can test out. So if you do shift shift in Android Studio you can go right to the activity. So that's what I'm going to do. So the params that were used for this were binary and param. So sometimes you can find like specific parameters that the activity is looking for and use those to your advantage and find bugs. Now back to the demo. So we just have a normal blank RCE activity here. You need to push this POC to the device itself. And we'll push it to SD card download just for simplicity. Now what we want to find is, there we go, the RCE POC.html. You can open it with whatever browser you want, zoom in. And with Android Studio VMs, it's control and then dragging the mouse. So let's try the test one command variable. That's awesome, but it output exactly the same as the terminal. Test two underscore test three, plant. And this should solve the flag for the exercise and also redirect the user. So yeah, I needed this to say, looking for intent params and the source code can disclose ways you can navigate to other parts of the application or ways you can find bugs and sense of data. So as an example, when the application is getting parameters, that's prime for you should append those to your deep link POCs, just like this. Now for Unicode collisions, this is kind of uncharted territory as far as Android applications go. So Java and Kotlin both have Unicode mapping collisions. And it just depends on how the application is using Unicode. This is prevalent in email. So password reset functions, for example, could exist in a database that converts certain Unicode, converts Unicode or uppercase characters, or converts Unicode uppercase or lowercase characters. This script right here just shows a simple way that I was able to confirm that the Unicode collisions did exist in Java. And as you can see here, first I just tried to string to lowercase and uppercase the Unicode B character. And if it equal double S as a to uppercase, it printed true. And then if the next one, I tried the email from the writeup that I was reading, which is included in my resources to uppercase, equaled what the POC was in that writeup, equals true, which it did. So needless to say, Unicode collisions do exist in Android applications that use Java or Kotlin. Just depends on the implementation. I also included a Unicode collision example in Kotlin just because I don't think there's very many examples out there currently. And this is actually one of the examples in Android as well. So this code is taking the post string value and converting it to all uppercase characters. Databases sometimes to uppercase or to lowercase values to keep data consistent. So this is a very realistic test case that is worth testing or I guess worth putting in your workflow for every application. And it's important to note that Unicode collisions can affect mobile to web interactions, especially if the mobile app is connected to a database that I haven't tried it out. I haven't tried out Unicode collision yet with Firebase. But definitely with certain types of databases, Unicode collisions could affect them. And there was one instance where I saved my username as a Unicode character. And I wasn't able to even run the Android app after that. And the only way I could get access back to that specific account was to change my username from a Unicode character back to a regular character. So that's also important to remember that it could happen for production apps. Authentication bypasses via insecure Flutter material page route. So navigator.push material page routes behaves similarly to deep links except, if not implemented correctly, could bypass application logic and validations The example below is an example of a correct implementation. You want to put these navigator.push routes after all of the form inputs. If you don't, I will show you what exactly happens. And it's kind of surprising. So this is an example of an insecure material page route and implementations that are like this can possibly have unexpected behavior and could leak sensitive data. For example, if this was a login form connected to a database and for whatever reason only the username was needed to link to the data for the user and it didn't check the password and pass validations because the navigator push wasn't after the form inputs, then that could be all bad. And it's demo time. So I have an example in here of an insecure navigator page route, which could potentially be an off bypass. So you'll notice if I push sign up, it says please enter your password and username. If I just put password, it says please enter a username. I just put a username. It takes me. It automatically brings me to the next page and doesn't really bother checking to make sure that my password was correct. So it's just another weird scenario. I guess I should say odd scenario where an implementation of a material page route located in the wrong spot could bypass validation. And this is all because of the material page route being after the username value that was entered. So I'll show you what exactly I mean. The material page route for this scenario is only validating that the username is not empty. And if it's not empty, then the page route, these navigator push, basically, they're just like, all right, cool. Everything before this being executed is good. So I'm going to send you to the next page, which is the home page. And the validation ends down here. So ideally, the navigator page route should be after all of the form key or all the validation takes place. Easily bypassable and broken encryption. So plugins for Cordova and Flutter are more susceptible to this. Any CBC cipher with a static initialization vector and hard-coded key. Hard-coded XORs or XOR bitwise operators can be found in assembly. So it's important to note that you potentially could find keys that are hard-coded and shared object files. It's going to be a little harder to identify, though, depending on the implementation. But decompiling those shared objects with Ghidra or Radar2 will definitely show you any strings. So these two code examples are examples I've found in a Cordova app or not a Cordova app React Native. And I believe that they use a Cordova plugin. And so in my opinion, there was really no point to use a plugin that encrypts data and decrypts it at runtime if you have a hard-coded encryption key. Because once you find the hard-coded encryption key, it's basically all the protection from encryption your data is gone. So when I was testing this application and I found this, I was able to decrypt the data pretty easily. Luckily for them, there was nothing sensitive, which also made no sense to me. I wasn't sure why they would encrypt data that wasn't sensitive. But yeah, if you find a plugin that uses a hard-coded key and doesn't change it upon every use and the hard-coded initialization vector, there's really no point. Because somebody can decompile the app and then use that key to decrypt all the data. And I'll show you the script I used to do that on the next slide. So like I said before, set it, code analysis may reveal the encryption keys. Then you can leverage them to decrypt potentially sensitive data with a Python script just to make it easier. Because it's easier than making a proof-of-concept app. Because in this situation, yeah, for encryption, making proof-of-concept apps would take a lot longer. Because you really just need to decrypt the data that's encrypted. You can also use a language of your choice to decrypt this. But this example is in Python, and it was pretty easy to write up. Reversing an XOR example, there's only two steps in the process of reversing XOR encryption. One, the XOR key. Two, applying the XOR key again to get the original data. So once you find an XOR key, or if you think you have an XOR key, just take the encrypted data and try applying the script to it. And you'll know pretty quickly if it was XOR encryption being used. It's also good to note that to keep a look out for any byte arrays, because byte arrays can be converted back to plain text pretty easily as well. And I have an example of that in this script. Because in one of the exercises for injured Android, there's a byte array that is supposed to be a clue to how to solve the actual flag. So I'll show you what I mean by that. So this is kind of a bonus demo time. But in this scenario, there's a byte array, which can easily be converted back to plain text. I'll show you how to do that right now. So if we run the Python script that's in the slides, it gives back the encrypted string, the decrypted, or the byte array, which is here. Yeah, so when you apply this XOR to win, it turns into this. And then when you apply the XOR to this, it turns into win. Yeah, and then I'll go over some further technical details of how I implemented this activity. After installing the native development kit, you can create shared object files or native libraries with C++ and C. And these can be used for a number of reasons. You could actually probably just program the entire Android application with JNI and the native development kit. It all depends on what your language or preferences. Anywho, every JNI call for the shared object file will look something like that. It'll have these attributes declared. One interesting fact is that there's still a bug with this specific function, new string UTF, where if it returns a Unicode character above a certain number, it will crash the entire app. Now I'll show you how the shared object file is initialized and used. So in the assembly activity, which is programed in Kotlin, this is where I have a companion object that initializes the native library by using system load library. In order to use the function, I had to declare this function as external and it has the same name as what's in the native lib. And this is important to note because while you're looking through assembly, you can track down the actual call faster if you know the method or the function being used. Now that we're looking through this, you can see that XOR string is the result of this method. And then that's one way we know that that method was intended just to return a specific value or specific data. And then XOR string is being converted to UTF-A. The bytes are being stored to a byte array so we can print it to the text view with bytes.content to string. And that's why you see the byte array on that specific exercise or CTF exercise. Thank you, everyone, for listening to my talk. I am super grateful to have been given the opportunity to present here at Defcon Red Team Village. I highly recommend you to visit Defcon Red Team Village I highly recommend you check out the OSP Mobile Security Testing Guide if you want to get into mobile security. And I will be sticking around for questions and answers in the Discord channel. Also feel free to message me on Discord or Twitter whenever. And I will try to answer any questions you may have. Thanks again. Until next time.