1 00:00:00,000 --> 00:00:11,980 My name is Kyle Benack, you might know me as b3nack on Twitter, and I'm super stoked to be presenting my Android application exploitation talk here at DEF CON Red Team Village. 2 00:00:11,980 --> 00:00:14,100 Try saying that three times fast. 3 00:00:14,200 --> 00:00:19,140 I also want to thank Joseph and Omar for inviting me. 4 00:00:19,380 --> 00:00:25,760 I am a full-time security researcher, and in my spare time, I really enjoy testing Android applications. 5 00:00:25,760 --> 00:00:33,680 This talk isn't targeting any specific skill level, and I hope all skill levels find the information in this talk useful. 6 00:00:33,720 --> 00:00:41,680 Also, I'll be focusing on the application level vulnerabilities in the Android ecosystem. 7 00:00:42,200 --> 00:00:45,540 So, let's get the party started. 8 00:00:46,120 --> 00:00:51,260 Right off the bat, I just wanted to show all the exciting things we have in store today. 9 00:00:51,260 --> 00:00:54,460 Here's my xMyMap for Android attack vectors. 10 00:00:54,460 --> 00:00:57,900 We're going to be covering a little bit of each of these main subjects. 11 00:00:57,940 --> 00:01:11,980 Everything from exported components, webviews, deeplinks, third-party SDKs, third-party plugins, APIs, insecure cryptography, and insecure data and file storage. 12 00:01:12,280 --> 00:01:28,660 I'll be focusing more on deeplinks just because I feel like this is an area that isn't researched as much, and the possibilities for deeplinks are honestly endless because they can be chained with all kinds of other different vulnerabilities. 13 00:01:30,360 --> 00:01:44,460 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. 14 00:01:45,060 --> 00:01:49,300 Also, this xMyMap will be available after the talk. 15 00:01:50,280 --> 00:01:51,280 Alright. 16 00:01:52,800 --> 00:01:59,660 Everybody has a methodology, so I wanted to go over my Android methodology a little bit before we get to the fun stuff. 17 00:01:59,880 --> 00:02:01,820 First is the reconnaissance phase. 18 00:02:01,980 --> 00:02:08,720 During this phase, I'm going to try to detect which framework is in use, whether it's native, Flutter, or React Native. 19 00:02:08,720 --> 00:02:25,900 If there's any exported activities, if there's any custom deeplink schemas or regular deeplink schemas, if there's any third-party plugin integrations or SDK integrations, as well as API keys. 20 00:02:26,680 --> 00:02:37,560 So, this is pretty easily done with the AndroidManifest.xml because the AndroidManifest.xml is basically a blueprint for the entire application. 21 00:02:38,520 --> 00:02:49,700 And I'll show you what key phrases that I look for, key entries, in AndroidManifest.xml as far as figuring out what framework is in use. 22 00:02:49,780 --> 00:02:51,900 Next is the preparation phase. 23 00:02:52,600 --> 00:03:01,660 So, using Objection, you can bypass most native and React Native applications' SSL. 24 00:03:02,360 --> 00:03:08,100 Unfortunately, it doesn't work for Flutter because Flutter uses a different SSL library called BornSSL. 25 00:03:08,140 --> 00:03:16,260 I will also show you what I use for split APKs because every once in a while, you'll run into a split APK. 26 00:03:17,800 --> 00:03:19,880 Next is attack vectors. 27 00:03:19,880 --> 00:03:23,240 This is what I'm going to spend most of my time on. 28 00:03:23,400 --> 00:03:31,620 I have an example of WebViewXSS for native and Flutter, as well as some deeplink examples. 29 00:03:31,620 --> 00:03:40,960 There's all kinds of awesome deeplink shenanigans to be had in combinations with other vulnerabilities. 30 00:03:41,180 --> 00:03:43,160 Next is Unicode collisions. 31 00:03:43,980 --> 00:03:57,340 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. 32 00:03:58,000 --> 00:03:59,940 And I will show examples of that. 33 00:04:00,780 --> 00:04:05,420 Lastly, validation bypass via FlutterRoute. 34 00:04:06,800 --> 00:04:14,420 And that's just an interesting implementation error that can lead to random results. 35 00:04:15,620 --> 00:04:17,660 Now let's talk about the ecosystems. 36 00:04:20,060 --> 00:04:33,260 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 exploiting the application or trying to find other bugs that are worth a bounty. 37 00:04:34,760 --> 00:04:43,500 Native apps, the code base will be mostly Java or Kotlin with shared object files if the NDK is in use. 38 00:04:43,640 --> 00:04:52,440 They're easier to decompile because there's a ton of tools out there that decompiles the code into readable format. 39 00:04:53,280 --> 00:04:57,580 It's easier to read and statically analyze after it's decompiled. 40 00:04:58,760 --> 00:05:01,540 And it imports AndroidX libraries. 41 00:05:02,900 --> 00:05:08,860 AndroidX libraries are the newer libraries for native applications currently. 42 00:05:09,000 --> 00:05:12,060 Flutter uses Dart. 43 00:05:12,060 --> 00:05:16,740 It's created with cross-platform compatibility in mind. 44 00:05:16,740 --> 00:05:29,800 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. 45 00:05:30,460 --> 00:05:36,280 Production binaries are not easy to reverse engineer because it's compiled to a .so file. 46 00:05:37,620 --> 00:05:48,100 The debug APKs create a bin file which is easy to find the source code. 47 00:05:49,800 --> 00:05:55,540 It's as easy as a grep command and then you get the Dart source back. 48 00:05:56,380 --> 00:06:05,870 The APKs tend to be bulkier unless you optimize the deployment to specific architectures. 49 00:06:06,560 --> 00:06:13,870 For React Native, these applications are mostly written in JavaScript then rendered with native activities. 50 00:06:14,400 --> 00:06:16,890 And a lot of the plugins are under the com directory. 51 00:06:17,680 --> 00:06:21,850 Moving on to detecting native Flutter and React applications. 52 00:06:21,850 --> 00:06:30,350 For native, usually there's a lot of AndroidManifest entries and most of the activities are easy to find in the com directory. 53 00:06:30,810 --> 00:06:40,930 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. 54 00:06:41,210 --> 00:06:48,990 For Flutter, Flutter has flutter.embedding entry in the AndroidManifest. 55 00:06:48,990 --> 00:06:54,810 There's also a folder called io.flutter in Flutter applications. 56 00:06:55,310 --> 00:06:59,130 There will be shared object files in the lib directory. 57 00:07:00,090 --> 00:07:04,730 In the release, there's libflutter.so and libapp.so. 58 00:07:04,730 --> 00:07:13,410 If it's a debug Flutter application, you'll find in the Flutter assets directory kernelblob.bin. 59 00:07:13,410 --> 00:07:17,330 And this is the file where you can grep for the Dart source code. 60 00:07:17,330 --> 00:07:20,530 But it's not very likely you'll find one of these in production. 61 00:07:21,910 --> 00:07:37,490 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. 62 00:07:37,490 --> 00:07:44,650 In the assets folder, you'll find an index.android.bundle file most of the time. 63 00:07:44,650 --> 00:07:49,910 And this is all the JavaScript code that hooks into the native activities. 64 00:07:51,430 --> 00:08:01,970 For the NDK and JNI, for the apps that use these, there's a shared object created in the lib directory. 65 00:08:02,970 --> 00:08:14,830 You will find JNI calls in those shared object files, and then that will for sure tell you that the NDK is in use. 66 00:08:14,830 --> 00:08:23,150 And you can decompile these shared object files with Ghidra or Dari2 and stuff like that. 67 00:08:24,150 --> 00:08:27,890 Using Objection for SSL pinning bypasses. 68 00:08:27,890 --> 00:08:43,170 So I use this script to automate the process of pulling the APK off of my device to my computer, renaming it, and opening JADX on the fly for a quick static source code analysis. 69 00:08:44,630 --> 00:08:53,670 Then, after I have the APK in that same directory, I'll use Objection to patch the APK. 70 00:08:54,670 --> 00:08:59,990 And what Objection does is it creates another APK with an agent loaded. 71 00:09:00,590 --> 00:09:06,850 And it tries to inject that agent into a launcher activity. 72 00:09:06,850 --> 00:09:13,830 So when you launch that patched APK, it'll pause execution. 73 00:09:13,830 --> 00:09:19,810 And when you use Objection Connect, you'll connect to that gadget. 74 00:09:19,810 --> 00:09:22,930 And from there, you can bypass the SSL pinning. 75 00:09:25,750 --> 00:09:43,630 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. 76 00:09:43,630 --> 00:09:49,290 And that Python script actually runs on top of Objection. 77 00:09:49,290 --> 00:09:52,770 So if you have Objection installed, you're good to go. 78 00:09:54,110 --> 00:09:57,870 And this works on React Native and native apps. 79 00:10:01,170 --> 00:10:02,710 Demo time! 80 00:10:03,350 --> 00:10:09,710 So, what I have here is an app that I own. 81 00:10:09,710 --> 00:10:16,050 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. 82 00:10:18,410 --> 00:10:21,810 So, what I'll do is... 83 00:10:25,180 --> 00:10:29,840 So, what I have is that script is aliased to auto-apk. 84 00:10:29,920 --> 00:10:33,840 And then all I have to do is put in the name of the app. 85 00:10:37,640 --> 00:10:40,740 And then from here, it'll do the rest of the work for me. 86 00:10:41,740 --> 00:10:44,120 Pulls it, puts it in the directory I'm in. 87 00:10:44,560 --> 00:10:53,260 And then all I have to do is use my other aliased command to patch 88 00:10:56,740 --> 00:10:57,360 it. 89 00:10:58,600 --> 00:11:02,840 So, this will install the gadget version I specified. 90 00:11:04,520 --> 00:11:10,700 While it's doing that, I can uninstall this version of the app. 91 00:11:10,940 --> 00:11:18,700 Because I have it automated to the point where it'll install the patched version on my device. 92 00:11:25,590 --> 00:11:31,890 And you can customize this Objection patch apk command however you like. 93 00:11:31,890 --> 00:11:38,430 Because sometimes the process will work without skip resources and skip native libs. 94 00:11:38,430 --> 00:11:45,030 And sometimes there's entries in Android Manifest that throw off the process and cause errors and stuff. 95 00:11:45,430 --> 00:11:47,810 So, it all depends on the application. 96 00:11:55,710 --> 00:12:02,830 Looks like it's done loading the agent... I mean the free-to-gadget. 97 00:12:02,830 --> 00:12:14,710 It re-signs the apk and puts it back in apk format and installs the apk on the device. 98 00:12:14,710 --> 00:12:25,570 So, since this apk doesn't have an SSL component to it, it won't freeze on startup. 99 00:12:25,690 --> 00:12:32,570 But production apps will stop execution because it will target the launcher activity. 100 00:12:38,020 --> 00:12:40,980 Bypassing Flutter SSL pinning. 101 00:12:41,180 --> 00:12:46,320 So, this is an entirely different beast in itself. 102 00:12:46,320 --> 00:12:50,900 So, Flutter uses Dart programming. 103 00:12:52,240 --> 00:12:57,000 I should say the Dart programming language which has it's own keystore. 104 00:12:57,020 --> 00:12:59,600 And the SSL libraries are completely different. 105 00:12:59,600 --> 00:13:04,180 It uses boring SSL to handle all of the SSL related functions. 106 00:13:04,180 --> 00:13:08,200 There's two main ways to bypass SSL pinning locally. 107 00:13:08,440 --> 00:13:17,540 And that's to find the SSL verification function in libflutter.so and change the response to true with a free-to-script. 108 00:13:18,540 --> 00:13:34,180 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. 109 00:13:34,180 --> 00:13:37,700 Find the offset to this exact method. 110 00:13:38,780 --> 00:13:47,520 Trust me, I was trying to find the offset for a specific architecture and it wasn't super easy. 111 00:13:47,540 --> 00:13:51,000 And I was having problems with compatibility with VMs. 112 00:13:51,000 --> 00:13:55,790 So, I'm going to show the second plugin bypass method. 113 00:13:57,520 --> 00:14:01,980 And later on, maybe in a YouTube video, I'll show the first one. 114 00:14:06,330 --> 00:14:15,570 And I want to say thank you to Joe and Beckers for creating the free-to-script and finding the Java method being used for SSL pinning in this particular plugin. 115 00:14:16,550 --> 00:14:26,270 So, running this command with the free-to-script waits for the method to execute. 116 00:14:26,270 --> 00:14:32,830 And once it executes, it changes the response in route to true. 117 00:14:33,950 --> 00:14:37,690 And I will show you exactly what I mean by that. 118 00:14:47,220 --> 00:15:01,160 So, I have this exercise programmed into Android that has the SSL pinning plugin. 119 00:15:03,700 --> 00:15:04,460 And 120 00:15:08,130 --> 00:15:11,030 so, if we use... 121 00:15:13,770 --> 00:15:15,770 So, if we use this command... 122 00:15:21,550 --> 00:15:23,070 This is actually... 123 00:15:23,530 --> 00:15:27,770 This app... 124 00:15:39,350 --> 00:15:41,250 Loading the script... 125 00:15:44,770 --> 00:15:47,750 This will restart the app, I believe. 126 00:15:47,770 --> 00:15:51,170 Because it injects the thread. 127 00:15:54,010 --> 00:16:00,830 So, once the app is restarted, you'll see... 128 00:16:11,300 --> 00:16:30,760 It doesn't matter what I put in for these, but just for demo purposes, it intercepts the pinning request every single time. 129 00:16:32,740 --> 00:16:36,860 And just to show you what happens without this... 130 00:16:42,540 --> 00:16:44,900 I'll exit out real quick. 131 00:16:49,830 --> 00:16:52,310 And I'll go back to the activity. 132 00:16:53,270 --> 00:16:58,010 So, without running the script, it actually does what it's supposed to do. 133 00:16:58,010 --> 00:17:04,010 So, if I put that in there, whatever fingerprint... 134 00:17:06,190 --> 00:17:10,050 It'll fail with the java exception. 135 00:17:12,050 --> 00:17:14,310 And there's that. 136 00:17:20,010 --> 00:17:21,870 Technical details of the Freedia script. 137 00:17:21,870 --> 00:17:32,070 So, this is, from my understanding, the Freedia API calls in use are java.use and java.perform. 138 00:17:32,830 --> 00:17:43,550 And java.use wraps a library in an object that can be used to modify calls to that library during runtime and return what is specified in the function. 139 00:17:43,650 --> 00:17:49,310 Java.perform ensures the call is attached to the current running thread for the function to disable pinning. 140 00:17:49,310 --> 00:17:54,650 And I believe that's why it restarts the application when you load the script. 141 00:17:57,640 --> 00:18:02,380 Third-party SDK and API exploitation. 142 00:18:02,380 --> 00:18:11,840 I can't emphasize enough the importance of reviewing third-party documentation to discover potential bugs. 143 00:18:11,840 --> 00:18:19,460 So, demo apps make great POCs, and almost all third-party services have demo apps. 144 00:18:20,260 --> 00:18:32,680 There's 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. 145 00:18:32,920 --> 00:18:39,960 But it doesn't check if it's an official app, if it's the official production app that's accessing it. 146 00:18:39,960 --> 00:18:44,660 So, it's basically just like a doorway into the production app. 147 00:18:44,660 --> 00:18:54,760 And it's even better if you can disclose sensitive data via some API call after you've SSL pinned the application. 148 00:18:55,900 --> 00:19:04,420 Mobile endpoints sometimes have separate APIs, like completely different ones that aren't designed the same exact way. 149 00:19:04,940 --> 00:19:14,020 And there could be different server headers, there could be calls without CSRF tokens. 150 00:19:14,020 --> 00:19:19,480 There could be calls with just one authentication header for all the calls. 151 00:19:19,480 --> 00:19:35,320 And sometimes you can even find the token in the Android app and use it to potentially elevate privileges and disclose more data. 152 00:19:35,800 --> 00:19:46,440 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. 153 00:19:47,250 --> 00:20:00,800 And lastly, I found this in an engagement where S3 bucket keys were in strings.xml. 154 00:20:00,800 --> 00:20:07,360 And I could upload and read the data in that bucket. 155 00:20:07,360 --> 00:20:16,740 I could see what files there were, so there's stack traces, all kinds of different files in that S3 bucket. 156 00:20:17,060 --> 00:20:26,120 And all I needed to do was add the keys in the bucket name to AWS Clive via the command line. 157 00:20:29,400 --> 00:20:36,220 So there's all kinds of stuff that you can find as far as API exploitation and third-party SDK. 158 00:20:36,220 --> 00:20:51,300 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. 159 00:20:51,300 --> 00:21:01,180 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. 160 00:21:06,050 --> 00:21:07,650 External file storage. 161 00:21:07,650 --> 00:21:19,990 So external file storage is essentially public on that device and any app can access external file storage. 162 00:21:19,990 --> 00:21:31,690 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. 163 00:21:31,690 --> 00:21:43,770 So a third-party app could scrape data from external file storage, literally these directories, and upload them to wherever they want, hypothetically. 164 00:21:43,770 --> 00:21:56,810 But I would like to note that external storage behavior changes in API 29+, because they scope it per user and per app. 165 00:21:57,110 --> 00:22:03,590 And shared storage has to be specified in order for files and data to be truly public. 166 00:22:03,590 --> 00:22:07,970 So it's a lot more stricter in the newer APIs. 167 00:22:10,580 --> 00:22:23,230 And these environment.getExternalStorage calls can easily be automated into a recon script. 168 00:22:27,810 --> 00:22:30,990 Native WebView XSS. 169 00:22:30,990 --> 00:22:36,630 So WebView XSS can be chained with deep links. 170 00:22:36,630 --> 00:22:44,200 So this can turn a quote-unquote self-XSS into a reflected XSS. 171 00:22:45,240 --> 00:22:50,900 The identifiers for vulnerable WebViews are similar between native and Flutter apps. 172 00:22:52,280 --> 00:22:58,600 By default, native and Flutter apps aren't susceptible to XSS unless JavaScript is enabled. 173 00:22:58,600 --> 00:23:06,180 So, for example, I have examples here for Java, Kotlin, and Flutter. 174 00:23:06,180 --> 00:23:22,320 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. 175 00:23:22,320 --> 00:23:25,440 Flutter WebView XSS via plugin. 176 00:23:25,440 --> 00:23:32,240 The official Flutter WebView plugin doesn't support alerting functions, but the development one does. 177 00:23:32,240 --> 00:23:43,320 And that's the interesting part, is that the plugin ecosystem kind of introduces vulnerabilities in a similar way that a WordPress environment does. 178 00:23:43,320 --> 00:23:49,880 And the way the values are handled is slightly different because the Dart programming language is used. 179 00:23:49,920 --> 00:23:56,760 So, firstly, the form value is transferred to a WebView with a navigator push material page route. 180 00:23:57,640 --> 00:24:06,340 Secondly, the WebView where the value is going has with JavaScript true set. 181 00:24:06,340 --> 00:24:17,180 And lastly, the user input value isn't escaped, and it's executed with eval JavaScript, which is built into the development plugin. 182 00:24:18,420 --> 00:24:21,380 And now it's demo time. 183 00:24:23,360 --> 00:24:26,580 So, I have an exercise for this, too. 184 00:24:27,760 --> 00:24:39,600 Let's just say this application doesn't filter or escape what the user puts in as a username. 185 00:24:40,780 --> 00:24:43,140 And I almost put that backwards. 186 00:24:48,650 --> 00:24:51,110 So, input whatever we want for the password. 187 00:24:52,910 --> 00:24:55,790 After the page loads, let's go to the profile. 188 00:24:56,310 --> 00:24:58,330 And bada-bing, bada-boom. 189 00:24:58,350 --> 00:25:00,450 We have stored XSS. 190 00:25:07,960 --> 00:25:29,340 So, you can see here, if you see eval JavaScript in a Flutter app, as well as JavaScript set to true, it's definitely vulnerable if the development plugin is in use. 191 00:25:31,720 --> 00:25:33,520 Native deep links. 192 00:25:33,520 --> 00:25:39,120 Deep links are specified in the Android manifest, and they can have a custom or web schema. 193 00:25:40,160 --> 00:25:47,380 Also, you can tell which activities have deep links by the intent filters. 194 00:25:47,380 --> 00:25:57,580 In the example below, there's a scheme of flag 13 and a host of RCE assigned to RCE activity. 195 00:25:57,580 --> 00:26:06,320 What this means is that a deep link will send a user directly to RCE activity as long as those conditions are met. 196 00:26:06,320 --> 00:26:09,120 And I'll show you exactly what I mean by that. 197 00:26:11,650 --> 00:26:20,750 So, if I use this adb shell example test command on Android, it'll send me directly to that activity. 198 00:26:21,070 --> 00:26:31,230 And that's the exact definition of a deep link, is it sends a user directly to a certain part of an Android application. 199 00:26:33,920 --> 00:26:35,340 Flutter deep links. 200 00:26:35,380 --> 00:26:55,800 So, Flutter deep links are implemented via the plugin unilinks, and they are handled pretty much the same way as native applications, where the plugin intercepts the deep link and then routes the user to wherever the deep link router for the application wants. 201 00:26:55,800 --> 00:27:01,400 However it's implemented, it'll send the user to that specific area. 202 00:27:01,980 --> 00:27:10,160 The Android manifest also has specific parts of that application that's assigned to the deep link. 203 00:27:11,320 --> 00:27:16,520 And the main thing is that they just look a lot different. 204 00:27:16,800 --> 00:27:23,020 The implementations just look a lot different because of the dark programming language and the async calls. 205 00:27:24,120 --> 00:27:27,020 Native deep link to reflected XSS. 206 00:27:27,020 --> 00:27:29,040 This is where the fun starts. 207 00:27:29,180 --> 00:27:41,960 So, we're going to use a deep link and a vulnerable web view to basically create reflected XSS in a mobile application. 208 00:27:42,860 --> 00:27:48,700 So, there's a couple preconditions that we need to have. 209 00:27:48,700 --> 00:27:53,220 It's a custom schema, so that way we know it's going to the mobile app. 210 00:27:53,580 --> 00:27:56,580 And the web view needs to have JavaScript enabled. 211 00:27:56,580 --> 00:28:05,780 And the example below shows that JavaScript is enabled for the web view. 212 00:28:06,120 --> 00:28:19,320 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. 213 00:28:21,740 --> 00:28:25,080 And it's demo time. 214 00:28:31,590 --> 00:28:39,710 So, the way you make deep link POCs is with HTML files. 215 00:28:39,990 --> 00:28:48,890 So, I'll just show you that it's just the same exact thing as a hyperlink. 216 00:28:48,970 --> 00:28:56,350 So, deep links are hyperlinks for mobile apps, basically, that take users directly to a specific part of an application. 217 00:28:57,330 --> 00:29:06,670 And then, I'm going to push it to my device with adb push the deep link file name. 218 00:29:08,030 --> 00:29:12,510 And for simplicity, just sdcard download. 219 00:29:15,220 --> 00:29:29,260 So, now if I go to downloads, and open the file, you can use any browser you want. 220 00:29:31,080 --> 00:29:46,820 It'll load the HTML file, and I'm just going to zoom in, click on this, 221 00:29:50,300 --> 00:29:54,100 it'll load in the web view, and it'll execute the JavaScript. 222 00:30:00,920 --> 00:30:06,140 And this is the vulnerable web view I used. 223 00:30:06,520 --> 00:30:12,040 This will also be a part of one of my other examples. 224 00:30:15,200 --> 00:30:18,320 Native deep link to LFI. 225 00:30:19,000 --> 00:30:28,440 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. 226 00:30:28,440 --> 00:30:42,620 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. 227 00:30:42,620 --> 00:30:55,900 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. 228 00:30:55,900 --> 00:31:17,090 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. 229 00:31:19,010 --> 00:31:30,090 So, it's going to read whatever file we put as that parameter, totally secure LFI, and log it to the text view. 230 00:31:32,750 --> 00:31:35,130 It's demo time. 231 00:31:41,990 --> 00:31:53,090 So I have a totally separate POC for the LFI that I will show everybody real fast. 232 00:31:53,990 --> 00:32:10,590 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. 233 00:32:11,990 --> 00:32:17,570 We'll push this file once again to our device, 234 00:32:20,950 --> 00:32:24,470 and to SD card download. 235 00:32:27,740 --> 00:32:32,100 Now we'll open the deep link. 236 00:32:32,780 --> 00:32:37,980 Actually, let's close this and go back to the files, 237 00:32:42,610 --> 00:32:46,470 open it in the browser, 238 00:32:55,330 --> 00:32:59,030 and it will load the contents in the web view. 239 00:32:59,690 --> 00:33:01,670 More fun with deep links. 240 00:33:01,670 --> 00:33:09,010 So I was curious if I could get remote code execution or command execution in general with a custom Go binary. 241 00:33:09,010 --> 00:33:19,510 Turns out, I was able to get code execution and remote code execution via deep link with this specific scenario. 242 00:33:21,610 --> 00:33:31,950 So, first I establish the process, and I get the run time of the custom Go binary, which is in the files directory of the application. 243 00:33:31,950 --> 00:33:45,710 The intent param is the Go binary name, and the binary param is the command that the binary has programmed as an option. 244 00:33:46,330 --> 00:33:53,770 Next, I make sure that I define a buffered reader to read the output for the command. 245 00:33:55,350 --> 00:34:13,010 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. 246 00:34:13,950 --> 00:34:39,150 Well, until the process finishes with processWaitFor, 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. 247 00:34:41,090 --> 00:34:48,050 First, I implemented this in Java, and then I figured out how to do it in Kotlin. 248 00:34:48,050 --> 00:34:56,490 The entire process was a lot of fun, and I'm really excited to share it. 249 00:34:57,510 --> 00:35:01,110 Without further ado, it's demo time. 250 00:35:07,660 --> 00:35:17,700 So, first, I thought it would be really cool just to see what the output should be. 251 00:35:18,200 --> 00:35:22,860 So, let me exit out of this. 252 00:35:24,260 --> 00:35:34,700 You can connect to a VM and phones via ADB once USB debugging is enabled, and establish a shell. 253 00:35:35,700 --> 00:35:37,660 I'll show you how to do this. 254 00:35:37,660 --> 00:35:50,880 Using ADB shell, we can navigate to this application's files directory, where the binaries are. 255 00:35:51,260 --> 00:36:03,500 And then, the binaries that execute the commands are narnia.x86-64, because that's the architecture for this VM. 256 00:36:05,520 --> 00:36:16,420 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 a parameter is needed. 257 00:36:17,820 --> 00:36:28,800 I remember programming a help parameter, so the commands available are test1, rick, test2, potato, and test3. 258 00:36:28,800 --> 00:36:31,520 So, let's try test1. 259 00:36:33,020 --> 00:36:34,200 It's treasure. 260 00:36:34,200 --> 00:36:35,360 Okay, cool. 261 00:36:37,260 --> 00:36:38,380 Test2. 262 00:36:38,900 --> 00:36:40,320 It's underscore. 263 00:36:41,180 --> 00:36:42,300 Test3. 264 00:36:43,780 --> 00:36:45,580 So, treasure planet. 265 00:36:47,240 --> 00:36:51,280 And, just for fun, let's see what rick is. 266 00:36:51,780 --> 00:36:55,140 So, rick is definitely the rip roll YouTube video. 267 00:36:56,140 --> 00:36:58,380 Because, you know, you have to have some fun. 268 00:36:58,380 --> 00:37:02,600 While programming CTF exercises. 269 00:37:03,320 --> 00:37:12,180 So, now that we know what the expected output is, let's try to execute commands via deep links. 270 00:37:12,380 --> 00:37:19,520 Clear the terminal, and I will show you my POC. 271 00:37:20,220 --> 00:37:22,580 HTML file. 272 00:37:25,870 --> 00:37:43,890 So, as you can see, this particular activity has the schema flag13, a host of RCE, the parameters binary, and param test1. 273 00:37:44,770 --> 00:37:50,050 So, the parameter called param is actually the binary command. 274 00:37:50,050 --> 00:38:06,480 And, so, how to be able to make a deep link POC, I will show you exactly where to look in the application itself. 275 00:38:06,480 --> 00:38:23,810 So, if I open Android Manifest for Android, right here. 276 00:38:27,120 --> 00:38:30,820 So, the RCE activity is the one that we are looking for. 277 00:38:31,980 --> 00:38:41,700 So, this is how, if you were looking to make deep link POCs, you would look for activities that have this intent filter. 278 00:38:43,580 --> 00:38:49,540 And, then you can find out what the scheme is, and what the host is. 279 00:38:49,540 --> 00:39:09,660 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. 280 00:39:11,460 --> 00:39:16,980 And it could disclose what params you can test out. 281 00:39:16,980 --> 00:39:25,720 So, if you do shift-shift in Android Studio, you can go right to the activity. 282 00:39:25,720 --> 00:39:27,100 So, that's what I'm going to do. 283 00:39:29,860 --> 00:39:36,760 So, the params that were used for this were binary and param. 284 00:39:36,760 --> 00:39:53,060 So, sometimes you can find specific parameters that the activity is looking for and use those to your advantage and find bugs. 285 00:39:54,200 --> 00:40:00,120 Now, back to the demo. 286 00:40:04,330 --> 00:40:08,790 So, we just have a normal blank RCE activity here. 287 00:40:09,570 --> 00:40:15,950 We need to push this POC to the device itself. 288 00:40:19,270 --> 00:40:24,710 And we'll push it to sdcarddownload, just for simplicity. 289 00:40:34,960 --> 00:40:41,060 Now, what we want to find is the RCE POC.html. 290 00:40:41,060 --> 00:40:44,120 You can open it with whatever browser you want. 291 00:40:49,880 --> 00:40:51,320 Zoom in. 292 00:40:51,320 --> 00:40:56,700 And with Android Studio VMs, it's CTRL and then dragging the mouse. 293 00:40:58,560 --> 00:41:02,800 So, let's try the test1 command variable. 294 00:41:06,300 --> 00:41:11,360 And that's awesome, but it output exactly the same as the terminal. 295 00:41:13,020 --> 00:41:23,280 Test2, underscore, test3, plant. 296 00:41:24,160 --> 00:41:28,720 And this should solve the flag for the exercise. 297 00:41:28,780 --> 00:41:33,440 And also redirect the user. 298 00:41:37,730 --> 00:41:51,970 So, needless to say, looking for intent params in the source code can disclose ways you can navigate to other parts of the navigation. 299 00:41:52,730 --> 00:42:00,470 Or ways you can find bugs in sensitive data. 300 00:42:01,850 --> 00:42:17,430 So, as an example, when the application is getting parameters, that's prime for you should append those to your deeplink POCs. 301 00:42:20,090 --> 00:42:22,790 Just like this. 302 00:42:30,440 --> 00:42:33,440 Now for Unicode collisions. 303 00:42:33,440 --> 00:42:41,560 This is kind of uncharted territory as far as Android applications go. 304 00:42:42,240 --> 00:42:50,100 So, Java and Kotlin both have Unicode mapping collisions. 305 00:42:50,100 --> 00:42:56,900 It just depends on how the application is using Unicode. 306 00:42:56,900 --> 00:43:01,740 This is prevalent in email. 307 00:43:02,260 --> 00:43:06,980 So, password reset functions, for example. 308 00:43:06,980 --> 00:43:20,620 It could exist in a database that converts Unicode to uppercase or lowercase characters. 309 00:43:20,620 --> 00:43:31,440 This script right here just shows a simple way that I was able to confirm that Unicode collisions did exist in Java. 310 00:43:31,880 --> 00:43:49,440 As you can see here, first I just tried to string to lowercase and uppercase the Unicode B character. 311 00:43:50,540 --> 00:43:59,660 And if it equaled double S as to uppercase, it printed true. 312 00:43:59,660 --> 00:44:23,700 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, it was true, which it did. 313 00:44:23,700 --> 00:44:32,320 Needless to say, Unicode collisions do exist in Android applications that use Java or Kotlin. 314 00:44:32,580 --> 00:44:35,200 It just depends on the implementation. 315 00:44:38,820 --> 00:44:47,400 I also included a Unicode collision example in Kotlin just because I don't think there's very many examples out there currently. 316 00:44:47,640 --> 00:44:54,020 And this is actually one of the examples in Android as well. 317 00:44:54,020 --> 00:45:01,960 So, this code is taking the post string value and converting it to all uppercase characters. 318 00:45:02,620 --> 00:45:08,040 Databases sometimes do uppercase or two lowercase values to keep data consistent. 319 00:45:08,260 --> 00:45:15,800 So, this is a very realistic test case that is worth testing. 320 00:45:17,260 --> 00:45:22,400 Or, I guess, worth putting in your workflow for every application. 321 00:45:24,100 --> 00:45:32,580 And it's important to note that Unicode collisions can affect mobile-to-web interactions. 322 00:45:33,800 --> 00:45:49,440 Especially if the mobile app is connected to a database that I haven't tried out a Unicode collision yet with Firebase. 323 00:45:49,440 --> 00:45:58,180 But definitely with certain types of databases, Unicode collisions could affect them. 324 00:45:59,060 --> 00:46:12,560 And there was one instance where I saved my username as a Unicode character and I wasn't even able to run the Android app after that. 325 00:46:12,560 --> 00:46:25,260 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. 326 00:46:25,260 --> 00:46:34,560 So, that's also important to remember that it could happen for production apps. 327 00:46:39,210 --> 00:46:45,310 Authentication bypasses via insecure Flutter material page route. 328 00:46:45,310 --> 00:47:01,510 So, navigator.push material page routes behave similarly to deep links except, if not implemented correctly, could bypass application logic and validations. 329 00:47:01,510 --> 00:47:08,270 The example below is an example of a correct implementation. 330 00:47:08,270 --> 00:47:16,130 You want to put these navigator.push routes after all of the form inputs. 331 00:47:16,130 --> 00:47:19,630 If you don't, I will show you what exactly happens. 332 00:47:20,110 --> 00:47:22,170 It's kind of surprising. 333 00:47:29,050 --> 00:47:34,390 So, this is an example of an insecure material page route. 334 00:47:34,390 --> 00:47:45,910 And implementations that are like this can possibly have unexpected behavior and could leak sensitive data. 335 00:47:46,690 --> 00:48:13,410 For example, if this was like 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, 336 00:48:13,410 --> 00:48:14,930 then that could be all bad. 337 00:48:18,990 --> 00:48:21,390 And it's demo time. 338 00:48:30,250 --> 00:48:44,060 So, I have an example in here of an insecure navigator page route, which could potentially be an off-bypass. 339 00:48:44,060 --> 00:48:52,400 So, you'll notice if I push signup, it says please enter your password and username. 340 00:48:52,400 --> 00:48:57,880 If I just put password, it says please enter a username. 341 00:48:58,740 --> 00:49:04,060 If I just put a username, it takes me... 342 00:49:04,060 --> 00:49:19,080 It automatically brings me to the next page and doesn't really bother checking to make sure that my password was correct. 343 00:49:19,080 --> 00:49:37,300 So, it's just another weird scenario, or I guess I should say odd scenario, where an implementation of a material page route located in the wrong spot could bypass validation. 344 00:49:39,660 --> 00:49:49,300 And this is all because of the material page route being after the username value that was entered. 345 00:49:50,960 --> 00:49:56,640 So, I'll show you what exactly I mean. 346 00:50:02,700 --> 00:50:15,340 The material page route for this scenario is only validating that the username is not empty. 347 00:50:18,350 --> 00:50:27,150 And if it's not empty, then the page route... these navigator push, basically, they're just like, alright, cool. 348 00:50:28,050 --> 00:50:38,450 Everything before this being executed is good, so I'm going to send you to the next page, which is the home page. 349 00:50:41,920 --> 00:50:45,060 And the validation ends down here. 350 00:50:46,040 --> 00:50:57,120 So, ideally, the navigator page route should be after all of the validation takes place. 351 00:51:04,660 --> 00:51:09,440 Easily bypassable and broken encryption. 352 00:51:09,760 --> 00:51:19,820 So, plugins for Cordova and Flutter are more susceptible to this. 353 00:51:19,820 --> 00:51:28,080 Any CBC cipher with a static initialization vector and hardcoded key. 354 00:51:28,540 --> 00:51:45,840 Hardcoded XORs or XOR bitwise operators can be found in assembly, so it's important to note that you potentially could find keys that are hardcoded in shared object files. 355 00:51:48,380 --> 00:52:05,300 It's going to be a little harder to identify, though, depending on the implementation, but decompiling those shared objects with Ghidra or Redari 2 will definitely show you any strings. 356 00:52:07,640 --> 00:52:15,740 So, these two code examples are examples I've found in a Cordova app. 357 00:52:15,740 --> 00:52:18,860 Or, not a Cordova app, React Native. 358 00:52:19,640 --> 00:52:23,940 And I believe that they use a Cordova plugin. 359 00:52:24,600 --> 00:52:42,700 And, so, in my opinion, there's really no point to use a plugin that encrypts data and decrypts it at runtime if you have a hardcoded encryption key. 360 00:52:42,700 --> 00:52:51,920 Because once you find the hardcoded encryption key, it's basically... it's just all the protection from encrypting your data is gone. 361 00:52:52,600 --> 00:53:05,040 So , when I was testing this application and I found this, I was able to decrypt the data pretty easily. 362 00:53:06,100 --> 00:53:11,440 Luckily for them, there was nothing sensitive, which also made no sense to me. 363 00:53:11,440 --> 00:53:15,340 I wasn't sure why they would encrypt data that wasn't sensitive. 364 00:53:17,620 --> 00:53:36,020 But, yeah, if you find a plugin that uses a hardcoded key and doesn't change it upon every use and a hardcoded initialization vector, there's really no point. 365 00:53:36,420 --> 00:53:41,580 Because somebody can decompile the app and then use that key to decrypt all the data. 366 00:53:42,360 --> 00:53:47,860 And I'll show you the script I used to do that on the next slide. 367 00:53:53,590 --> 00:54:00,310 So, like I said before, static code analysis may reveal the encryption keys. 368 00:54:00,410 --> 00:54:06,750 Then you can leverage them to decrypt potentially sensitive data with a Python script just to make it easier. 369 00:54:06,750 --> 00:54:10,650 Because it's easier than making a proof-of-concept app. 370 00:54:10,650 --> 00:54:22,470 Because in this situation, yeah, for encryption, making a proof-of-concept app would take a lot longer. 371 00:54:24,110 --> 00:54:27,410 Because you really just need to decrypt the data that's encrypted. 372 00:54:28,630 --> 00:54:38,570 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. 373 00:54:39,770 --> 00:54:41,490 Reversing an XOR example. 374 00:54:41,490 --> 00:54:44,830 There's only two steps in the process of reversing an XOR encryption. 375 00:54:45,310 --> 00:54:47,310 One, the XOR key. 376 00:54:48,050 --> 00:54:52,570 Two, applying the XOR key again to get the original data. 377 00:54:53,450 --> 00:55:03,410 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. 378 00:55:03,410 --> 00:55:08,910 And you'll know pretty quickly if it was XOR encryption being used. 379 00:55:09,270 --> 00:55:21,620 It's also good to note that to keep a lookout for any byte arrays, because byte arrays can be converted back to plain text pretty easily as well. 380 00:55:22,090 --> 00:55:25,050 And I have an example of that in this script. 381 00:55:25,050 --> 00:55:37,650 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. 382 00:55:37,650 --> 00:55:43,170 So, I'll show you what I mean by that. 383 00:55:43,170 --> 00:55:47,430 So, this is kind of a bonus demo time. 384 00:55:48,430 --> 00:56:00,990 But , in this scenario, there's a byte array, which can easily be converted back to plain text. 385 00:56:01,930 --> 00:56:04,630 I'll show you how to do that right now. 386 00:56:04,630 --> 00:56:24,290 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. 387 00:56:28,590 --> 00:56:34,510 So, when you apply this XOR to when, it turns into this. 388 00:56:34,510 --> 00:56:37,910 And when you apply the XOR to this, it turns into when. 389 00:56:44,200 --> 00:56:53,880 I'll go over some further technical details of how I implemented this activity. 390 00:56:53,880 --> 00:57:05,560 After installing the Native Development Kit, you can create shared object files or native libraries with C++ and C. 391 00:57:05,560 --> 00:57:08,820 And these can be used for a number of reasons. 392 00:57:08,960 --> 00:57:20,260 I mean, you could actually probably just program the entire Android application with JNI and the Native Development Kit. 393 00:57:20,260 --> 00:57:24,540 It all depends on what your language or preference is. 394 00:57:24,960 --> 00:57:36,520 Anywho, every JNI call for the shared object file will look something like that. 395 00:57:36,720 --> 00:57:42,120 It'll have these attributes declared. 396 00:57:42,120 --> 00:58:02,000 One interesting fact is that there's still a bug with this specific function, newStringUTF, where if it returns a Unicode character above a certain number, it will crash the entire app. 397 00:58:02,000 --> 00:58:06,600 Now I'll show you how the shared object file is initialized and used. 398 00:58:06,600 --> 00:58:24,900 So in the assembly activity, which is programmed in Kotlin, this is where I have a companion object that initializes the native library by using System.LoadLibrary. 399 00:58:27,900 --> 00:58:42,180 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. 400 00:58:42,180 --> 00:58:56,140 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. 401 00:58:58,200 --> 00:59:06,360 Now that we're looking through this, you can see that XORString is the result of this method. 402 00:59:07,640 --> 00:59:21,660 And that's one way we know that method was intended just to return a specific value or specific data. 403 00:59:23,790 --> 00:59:30,470 And then XORString is being converted to UTF-8. 404 00:59:30,470 --> 00:59:46,010 The bytes are being stored to a byte array so we can print it to the text view with bytes.content to string. 405 00:59:46,930 --> 00:59:54,630 And that's why you see the byte array on that specific exercise, or CTF exercise. 406 00:59:57,810 --> 01:00:00,170 Thank you everyone for listening to my talk. 407 01:00:00,170 --> 01:00:06,070 I am super grateful to have been given the opportunity to present here at DefCon Red Team Village. 408 01:00:06,510 --> 01:00:12,350 I highly recommend you check out the OWASP Mobile Security Testing Guide if you want to get into mobile security. 409 01:00:12,950 --> 01:00:20,790 And I will be sticking around for questions and answers in the Discord channel. 410 01:00:20,790 --> 01:00:33,730 Also feel free to message me on Discord or Twitter whenever, and I will try to answer any questions you may have. 411 01:00:33,730 --> 01:00:36,790 Thanks again, until next time.