WEBVTT

00:00.000 --> 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.

00:11.980 --> 00:14.100
Try saying that three times fast.

00:14.200 --> 00:19.140
I also want to thank Joseph and Omar for inviting me.

00:19.380 --> 00:25.760
I am a full-time security researcher, and in my spare time, I really enjoy testing Android applications.

00:25.760 --> 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.

00:33.720 --> 00:41.680
Also, I'll be focusing on the application level vulnerabilities in the Android ecosystem.

00:42.200 --> 00:45.540
So, let's get the party started.

00:46.120 --> 00:51.260
Right off the bat, I just wanted to show all the exciting things we have in store today.

00:51.260 --> 00:54.460
Here's my xMyMap for Android attack vectors.

00:54.460 --> 00:57.900
We're going to be covering a little bit of each of these main subjects.

00:57.940 --> 01:11.980
Everything from exported components, webviews, deeplinks, third-party SDKs, third-party plugins, APIs, insecure cryptography, and insecure data and file storage.

01:12.280 --> 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.

01:30.360 --> 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.

01:45.060 --> 01:49.300
Also, this xMyMap will be available after the talk.

01:50.280 --> 01:51.280
Alright.

01:52.800 --> 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.

01:59.880 --> 02:01.820
First is the reconnaissance phase.

02:01.980 --> 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.

02:08.720 --> 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.

02:26.680 --> 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.

02:38.520 --> 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.

02:49.780 --> 02:51.900
Next is the preparation phase.

02:52.600 --> 03:01.660
So, using Objection, you can bypass most native and React Native applications' SSL.

03:02.360 --> 03:08.100
Unfortunately, it doesn't work for Flutter because Flutter uses a different SSL library called BornSSL.

03:08.140 --> 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.

03:17.800 --> 03:19.880
Next is attack vectors.

03:19.880 --> 03:23.240
This is what I'm going to spend most of my time on.

03:23.400 --> 03:31.620
I have an example of WebViewXSS for native and Flutter, as well as some deeplink examples.

03:31.620 --> 03:40.960
There's all kinds of awesome deeplink shenanigans to be had in combinations with other vulnerabilities.

03:41.180 --> 03:43.160
Next is Unicode collisions.

03:43.980 --> 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.

03:58.000 --> 03:59.940
And I will show examples of that.

04:00.780 --> 04:05.420
Lastly, validation bypass via FlutterRoute.

04:06.800 --> 04:14.420
And that's just an interesting implementation error that can lead to random results.

04:15.620 --> 04:17.660
Now let's talk about the ecosystems.

04:20.060 --> 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.

04:34.760 --> 04:43.500
Native apps, the code base will be mostly Java or Kotlin with shared object files if the NDK is in use.

04:43.640 --> 04:52.440
They're easier to decompile because there's a ton of tools out there that decompiles the code into readable format.

04:53.280 --> 04:57.580
It's easier to read and statically analyze after it's decompiled.

04:58.760 --> 05:01.540
And it imports AndroidX libraries.

05:02.900 --> 05:08.860
AndroidX libraries are the newer libraries for native applications currently.

05:09.000 --> 05:12.060
Flutter uses Dart.

05:12.060 --> 05:16.740
It's created with cross-platform compatibility in mind.

05:16.740 --> 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.

05:30.460 --> 05:36.280
Production binaries are not easy to reverse engineer because it's compiled to a .so file.

05:37.620 --> 05:48.100
The debug APKs create a bin file which is easy to find the source code.

05:49.800 --> 05:55.540
It's as easy as a grep command and then you get the Dart source back.

05:56.380 --> 06:05.870
The APKs tend to be bulkier unless you optimize the deployment to specific architectures.

06:06.560 --> 06:13.870
For React Native, these applications are mostly written in JavaScript then rendered with native activities.

06:14.400 --> 06:16.890
And a lot of the plugins are under the com directory.

06:17.680 --> 06:21.850
Moving on to detecting native Flutter and React applications.

06:21.850 --> 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.

06:30.810 --> 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.

06:41.210 --> 06:48.990
For Flutter, Flutter has flutter.embedding entry in the AndroidManifest.

06:48.990 --> 06:54.810
There's also a folder called io.flutter in Flutter applications.

06:55.310 --> 06:59.130
There will be shared object files in the lib directory.

07:00.090 --> 07:04.730
In the release, there's libflutter.so and libapp.so.

07:04.730 --> 07:13.410
If it's a debug Flutter application, you'll find in the Flutter assets directory kernelblob.bin.

07:13.410 --> 07:17.330
And this is the file where you can grep for the Dart source code.

07:17.330 --> 07:20.530
But it's not very likely you'll find one of these in production.

07:21.910 --> 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.

07:37.490 --> 07:44.650
In the assets folder, you'll find an index.android.bundle file most of the time.

07:44.650 --> 07:49.910
And this is all the JavaScript code that hooks into the native activities.

07:51.430 --> 08:01.970
For the NDK and JNI, for the apps that use these, there's a shared object created in the lib directory.

08:02.970 --> 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.

08:14.830 --> 08:23.150
And you can decompile these shared object files with Ghidra or Dari2 and stuff like that.

08:24.150 --> 08:27.890
Using Objection for SSL pinning bypasses.

08:27.890 --> 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.

08:44.630 --> 08:53.670
Then, after I have the APK in that same directory, I'll use Objection to patch the APK.

08:54.670 --> 08:59.990
And what Objection does is it creates another APK with an agent loaded.

09:00.590 --> 09:06.850
And it tries to inject that agent into a launcher activity.

09:06.850 --> 09:13.830
So when you launch that patched APK, it'll pause execution.

09:13.830 --> 09:19.810
And when you use Objection Connect, you'll connect to that gadget.

09:19.810 --> 09:22.930
And from there, you can bypass the SSL pinning.

09:25.750 --> 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.

09:43.630 --> 09:49.290
And that Python script actually runs on top of Objection.

09:49.290 --> 09:52.770
So if you have Objection installed, you're good to go.

09:54.110 --> 09:57.870
And this works on React Native and native apps.

10:01.170 --> 10:02.710
Demo time!

10:03.350 --> 10:09.710
So, what I have here is an app that I own.

10:09.710 --> 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.

10:18.410 --> 10:21.810
So, what I'll do is...

10:25.180 --> 10:29.840
So, what I have is that script is aliased to auto-apk.

10:29.920 --> 10:33.840
And then all I have to do is put in the name of the app.

10:37.640 --> 10:40.740
And then from here, it'll do the rest of the work for me.

10:41.740 --> 10:44.120
Pulls it, puts it in the directory I'm in.

10:44.560 --> 10:53.260
And then all I have to do is use my other aliased command to patch

10:56.740 --> 10:57.360
it.

10:58.600 --> 11:02.840
So, this will install the gadget version I specified.

11:04.520 --> 11:10.700
While it's doing that, I can uninstall this version of the app.

11:10.940 --> 11:18.700
Because I have it automated to the point where it'll install the patched version on my device.

11:25.590 --> 11:31.890
And you can customize this Objection patch apk command however you like.

11:31.890 --> 11:38.430
Because sometimes the process will work without skip resources and skip native libs.

11:38.430 --> 11:45.030
And sometimes there's entries in Android Manifest that throw off the process and cause errors and stuff.

11:45.430 --> 11:47.810
So, it all depends on the application.

11:55.710 --> 12:02.830
Looks like it's done loading the agent... I mean the free-to-gadget.

12:02.830 --> 12:14.710
It re-signs the apk and puts it back in apk format and installs the apk on the device.

12:14.710 --> 12:25.570
So, since this apk doesn't have an SSL component to it, it won't freeze on startup.

12:25.690 --> 12:32.570
But production apps will stop execution because it will target the launcher activity.

12:38.020 --> 12:40.980
Bypassing Flutter SSL pinning.

12:41.180 --> 12:46.320
So, this is an entirely different beast in itself.

12:46.320 --> 12:50.900
So, Flutter uses Dart programming.

12:52.240 --> 12:57.000
I should say the Dart programming language which has it's own keystore.

12:57.020 --> 12:59.600
And the SSL libraries are completely different.

12:59.600 --> 13:04.180
It uses boring SSL to handle all of the SSL related functions.

13:04.180 --> 13:08.200
There's two main ways to bypass SSL pinning locally.

13:08.440 --> 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.

13:18.540 --> 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.

13:34.180 --> 13:37.700
Find the offset to this exact method.

13:38.780 --> 13:47.520
Trust me, I was trying to find the offset for a specific architecture and it wasn't super easy.

13:47.540 --> 13:51.000
And I was having problems with compatibility with VMs.

13:51.000 --> 13:55.790
So, I'm going to show the second plugin bypass method.

13:57.520 --> 14:01.980
And later on, maybe in a YouTube video, I'll show the first one.

14:06.330 --> 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.

14:16.550 --> 14:26.270
So, running this command with the free-to-script waits for the method to execute.

14:26.270 --> 14:32.830
And once it executes, it changes the response in route to true.

14:33.950 --> 14:37.690
And I will show you exactly what I mean by that.

14:47.220 --> 15:01.160
So, I have this exercise programmed into Android that has the SSL pinning plugin.

15:03.700 --> 15:04.460
And

15:08.130 --> 15:11.030
so, if we use...

15:13.770 --> 15:15.770
So, if we use this command...

15:21.550 --> 15:23.070
This is actually...

15:23.530 --> 15:27.770
This app...

15:39.350 --> 15:41.250
Loading the script...

15:44.770 --> 15:47.750
This will restart the app, I believe.

15:47.770 --> 15:51.170
Because it injects the thread.

15:54.010 --> 16:00.830
So, once the app is restarted, you'll see...

16:11.300 --> 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.

16:32.740 --> 16:36.860
And just to show you what happens without this...

16:42.540 --> 16:44.900
I'll exit out real quick.

16:49.830 --> 16:52.310
And I'll go back to the activity.

16:53.270 --> 16:58.010
So, without running the script, it actually does what it's supposed to do.

16:58.010 --> 17:04.010
So, if I put that in there, whatever fingerprint...

17:06.190 --> 17:10.050
It'll fail with the java exception.

17:12.050 --> 17:14.310
And there's that.

17:20.010 --> 17:21.870
Technical details of the Freedia script.

17:21.870 --> 17:32.070
So, this is, from my understanding, the Freedia API calls in use are java.use and java.perform.

17:32.830 --> 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.

17:43.650 --> 17:49.310
Java.perform ensures the call is attached to the current running thread for the function to disable pinning.

17:49.310 --> 17:54.650
And I believe that's why it restarts the application when you load the script.

17:57.640 --> 18:02.380
Third-party SDK and API exploitation.

18:02.380 --> 18:11.840
I can't emphasize enough the importance of reviewing third-party documentation to discover potential bugs.

18:11.840 --> 18:19.460
So, demo apps make great POCs, and almost all third-party services have demo apps.

18:20.260 --> 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.

18:32.920 --> 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.

18:39.960 --> 18:44.660
So, it's basically just like a doorway into the production app.

18:44.660 --> 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.

18:55.900 --> 19:04.420
Mobile endpoints sometimes have separate APIs, like completely different ones that aren't designed the same exact way.

19:04.940 --> 19:14.020
And there could be different server headers, there could be calls without CSRF tokens.

19:14.020 --> 19:19.480
There could be calls with just one authentication header for all the calls.

19:19.480 --> 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.

19:35.800 --> 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.

19:47.250 --> 20:00.800
And lastly, I found this in an engagement where S3 bucket keys were in strings.xml.

20:00.800 --> 20:07.360
And I could upload and read the data in that bucket.

20:07.360 --> 20:16.740
I could see what files there were, so there's stack traces, all kinds of different files in that S3 bucket.

20:17.060 --> 20:26.120
And all I needed to do was add the keys in the bucket name to AWS Clive via the command line.

20:29.400 --> 20:36.220
So there's all kinds of stuff that you can find as far as API exploitation and third-party SDK.

20:36.220 --> 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.

20:51.300 --> 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.

21:06.050 --> 21:07.650
External file storage.

21:07.650 --> 21:19.990
So external file storage is essentially public on that device and any app can access external file storage.

21:19.990 --> 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.

21:31.690 --> 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.

21:43.770 --> 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.

21:57.110 --> 22:03.590
And shared storage has to be specified in order for files and data to be truly public.

22:03.590 --> 22:07.970
So it's a lot more stricter in the newer APIs.

22:10.580 --> 22:23.230
And these environment.getExternalStorage calls can easily be automated into a recon script.

22:27.810 --> 22:30.990
Native WebView XSS.

22:30.990 --> 22:36.630
So WebView XSS can be chained with deep links.

22:36.630 --> 22:44.200
So this can turn a quote-unquote self-XSS into a reflected XSS.

22:45.240 --> 22:50.900
The identifiers for vulnerable WebViews are similar between native and Flutter apps.

22:52.280 --> 22:58.600
By default, native and Flutter apps aren't susceptible to XSS unless JavaScript is enabled.

22:58.600 --> 23:06.180
So, for example, I have examples here for Java, Kotlin, and Flutter.

23:06.180 --> 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.

23:22.320 --> 23:25.440
Flutter WebView XSS via plugin.

23:25.440 --> 23:32.240
The official Flutter WebView plugin doesn't support alerting functions, but the development one does.

23:32.240 --> 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.

23:43.320 --> 23:49.880
And the way the values are handled is slightly different because the Dart programming language is used.

23:49.920 --> 23:56.760
So, firstly, the form value is transferred to a WebView with a navigator push material page route.

23:57.640 --> 24:06.340
Secondly, the WebView where the value is going has with JavaScript true set.

24:06.340 --> 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.

24:18.420 --> 24:21.380
And now it's demo time.

24:23.360 --> 24:26.580
So, I have an exercise for this, too.

24:27.760 --> 24:39.600
Let's just say this application doesn't filter or escape what the user puts in as a username.

24:40.780 --> 24:43.140
And I almost put that backwards.

24:48.650 --> 24:51.110
So, input whatever we want for the password.

24:52.910 --> 24:55.790
After the page loads, let's go to the profile.

24:56.310 --> 24:58.330
And bada-bing, bada-boom.

24:58.350 --> 25:00.450
We have stored XSS.

25:07.960 --> 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.

25:31.720 --> 25:33.520
Native deep links.

25:33.520 --> 25:39.120
Deep links are specified in the Android manifest, and they can have a custom or web schema.

25:40.160 --> 25:47.380
Also, you can tell which activities have deep links by the intent filters.

25:47.380 --> 25:57.580
In the example below, there's a scheme of flag 13 and a host of RCE assigned to RCE activity.

25:57.580 --> 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.

26:06.320 --> 26:09.120
And I'll show you exactly what I mean by that.

26:11.650 --> 26:20.750
So, if I use this adb shell example test command on Android, it'll send me directly to that activity.

26:21.070 --> 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.

26:33.920 --> 26:35.340
Flutter deep links.

26:35.380 --> 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.

26:55.800 --> 27:01.400
However it's implemented, it'll send the user to that specific area.

27:01.980 --> 27:10.160
The Android manifest also has specific parts of that application that's assigned to the deep link.

27:11.320 --> 27:16.520
And the main thing is that they just look a lot different.

27:16.800 --> 27:23.020
The implementations just look a lot different because of the dark programming language and the async calls.

27:24.120 --> 27:27.020
Native deep link to reflected XSS.

27:27.020 --> 27:29.040
This is where the fun starts.

27:29.180 --> 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.

27:42.860 --> 27:48.700
So, there's a couple preconditions that we need to have.

27:48.700 --> 27:53.220
It's a custom schema, so that way we know it's going to the mobile app.

27:53.580 --> 27:56.580
And the web view needs to have JavaScript enabled.

27:56.580 --> 28:05.780
And the example below shows that JavaScript is enabled for the web view.

28:06.120 --> 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.

28:21.740 --> 28:25.080
And it's demo time.

28:31.590 --> 28:39.710
So, the way you make deep link POCs is with HTML files.

28:39.990 --> 28:48.890
So, I'll just show you that it's just the same exact thing as a hyperlink.

28:48.970 --> 28:56.350
So, deep links are hyperlinks for mobile apps, basically, that take users directly to a specific part of an application.

28:57.330 --> 29:06.670
And then, I'm going to push it to my device with adb push the deep link file name.

29:08.030 --> 29:12.510
And for simplicity, just sdcard download.

29:15.220 --> 29:29.260
So, now if I go to downloads, and open the file, you can use any browser you want.

29:31.080 --> 29:46.820
It'll load the HTML file, and I'm just going to zoom in, click on this,

29:50.300 --> 29:54.100
it'll load in the web view, and it'll execute the JavaScript.

30:00.920 --> 30:06.140
And this is the vulnerable web view I used.

30:06.520 --> 30:12.040
This will also be a part of one of my other examples.

30:15.200 --> 30:18.320
Native deep link to LFI.

30:19.000 --> 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.

30:28.440 --> 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.

30:42.620 --> 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.

30:55.900 --> 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.

31:19.010 --> 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.

31:32.750 --> 31:35.130
It's demo time.

31:41.990 --> 31:53.090
So I have a totally separate POC for the LFI that I will show everybody real fast.

31:53.990 --> 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.

32:11.990 --> 32:17.570
We'll push this file once again to our device,

32:20.950 --> 32:24.470
and to SD card download.

32:27.740 --> 32:32.100
Now we'll open the deep link.

32:32.780 --> 32:37.980
Actually, let's close this and go back to the files,

32:42.610 --> 32:46.470
open it in the browser,

32:55.330 --> 32:59.030
and it will load the contents in the web view.

32:59.690 --> 33:01.670
More fun with deep links.

33:01.670 --> 33:09.010
So I was curious if I could get remote code execution or command execution in general with a custom Go binary.

33:09.010 --> 33:19.510
Turns out, I was able to get code execution and remote code execution via deep link with this specific scenario.

33:21.610 --> 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.

33:31.950 --> 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.

33:46.330 --> 33:53.770
Next, I make sure that I define a buffered reader to read the output for the command.

33:55.350 --> 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.

34:13.950 --> 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.

34:41.090 --> 34:48.050
First, I implemented this in Java, and then I figured out how to do it in Kotlin.

34:48.050 --> 34:56.490
The entire process was a lot of fun, and I'm really excited to share it.

34:57.510 --> 35:01.110
Without further ado, it's demo time.

35:07.660 --> 35:17.700
So, first, I thought it would be really cool just to see what the output should be.

35:18.200 --> 35:22.860
So, let me exit out of this.

35:24.260 --> 35:34.700
You can connect to a VM and phones via ADB once USB debugging is enabled, and establish a shell.

35:35.700 --> 35:37.660
I'll show you how to do this.

35:37.660 --> 35:50.880
Using ADB shell, we can navigate to this application's files directory, where the binaries are.

35:51.260 --> 36:03.500
And then, the binaries that execute the commands are narnia.x86-64, because that's the architecture for this VM.

36:05.520 --> 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.

36:17.820 --> 36:28.800
I remember programming a help parameter, so the commands available are test1, rick, test2, potato, and test3.

36:28.800 --> 36:31.520
So, let's try test1.

36:33.020 --> 36:34.200
It's treasure.

36:34.200 --> 36:35.360
Okay, cool.

36:37.260 --> 36:38.380
Test2.

36:38.900 --> 36:40.320
It's underscore.

36:41.180 --> 36:42.300
Test3.

36:43.780 --> 36:45.580
So, treasure planet.

36:47.240 --> 36:51.280
And, just for fun, let's see what rick is.

36:51.780 --> 36:55.140
So, rick is definitely the rip roll YouTube video.

36:56.140 --> 36:58.380
Because, you know, you have to have some fun.

36:58.380 --> 37:02.600
While programming CTF exercises.

37:03.320 --> 37:12.180
So, now that we know what the expected output is, let's try to execute commands via deep links.

37:12.380 --> 37:19.520
Clear the terminal, and I will show you my POC.

37:20.220 --> 37:22.580
HTML file.

37:25.870 --> 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.

37:44.770 --> 37:50.050
So, the parameter called param is actually the binary command.

37:50.050 --> 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.

38:06.480 --> 38:23.810
So, if I open Android Manifest for Android, right here.

38:27.120 --> 38:30.820
So, the RCE activity is the one that we are looking for.

38:31.980 --> 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.

38:43.580 --> 38:49.540
And, then you can find out what the scheme is, and what the host is.

38:49.540 --> 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.

39:11.460 --> 39:16.980
And it could disclose what params you can test out.

39:16.980 --> 39:25.720
So, if you do shift-shift in Android Studio, you can go right to the activity.

39:25.720 --> 39:27.100
So, that's what I'm going to do.

39:29.860 --> 39:36.760
So, the params that were used for this were binary and param.

39:36.760 --> 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.

39:54.200 --> 40:00.120
Now, back to the demo.

40:04.330 --> 40:08.790
So, we just have a normal blank RCE activity here.

40:09.570 --> 40:15.950
We need to push this POC to the device itself.

40:19.270 --> 40:24.710
And we'll push it to sdcarddownload, just for simplicity.

40:34.960 --> 40:41.060
Now, what we want to find is the RCE POC.html.

40:41.060 --> 40:44.120
You can open it with whatever browser you want.

40:49.880 --> 40:51.320
Zoom in.

40:51.320 --> 40:56.700
And with Android Studio VMs, it's CTRL and then dragging the mouse.

40:58.560 --> 41:02.800
So, let's try the test1 command variable.

41:06.300 --> 41:11.360
And that's awesome, but it output exactly the same as the terminal.

41:13.020 --> 41:23.280
Test2, underscore, test3, plant.

41:24.160 --> 41:28.720
And this should solve the flag for the exercise.

41:28.780 --> 41:33.440
And also redirect the user.

41:37.730 --> 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.

41:52.730 --> 42:00.470
Or ways you can find bugs in sensitive data.

42:01.850 --> 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.

42:20.090 --> 42:22.790
Just like this.

42:30.440 --> 42:33.440
Now for Unicode collisions.

42:33.440 --> 42:41.560
This is kind of uncharted territory as far as Android applications go.

42:42.240 --> 42:50.100
So, Java and Kotlin both have Unicode mapping collisions.

42:50.100 --> 42:56.900
It just depends on how the application is using Unicode.

42:56.900 --> 43:01.740
This is prevalent in email.

43:02.260 --> 43:06.980
So, password reset functions, for example.

43:06.980 --> 43:20.620
It could exist in a database that converts Unicode to uppercase or lowercase characters.

43:20.620 --> 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.

43:31.880 --> 43:49.440
As you can see here, first I just tried to string to lowercase and uppercase the Unicode B character.

43:50.540 --> 43:59.660
And if it equaled double S as to uppercase, it printed true.

43:59.660 --> 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.

44:23.700 --> 44:32.320
Needless to say, Unicode collisions do exist in Android applications that use Java or Kotlin.

44:32.580 --> 44:35.200
It just depends on the implementation.

44:38.820 --> 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.

44:47.640 --> 44:54.020
And this is actually one of the examples in Android as well.

44:54.020 --> 45:01.960
So, this code is taking the post string value and converting it to all uppercase characters.

45:02.620 --> 45:08.040
Databases sometimes do uppercase or two lowercase values to keep data consistent.

45:08.260 --> 45:15.800
So, this is a very realistic test case that is worth testing.

45:17.260 --> 45:22.400
Or, I guess, worth putting in your workflow for every application.

45:24.100 --> 45:32.580
And it's important to note that Unicode collisions can affect mobile-to-web interactions.

45:33.800 --> 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.

45:49.440 --> 45:58.180
But definitely with certain types of databases, Unicode collisions could affect them.

45:59.060 --> 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.

46:12.560 --> 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.

46:25.260 --> 46:34.560
So, that's also important to remember that it could happen for production apps.

46:39.210 --> 46:45.310
Authentication bypasses via insecure Flutter material page route.

46:45.310 --> 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.

47:01.510 --> 47:08.270
The example below is an example of a correct implementation.

47:08.270 --> 47:16.130
You want to put these navigator.push routes after all of the form inputs.

47:16.130 --> 47:19.630
If you don't, I will show you what exactly happens.

47:20.110 --> 47:22.170
It's kind of surprising.

47:29.050 --> 47:34.390
So, this is an example of an insecure material page route.

47:34.390 --> 47:45.910
And implementations that are like this can possibly have unexpected behavior and could leak sensitive data.

47:46.690 --> 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,

48:13.410 --> 48:14.930
then that could be all bad.

48:18.990 --> 48:21.390
And it's demo time.

48:30.250 --> 48:44.060
So, I have an example in here of an insecure navigator page route, which could potentially be an off-bypass.

48:44.060 --> 48:52.400
So, you'll notice if I push signup, it says please enter your password and username.

48:52.400 --> 48:57.880
If I just put password, it says please enter a username.

48:58.740 --> 49:04.060
If I just put a username, it takes me...

49:04.060 --> 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.

49:19.080 --> 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.

49:39.660 --> 49:49.300
And this is all because of the material page route being after the username value that was entered.

49:50.960 --> 49:56.640
So, I'll show you what exactly I mean.

50:02.700 --> 50:15.340
The material page route for this scenario is only validating that the username is not empty.

50:18.350 --> 50:27.150
And if it's not empty, then the page route... these navigator push, basically, they're just like, alright, cool.

50:28.050 --> 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.

50:41.920 --> 50:45.060
And the validation ends down here.

50:46.040 --> 50:57.120
So, ideally, the navigator page route should be after all of the validation takes place.

51:04.660 --> 51:09.440
Easily bypassable and broken encryption.

51:09.760 --> 51:19.820
So, plugins for Cordova and Flutter are more susceptible to this.

51:19.820 --> 51:28.080
Any CBC cipher with a static initialization vector and hardcoded key.

51:28.540 --> 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.

51:48.380 --> 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.

52:07.640 --> 52:15.740
So, these two code examples are examples I've found in a Cordova app.

52:15.740 --> 52:18.860
Or, not a Cordova app, React Native.

52:19.640 --> 52:23.940
And I believe that they use a Cordova plugin.

52:24.600 --> 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.

52:42.700 --> 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.

52:52.600 --> 53:05.040
So , when I was testing this application and I found this, I was able to decrypt the data pretty easily.

53:06.100 --> 53:11.440
Luckily for them, there was nothing sensitive, which also made no sense to me.

53:11.440 --> 53:15.340
I wasn't sure why they would encrypt data that wasn't sensitive.

53:17.620 --> 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.

53:36.420 --> 53:41.580
Because somebody can decompile the app and then use that key to decrypt all the data.

53:42.360 --> 53:47.860
And I'll show you the script I used to do that on the next slide.

53:53.590 --> 54:00.310
So, like I said before, static code analysis may reveal the encryption keys.

54:00.410 --> 54:06.750
Then you can leverage them to decrypt potentially sensitive data with a Python script just to make it easier.

54:06.750 --> 54:10.650
Because it's easier than making a proof-of-concept app.

54:10.650 --> 54:22.470
Because in this situation, yeah, for encryption, making a proof-of-concept app would take a lot longer.

54:24.110 --> 54:27.410
Because you really just need to decrypt the data that's encrypted.

54:28.630 --> 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.

54:39.770 --> 54:41.490
Reversing an XOR example.

54:41.490 --> 54:44.830
There's only two steps in the process of reversing an XOR encryption.

54:45.310 --> 54:47.310
One, the XOR key.

54:48.050 --> 54:52.570
Two, applying the XOR key again to get the original data.

54:53.450 --> 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.

55:03.410 --> 55:08.910
And you'll know pretty quickly if it was XOR encryption being used.

55:09.270 --> 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.

55:22.090 --> 55:25.050
And I have an example of that in this script.

55:25.050 --> 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.

55:37.650 --> 55:43.170
So, I'll show you what I mean by that.

55:43.170 --> 55:47.430
So, this is kind of a bonus demo time.

55:48.430 --> 56:00.990
But , in this scenario, there's a byte array, which can easily be converted back to plain text.

56:01.930 --> 56:04.630
I'll show you how to do that right now.

56:04.630 --> 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.

56:28.590 --> 56:34.510
So, when you apply this XOR to when, it turns into this.

56:34.510 --> 56:37.910
And when you apply the XOR to this, it turns into when.

56:44.200 --> 56:53.880
I'll go over some further technical details of how I implemented this activity.

56:53.880 --> 57:05.560
After installing the Native Development Kit, you can create shared object files or native libraries with C++ and C.

57:05.560 --> 57:08.820
And these can be used for a number of reasons.

57:08.960 --> 57:20.260
I mean, you could actually probably just program the entire Android application with JNI and the Native Development Kit.

57:20.260 --> 57:24.540
It all depends on what your language or preference is.

57:24.960 --> 57:36.520
Anywho, every JNI call for the shared object file will look something like that.

57:36.720 --> 57:42.120
It'll have these attributes declared.

57:42.120 --> 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.

58:02.000 --> 58:06.600
Now I'll show you how the shared object file is initialized and used.

58:06.600 --> 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.

58:27.900 --> 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.

58:42.180 --> 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.

58:58.200 --> 59:06.360
Now that we're looking through this, you can see that XORString is the result of this method.

59:07.640 --> 59:21.660
And that's one way we know that method was intended just to return a specific value or specific data.

59:23.790 --> 59:30.470
And then XORString is being converted to UTF-8.

59:30.470 --> 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.

59:46.930 --> 59:54.630
And that's why you see the byte array on that specific exercise, or CTF exercise.

59:57.810 --> 01:00:00.170
Thank you everyone for listening to my talk.

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.

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.

01:00:12.950 --> 01:00:20.790
And I will be sticking around for questions and answers in the Discord channel.

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.

01:00:33.730 --> 01:00:36.790
Thanks again, until next time.

