57 |
58 |
59 | {% include comments.html element=".post-content" github_account="nibarius/learning-frida" %}
60 |
61 |
62 |
63 |
--------------------------------------------------------------------------------
/docs/_posts/2020-05-15-installing-frida.markdown:
--------------------------------------------------------------------------------
1 | ---
2 | layout: post
3 | title: "Installing Frida"
4 | date: 2020-05-15
5 | comment_issue_id: 1
6 | ---
7 |
8 | The first thing we need to do is to install the Frida CLI tools, which is is fairly [straight forward](https://frida.re/docs/installation/). Basically you need to have python installed, then you just run `pip install frida-tools` and you're done.
9 |
10 | ## Prepare an Android device
11 |
12 | We also need an Android device that will be used together with Friday. It's possible to use Frida on non-rooted phones, but it's not quite as straight forward. So for an easy start I'm using an emulator running an Android 9 (Pie) system image without any Google services. Without Google services you can get root access right away which makes things easier.
13 |
14 | I did try an older Android version at first, but then Frida crashed on startup so I decided to go with Android 9 that works fine for me.
15 |
16 | When installing the Android system image for the emulator, pay attention to which version you are using, I'll picked an `x86_64` version since that matches my computer's architecture.
17 |
18 | {% include image.html url="/learning-frida/assets/install/install_emulator.png" description="Emulator setup" %}
19 |
20 | ## Install the Frida server on the phone
21 |
22 | The Frida web site has a [good guide](https://frida.re/docs/android/) explaining how to get Frida set up on an Android device.
23 |
24 | When you download the Frida server for Android make sure you pick one that matches the architecture of your device / emulator image. Since I'm using `x86_64` I downloaded the Frida server `frida-server-12.8.20-android-x86_64.xz`
25 |
26 | Extract the server and possibly rename it to something shorter like `frida-server` to make things easier.
27 |
28 | Now it's time to install the Frida server on the phone, first switch to root by running `adb root` in a terminal and then push the Frida server to the phone: `adb push frida-server /data/local/tmp/`. After this we just have to connect to the phone and start the server.
29 |
30 | ```
31 | adb shell
32 | cd /data/local/tmp
33 | chmod 755 ./frida-server
34 | ./frida-server
35 | ```
36 |
37 | Tip: you can see if you have root access on the phone by looking for `#` on the input line in the shell, if `$` is shown instead of `#` you don't have root access.
38 |
39 | {% include image.html url="/learning-frida/assets/install/adb_shell_root.png" description="Non-root vs root" %}
40 |
41 | ## Test that Frida is working
42 |
43 | Open a new console on your computer and run `frida-ps -U -a` to list all applications (`-a`) running on the phone / attached usb device (`-U`)
44 |
45 | {% include image.html url="/learning-frida/assets/install/listing_running_apps.png" description="Listing running apps with Frida" %}
46 |
47 | Now that everything is up and running we can [get started using Frida]({{ site.baseurl }}{% post_url 2020-05-16-uncrackable1 %}).
48 |
--------------------------------------------------------------------------------
/docs/_posts/2020-05-16-uncrackable1.markdown:
--------------------------------------------------------------------------------
1 | ---
2 | layout: post
3 | title: "Solving OWASP MSTG UnCrackable App for Android Level 1"
4 | date: 2020-05-16
5 | comment_issue_id: 2
6 | ---
7 |
8 | Now that we have [Frida set up]({{ site.baseurl }}{% post_url 2020-05-15-installing-frida %}), we can try to use it to solve the OWASP mobile security testing guide's [UnCrackable App for Android Level 1](https://github.com/OWASP/owasp-mstg/tree/master/Crackmes#uncrackable-mobile-apps).
9 |
10 | This challenge have a secret hidden inside an apk somewhere and the task is to find it.
11 |
12 | ## Preparations
13 |
14 | In addition to Frida we are going to use two additional tools:
15 | * [dex2jar](https://github.com/pxb1988/dex2jar) to convert the apk file to a jar file
16 | * [JD-GUI](https://java-decompiler.github.io) to decompile the jar file to be able to see the source code of the of the original apk.
17 |
18 | Both dex2jar and JD-GUI are trivial to set up, just download the tools and extract them to the desired location.
19 |
20 | Download `UnCrackable-Level1.apk` and install it on your device by running `adb install UnCrackable-Level1.apk`. Also run it trough dex2jar (`d2j-dex2jar.bat -f UnCrackable-Level1.apk`) and open the jar file in JD-GUI.
21 |
22 | ## Getting started
23 |
24 | Now we're all set to start experimenting with the app. Start by just running it on your device.
25 |
26 | ```
27 | +---------------------------------+
28 | | Root detected! |
29 | | |
30 | | This is unacceptable. The app |
31 | | is now going to exit. |
32 | | |
33 | | OK |
34 | +---------------------------------+
35 | ```
36 |
37 | That's not good, looks like the app doesn't allow being run on a rooted device. Let's switch to JD-GUI and take a look at the code. Since this happens on startup the `MainActivity` class and the `onCreate` method is probably a good place to start.
38 |
39 | It looks like the apps does several different root checks and one check if the app is debuggable. If this is the case the method `a` is called which shows the dialog we saw and then exits as soon as the dialog is closed.
40 |
41 | {% include image.html url="/learning-frida/assets/uncrackable1/root_detection.png" description="Root detection code" %}
42 |
43 | Let's try to use Frida to replace the method `a` with another method that doesn't do anything.
44 |
45 | ## Bypassing root detection
46 | By running `frida -U owasp.mstg.uncrackable1` it's possible to attach to the application. But this requires that the application is already running. However the application does the root check on startup, and by the time we have attached it's already too late.
47 |
48 | So we need to make sure we modify the app before we start it. We'll start by writing the code needed to replace the original implementation of the `a` method. Create file called `uncrackable1.js` with the following content:
49 |
50 | {% highlight javascript %}
51 | Java.perform(function() {
52 | Java.use("sg.vantagepoint.uncrackable1.MainActivity").a.implementation = function(s) {
53 | console.log("Tamper detection suppressed, message was: " + s);
54 | }
55 | });
56 | {% endhighlight %}
57 |
58 | The [Frida javascript api documentation](https://frida.re/docs/javascript-api/#java) describes these functions and can be helpful for understanding what we're actually doing. But basically the enclosing `Java.perform()` takes care of running the code we write.
59 |
60 | `Java.use` gets a JavaScript wrapper for the given class name so that we can interact with it. As mentioned earlier we want to start by modifying the `a` method in the `MainActivity` to bypass the root detection.
61 |
62 | By using `implementation` we can replace the implementation of the method. Let's just write a message to the console so we see that the method was called.
63 |
64 | With this in place we can run Frida again, this time doing it without first starting the app and using the command `frida -U --no-pause -l uncrackable1.js -f owasp.mstg.uncrackable1`. `-f` will start the given package and pause the main thread before the app have a chance to run. the `-l` flag provides a javascript that should be loaded. This script holds all the modifications that we will be doing to the app. Since we include a script to be loaded there is no need to pause the main thread on startup, so we pass along the `--no-pause` to prevent this.
65 |
66 | {% include image.html url="/learning-frida/assets/uncrackable1/root_bypassed.png" description="Root detection bypassed" %}
67 |
68 | ## Finding the secret
69 |
70 | The app now starts and instead of the root detection dialog we just get a printout in the console. That's a good start, now we can actually use the app. Looks like there's a text field where you can enter a secret and verify it. We're not going to be able to guess the secret, so let's go back to JD-GUI and take a look at the code again.
71 |
72 | {% include image.html url="/learning-frida/assets/uncrackable1/verify.png" %}
73 |
74 | In the `MainActivity` there is a `verify()` method that calls `sg.vantagepoint.uncrackable1.a.a()` with the entered string, it seems like this method returns true if the secret is correct. Let's take a look at this method.
75 |
76 | {% include image.html url="/learning-frida/assets/uncrackable1/string_checker.png" %}
77 |
78 | Ok, that method seems to contain the secret encrypted with AES and base64 encoded. An obfuscated version of the encryption key seems to be there as well. The method `b` in the same class seems to handle the de-obfuscation.
79 |
80 | ## Getting the secret
81 |
82 | But let's ignore that, looking closer at the code it looks like `sg.vantagepoint.a.a.a` is responsible for the decryption of the secret. So if we can just see what this method returns we should be able to get our hands on the secret.
83 |
84 | Time to update `uncrackable1.js` with some additional code:
85 | {% highlight javascript %}
86 | function bufferToString(buf) {
87 | var buffer = Java.array('byte', buf);
88 | var result = "";
89 | for(var i = 0; i < buffer.length; ++i){
90 | result += (String.fromCharCode(buffer[i] & 0xff));
91 | }
92 | return result;
93 | }
94 |
95 | Java.use("sg.vantagepoint.a.a").a.implementation = function(ba1, ba2) {
96 | const retval = this.a(ba1, ba2);
97 | console.log("secret code is: " + bufferToString(retval));
98 | return retval;
99 | }
100 | {% endhighlight %}
101 |
102 | `sg.vantagepoint.a.a.a` returns an array of bytes, which isn't very readable. So we start by creating a helper method called `bufferToString` that converts an array of bytes to a string as described [in this answer on the Reverse Engineering StackExchange](https://reverseengineering.stackexchange.com/a/22255).
103 |
104 | With that in place we continue to modify the implementation `sg.vantagepoint.a.a.a` in a similar way to before. This time we first call the original implementation, convert the return value (the secret code) to a string and print it to the console before returning it.
105 |
106 | All we have to do now is to save the file and tap the verify button in the app. Frida automatically reloads the script when saved, so you don't even have to restart Frida or the app. When pressing verify the secret is revealed in the console. Go ahead and try it in the app to make sure you got it right.
107 |
108 | {% include image.html url="/learning-frida/assets/uncrackable1/code_found.png" description="We got the secret code!"%}
109 |
110 | ## Improving the process
111 |
112 | We could be done here, but why not take the chance to learn a bit more? Wouldn't it be nice if we could get the secret right away without having to first guess something. Let's try to modify the `onResume()` method so that it tells us the secret when called. If you're familiar with the [Activity lifecycle in Android](https://developer.android.com/guide/components/activities/activity-lifecycle) you might know that this is a method that's called when the app becomes visible. So that feels like convenient place to put code we want run when the app is started.
113 |
114 | Then we need to figure out what code we want to run. Earlier we saw that the `verify()` method calls `sg.vantagepoint.uncrackable1.a.a()` with the manually entered value. We could just call this ourselves with a dummy value to trigger our code that prints the secret when it's accessed. Since this method is a static method we can just call it right away without having to create a new instance. This makes the code we need to add fairly straight forward:
115 |
116 | {% highlight javascript %}
117 | Java.use("sg.vantagepoint.uncrackable1.MainActivity").onResume.implementation = function() {
118 | this.onResume();
119 | Java.use("sg.vantagepoint.uncrackable1.a").a("dummy");
120 | }
121 | {% endhighlight %}
122 |
123 |
124 | Just update the code and save the file. To see that it works turn off the screen on the phone and turn it on again since this will trigger `onResume()` to be called.
125 |
126 | ## Things learned
127 | We've only done fairly simple things so far, but with absolutely no prior experience with Frida we've actually learned a lot. Here are some highlights:
128 |
129 | * How to start Frida (`frida -U`) with a JavaScript loaded (`-l`) and spawn an app (`-f `) to be able to modify app behavior during early startup.
130 | * What a basic JavaScript that Frida loads should look like
131 | * How to replace the implementation of a method using `Java.use("")..implementation`
132 | * How to call the original implementation of the method being replaced
133 | * How to convert an array of bytes to a string
134 | * How to call static methods
135 |
136 | [Full code is available on GitHub](https://github.com/nibarius/learning-frida/blob/master/src/uncrackable1/uncrackable1.js)
137 |
138 |
--------------------------------------------------------------------------------
/docs/_posts/2020-06-27-evabs.markdown:
--------------------------------------------------------------------------------
1 | ---
2 | layout: post
3 | title: "Solving the EVABS instrument challenge"
4 | date: 2020-06-27
5 | comment_issue_id: 6
6 | ---
7 |
8 | [EVABS][evabs] is an "Android application that is intentionally vulnerable so as to act as a learning platform for Android application security beginners". It has 12 different challenges of varying difficulties and the last one is intended to be solved with Frida, so that's what I'm going to do in this post.
9 |
10 | ## Getting started
11 | Opening up the final challenge in the EVABS app shows a screen that have some mapping related details. Hitting the "Map area" button results in a "Co-ordinates Not Found!" message. The app doesn't provide any way of modifying the coordinates.
12 |
13 | {% include image.html url="/learning-frida/assets/evabs/instrument_challenge.png" description="The EVABS instrument challenge"%}
14 |
15 | It looks like we're supposed to use Frida to modify the coordinates to the correct values to get the flag printed. As a start, we use [dex2jar][dex2jar] and [JD-GUI][jd-gui] to extract and analyze the jar file. This app has a class called `Frida1` which sounds like a good start.
16 |
17 | {% include image.html url="/learning-frida/assets/evabs/code.png" description="The onClick handler for the map area button"%}
18 |
19 | ## Time to write some code
20 |
21 | This class looks like the right one and the `onClick` handler seems to contain the most important code. It reads some hard coded values, does a simple calculation, and check if it should show the secret flag that is stored in native code. One approach could be to modify the hard coded values so the comparison always succeeds by finding the `Frida1` instance with `Java.choose()` and modifying the value with `instance.a.value = 100`.
22 |
23 | But the approach we're going to take is to just call the `stringFromJNI()` method and fetch the flag directly and also do this on startup so we don't have to go into the challenge activity to get the flag.
24 |
25 | This is done by by creating a new instance of the `Frida1` class and then calling `stringFromJNI()` on it.
26 |
27 | {% highlight javascript %}
28 | var f = Java.use("com.revo.evabs.Frida1").$new();
29 | console.log("Frida: '" + f.stringFromJNI() + "'");
30 | {% endhighlight %}
31 |
32 | Now we need to decide when to call this code. After looking around a bit, the `onCreate` method of the `Launch` activity feels like a good option since this is run on startup.
33 |
34 | {% highlight javascript %}
35 | Java.perform(function(){
36 | var Launch = Java.use("com.revo.evabs.Launch");
37 | Launch.onCreate.overload("android.os.Bundle").implementation = function(bundle) {
38 | this.onCreate(bundle);
39 | var f = Java.use("com.revo.evabs.Frida1").$new();
40 | console.log("Frida: '" + f.stringFromJNI() + "'");
41 | }
42 | });
43 | {% endhighlight %}
44 |
45 | ## Capturing the flag
46 | It's time to run the code and capture the flag: `frida -U -f com.revo.evabs -l evabs.js --no-pause`
47 |
48 | {% include image.html url="/learning-frida/assets/evabs/running_frida.png" description="We got the flag"%}
49 |
50 | For some reason, calling `Frida1.stringFromJNI()` like this results in some additional junk at the end of the string. However this only happens the first time the method is called, when calling it again the junk disappears. But even with the junk, we've found the flag and since the format of it is well known it's easy to spot the junk.
51 |
52 | I can't explain where this junk is coming from, and if `stringFromJNI()` is called from the `Frida1` instance created when the challenge is opened it does not show up. It's also not seen when logged by the app itself. If you know what causes this junk, feel free to leave a comment.
53 |
54 | ## Bonus content
55 | Several of the challenges in EVABS hides the flag in native code and these can easily be extracted the same way. For example the flag for the debug challenge can be extracted with:
56 |
57 | {% highlight javascript %}
58 | var debugMe = Java.use("com.revo.evabs.DebugMe").$new();
59 | console.log("debugMe: 'EVABS{" + debugMe.stringFromJNI() + "}'");
60 | {% endhighlight %}
61 |
62 | Unfortunately the EVABS app isn't completely stable and most of the challenges that hides the flags in native code [crash][my-bugreport] when the `stringFromJNI()` method is called on the emulator I'm using. So I haven't been able to use the same method to extract all secrets stored in native code. But it should work if running Frida on an actual device where the crash does not happen.
63 |
64 | ## Things learned
65 |
66 | After solving the [UnCrackable App][uncrackable] this was a fairly simple task without any noticeable lessons, but it's always nice with more experience and to see how earlier efforts pays off.
67 |
68 | The complete code is [available on GitHub][my-code].
69 |
70 | [evabs]: https://github.com/abhi-r3v0/EVABS
71 | [uncrackable]: https://github.com/OWASP/owasp-mstg/tree/master/Crackmes
72 | [dex2jar]: https://github.com/pxb1988/dex2jar
73 | [jd-gui]: https://java-decompiler.github.io
74 | [my-bugreport]: https://github.com/abhi-r3v0/EVABS/issues/5
75 | [my-code]: https://github.com/nibarius/learning-frida/blob/master/src/evabs/evabs.js
--------------------------------------------------------------------------------
/docs/_posts/2020-07-12-h101-oauthbreaker.markdown:
--------------------------------------------------------------------------------
1 | ---
2 | layout: post
3 | title: "Hacker101 CTF - Oauthbreaker"
4 | date: 2020-07-12
5 | comment_issue_id: 7
6 | ---
7 |
8 | [Hacker101](https://www.hacker101.com) is a free class for web security with many different CTF challenges. A couple of these are Android challenges and I'm going to tackle the Oauthbreaker challenge here. This challenge have two flags. There is no need to use Frida to find the first flag, but for the second flag Frida comes in handy, so that's what I'll be focusing on.
9 |
10 | ## Taking a look at the code
11 |
12 | We start by installing the apk and starting it on our device, run the apk trough [dex2jar](https://github.com/pxb1988/dex2jar) and opening the resulting jar file in [JD-GUI](https://java-decompiler.github.io) to see the source code.
13 |
14 | The `MainActivity` and the `Browser` have some oauth related code, but the really interesting part is in the `WebAppInterace` class. There is a method here with the interesting name `getFlagPath`.
15 |
16 | {% include image.html url="/learning-frida/assets/h101-oauthbreaker/webappinterface.png" description="This method might just give us the flag location" %}
17 |
18 | The method does a bunch of convoluted operations to generate a string, but instead of trying to understand the code, let's just call the method and see what it returns. To do this we need to first create an instance of the `WebAppInterace` class. The constructor takes a `Context` as parameter, but since this is not used we can just pass in `null`.
19 |
20 | {% highlight javascript %}
21 | Java.perform(function(){
22 | var WebAppInterface = Java.use("com.hacker101.oauth.WebAppInterface");
23 | var flag = WebAppInterface.$new(null).getFlagPath();
24 | console.log("Flag path: " + flag);
25 | });
26 | {% endhighlight %}
27 |
28 | ## Capturing the flag
29 |
30 | With the Oauthbreaker app running on the device we load our script and see what we get: `frida -U -l oauth.js com.hacker101.oauth`.
31 |
32 | {% include image.html url="/learning-frida/assets/h101-oauthbreaker/frida.png" description="Revealing the flag path" %}
33 |
34 | Now we have a path to an html file that we need to use somewhere. Clicking on the "Authenticate" button in the app opens the associated oauth page. Just replace `oauth?` and everything after that in the url with the flag path and the actual flag will be revealed. Submit the flag on the Hacker101 page to be awarded with 4 points.
35 |
36 | [Full code is available on GitHub](https://github.com/nibarius/learning-frida/blob/master/src/h101/oauth.js)
37 |
38 |
39 |
--------------------------------------------------------------------------------
/docs/_posts/2020-08-22-cybertruckchallange19-revisited.markdown:
--------------------------------------------------------------------------------
1 | ---
2 | layout: post
3 | title: "CyberTruckChallenge19 - revisited"
4 | date: 2020-08-22
5 | comment_issue_id: 8
6 | ---
7 | While writing my [previous post][previous-post] about [CyberTruckChallenge19][cybertruck] I noticed that it's possible intercept individual instructions in native code in addition to just functions. With this new knowledge I decided take on the third part of the CyberTruck challenge one more time.
8 |
9 | This requires a bit more knowledge about x64 assembly and Intel's [Introduction to x64 Assembly][assembly-intro] was of great help to me.
10 |
11 | ## Getting the hard coded key
12 | As we remember from last time, a pointer to the hard coded key is passed to `strlen`. Looking at the corresponding assembly we can see that the LEA instruction (load effective address) is used to load the pointer to the key into the RDI register. If you're not familiar with the LEA instruction here's a good [Youtube][youtube] video I found that explains the basics of it.
13 |
14 | {% include image.html url="/learning-frida/assets/cybertruck/lea.png" description="LEA loads the pointer pointing to the key into RDI" %}
15 |
16 | This means that if we intercept the following instruction (MOV at 0x760) we can just pick up the key from RDI, so let's do that.
17 |
18 | {% highlight javascript %}
19 | const SECRET_LENGTH = 32;
20 | var keyHook = Interceptor.attach(Module.findBaseAddress('libnative-lib.so').add(0x760), {
21 | onEnter: function(args) {
22 | console.log("Challenge3 key: " + this.context.rdi.readUtf8String(SECRET_LENGTH));
23 | keyHook.detach();
24 | }
25 | })
26 | {% endhighlight %}
27 |
28 | ## Getting the dynamic secret
29 |
30 | The dynamic secret is generated by xor:ing the key with some data, so now we need to find the xor operation in the assembly.
31 |
32 | {% include image.html url="/learning-frida/assets/cybertruck/xor.png" description="The secret generation" %}
33 |
34 | Here we can see the XOR instruction at 0x7bd which xor:s the content of ECX and EDX and put the result in ECX. So like before we intercept the instruction after XOR which is on 0x7bf and pick up the result from ECX.
35 |
36 | One small complication here is that Frida only provide access to the `R*X` registers and not the `E*X` registers. But the E registers is just the lower 4 bytes of the R registers, so we can just look at RCX instead.
37 |
38 | {% highlight javascript %}
39 | var secret = "";
40 | var len = 0;
41 | var secretHook = Interceptor.attach(Module.findBaseAddress('libnative-lib.so').add(0x7bf), {
42 | onEnter: function(args) {
43 | if (len++ < SECRET_LENGTH) {
44 | secret += String.fromCharCode(this.context.rcx);
45 | } else {
46 | console.log("Challenge3 secret: " + secret);
47 | secretHook.detach();
48 | }
49 | }
50 | });
51 | {% endhighlight %}
52 |
53 |
54 | With this in place we can run our script and extract both secrets quick and easy.
55 |
56 | {% include image.html url="/learning-frida/assets/cybertruck/secrets-extracted2.png" description="Challenge3 solved again" %}
57 |
58 | ## Caveats with attaching to instructions
59 |
60 | I noticed that it's not possible to use `Interceptor.attach` on both a function and an instruction inside that function at the same time. If you try to do this the app will crash when the instruction is executed.
61 |
62 | It also seems like it's not possible to attach to two instructions immediately following each other. Whenever I tried this the app crashed when the second instruction was hit. Intercepting two separate instructions with at least one other instruction in between them has worked fine for me though.
63 |
64 | [Full code is available on GitHub][my-code]
65 |
66 | [previous-post]: {{ site.baseurl }}{% post_url 2020-08-17-cybertruckchallange19 %}
67 | [assembly-intro]: https://software.intel.com/content/www/us/en/develop/articles/introduction-to-x64-assembly.html
68 | [youtube]: https://www.youtube.com/watch?v=mKcWIA1vKOw
69 | [cybertruck]: https://github.com/nowsecure/cybertruckchallenge19
70 | [my-code]:https://github.com/nibarius/learning-frida/blob/master/src/cybertruck19/cyber2.js
71 |
--------------------------------------------------------------------------------
/docs/_posts/2020-08-29-veryandroidoso.markdown:
--------------------------------------------------------------------------------
1 | ---
2 | layout: post
3 | title: "DEFCON Quals 2019 Veryandroidoso"
4 | date: 2020-08-29
5 | comment_issue_id: 10
6 | ---
7 |
8 | This time it's time to tackle the [DEFCON Quals 2019 Veryandroidoso][challenge] challenge. It's a reverse engineering challenge with one flag that you're supposed to find. It's a bit different from the challenges I've done earlier with more focus on reversing the algorithm rather than just finding the right place to pick up the secret from.
9 |
10 | ## Analyzing the application
11 |
12 | [Jadx][jadx] wasn't able to handle this apk very well. It produced difficult to read java code that was even incorrect in some places, so I ended up using [JD-GUI][jd-gui] instead.
13 |
14 | {% include image.html url="/learning-frida/assets/veryandroidoso/jdgui-vs-jadx.png" description="JD-GUI produced much better code than jadx, especially around the return statements seen here" %}
15 |
16 | The application is fairly simple, when you submit a flag it check that it begins with `OOO{`, ends with `}` and contains 18 hexadecimal characters in between. The hexadecimal characters are converted to nine integers between 0 and 255 and passed to the `solve()` method where all the work is done.
17 |
18 | The solve method is around 6000 lines of if statements that maps each argument to another number before it checks if they are correct. It also have methods like `scramble` and `getSecretNumber` that converts the input further. There is also a similar conversion made in native code before the final check is done whether or not the given argument is acceptable.
19 |
20 | This makes it really hard to figure out what the correct input is by just analyzing the code. There is also no check against a hard coded secret that we can hook. So we'll have to find the flag by brute force. However there are some brute force protection in place that we need to bypass first.
21 |
22 | ## Bypassing the brute force protection
23 | The `scramble` method is called several times every time the `solve` method is called, and it also calls the `sleep` method which sleeps for 500 milliseconds. So any attempt at brute focing `solve` will end up with sleeping most of the time. We can get around this by replacing the implementation of the `sleep` method with one that just returns the sleep duration directly without actually sleeping.
24 |
25 | We'll also count the number of times the sleep method is called, since this will come in handy later on.
26 |
27 | {% highlight javascript %}
28 | var sleepCount = 0;
29 | function bypassSleep(Solver) {
30 | Solver.sleep.implementation = function(input) {
31 | sleepCount++;
32 | return input;
33 | };
34 | }
35 | {% endhighlight %}
36 |
37 | ## Finding the correct values for arguments 1-4
38 |
39 | The beginning of the `solve` method looks as follows:
40 |
41 | {% highlight java %}
42 | int j = scramble(13);
43 | if (paramInt1 == 0) {
44 | i = m0(100, getSecretNumber(j));
45 | } else if (paramInt1 == 1) {
46 | i = m0(190, getSecretNumber(j));
47 | ...
48 | } else if (paramInt1 == 255) {
49 | i = m0(61, getSecretNumber(j));
50 | } else {
51 | i = 0;
52 | }
53 | if ((i & 0xFF) != 172)
54 | return false;
55 | j = scramble(j);
56 | if (paramInt2 == 0) {
57 | {% endhighlight %}
58 |
59 | It first calculates a new value based on the first argument, then it checks if that calculated value is 172. If it's not it returns false immediately. If the first argument is correct it continues with verifying the second argument by first calling `scramble` before it calculates a new value to compair against.
60 |
61 | As we remember from earlier, `scramble` calls `sleep` and we are counting how many times sleep is called. This means that if we call `solve` and `sleep` is called only once it means the first argument is wrong. If `sleep` is called twice or more it means the first argument is correct.
62 |
63 | {% highlight javascript %}
64 | var a = 0;
65 | do {
66 | sleepCount = 0;
67 | Solver.solve(a, 0, 0, 0, 0, 0, 0, 0, 0);
68 | if (sleepCount > 1) {
69 | console.log("accepted value for a found: " + a);
70 | tmp = a;
71 | }
72 | } while (sleepCount == 1 && a++ < 256)
73 | {% endhighlight %}
74 |
75 | With this we can find out what the expected value of the first parameter should be in at most 256 attempts. The second parameter works exactly the same so we can take the same approach there.
76 |
77 | The check for the third and fourth parameter accepts more than one value. So we have to modify our approach a bit and record all accepted values rather than only the first.
78 |
79 | ## Finding the correct values for arguments 5-6
80 |
81 | When we get to the fifth argument it's a bit different. Argument 5 is checked exactly as the ones before, but argument 6 is special. There is no call to `scramble` or any special conversions done, instead it's just a simple bitmask check. So the correct code for both argument 5 and 6 have to be correct for `scramble` to be called the next time.
82 |
83 | {% include image.html url="/learning-frida/assets/veryandroidoso/arg56.png" description="Code for argument 5 and 6" %}
84 |
85 | Since the sixth argument is checked against a bitmask it's easy to find all acceptable values.
86 |
87 | {% highlight javascript %}
88 | var acceptable = [];
89 | for(var i = 0; i < 256; i++) {
90 | if ((i & 0x41) == 65) {
91 | acceptable.push(i);
92 | }
93 | }
94 | {% endhighlight %}
95 |
96 | Once we have the sixth argument we can find the fifth using the same method as earlier and just pass in an acceptable value for argument six. Argument seven and eight works just like the first so those are straightforward to find.
97 |
98 | ## Finding the last argument
99 |
100 | Things gets a bit more complicated on the last argument. First the native function `m9` is called with one argument based on all previous arguments. Later the native function `m8` is called to generate the number that the final check is made against. The output of `m8` depends on what `m9` was called with earlier, which means all arguments must be correct before we can find the correct value of the last argument.
101 |
102 | We will just have to try all combinations of the previously found valid candidates together with all possible values of the last argument (0-255) until the solve method returns true.
103 |
104 | {% highlight javascript %}
105 | function solveLast(Solver, acceptable) {
106 | console.log("solving last, this will take a while...");
107 | for (var a = 0; a < acceptable[0].length; a++) {
108 | for (var b = 0; b < acceptable[1].length; b++) {
109 | for (var c = 0; c < acceptable[2].length; c++) {
110 | for (var d = 0; d < acceptable[3].length; d++) {
111 | for (var e = 0; e < acceptable[4].length; e++) {
112 | for (var f = 0; f < acceptable[5].length; f++) {
113 | for (var g = 0; g < acceptable[6].length; g++) {
114 | for (var h = 0; h < acceptable[7].length; h++) {
115 | for (var i = 0; i < 256; i++) {
116 | var ret = Solver.solve(acceptable[0][a], acceptable[1][b], acceptable[2][c],
117 | acceptable[3][d], acceptable[4][e], acceptable[5][f], acceptable[6][g],
118 | acceptable[7][h], i);
119 | if (ret) {
120 | var secret = [acceptable[0][a], acceptable[1][b], acceptable[2][c],
121 | acceptable[3][d], acceptable[4][e], acceptable[5][f], acceptable[6][g],
122 | acceptable[7][h], i];
123 | console.log("Secret found: " + secret);
124 | return secret;
125 | }
126 | ...
127 | {% endhighlight %}
128 |
129 | To check all options about a million calls to `solve` is needed, but luckily the right answer is found after around shortly after 30,000 calls so it doesn't take too long to find the code. For me the last step takes around 7 minutes.
130 |
131 | ## Constructing the flag
132 |
133 | With the correct values for all parameters known all that remains is to construct the real flag which is done by converting the 9 arguments to a hex string and adding `OOO{` and `}` to it.
134 |
135 | {% highlight javascript %}
136 | function getFlag(bytes) {
137 | var ret = "OOO{";
138 | for(var i = 0; i < bytes.length; i++) {
139 | ret += ("0" + bytes[i].toString(16)).slice(-2);
140 | }
141 | return ret + "}";
142 | }
143 | {% endhighlight %}
144 |
145 | {% include image.html url="/learning-frida/assets/veryandroidoso/solved.png" description="Veryandroidoso solved" %}
146 |
147 | [Full code is available on GitHub][my-code]
148 |
149 | [challenge]: https://archive.ooo/c/VeryAndroidoso/272/
150 | [jadx]: https://github.com/skylot/jadx
151 | [jd-gui]: https://java-decompiler.github.io
152 | [my-code]:https://github.com/nibarius/learning-frida/blob/master/src/veryandroidoso/veryandroidoso.js
153 |
--------------------------------------------------------------------------------
/docs/_posts/2021-01-23-sniffing-https-traffic.markdown:
--------------------------------------------------------------------------------
1 | ---
2 | layout: post
3 | title: "Sniffing https traffic on Android 11"
4 | date: 2021-01-23
5 | comment_issue_id: 12
6 | ---
7 |
8 | Being able to intercept, inspect and modify https traffic between an app and a server can be very useful. In this post I'm going to describe how you can do this with Burp Suite and the Android Studio Emulator running any Android version from 4 until 11 which is the latest version at the time of writing.
9 |
10 | ## Basics
11 |
12 | If you want to intercept traffic going in and out from a phone you can set up an http/https proxy server, make sure your phone uses it and then monitor all traffic going trough the proxy. By doing this you can easily see all http traffic, but since https traffic is encrypted the proxy is not able to read the data.
13 |
14 | To be able to intercept the https traffic the proxy could pretend to be the remote server and present it's own certificate to the phone when it is trying to connect to the remote server. However, since the proxy's certificate is not trusted by the phone the phone will not accept this. If you can get the phone to trust the proxy's certificate, for example by installing it as a trusted certificate, the phone will happily accept it when it is presented by the proxy.
15 |
16 | When this is done the proxy decrypts the incoming request and can do whatever it wants with it, before it sets up a new https connection with the remote server, pretending to be the app and thus acting as a [man in the middle (MITM)][mitm-attack].
17 |
18 | {% include image.html url="/learning-frida/assets/https-sniffing/mitm.png" %}
19 |
20 | ## Proxy and certificate setup
21 |
22 | ### Installing the proxy
23 |
24 | We'll get started by installing and preparing the proxy server we want to use. My proxy of choice is [Fiddler (classic)][fiddler], it's pretty nice looking and easy to use for both observing and modifying traffic. While it works great most of the time, I haven't been able to get it to work when I install it's certificate as a system certificate on an emulator. With physical phones it works fine, but when using an emulator it breaks down. If you manage to get Fiddler working well with the Android Studio emulator, please let me know.
25 |
26 | Due to this I have to settle with [Burp Suite Community Edition][burp] instead. It's a very powerful and popular choice even though I haven't learned to like it yet.
27 |
28 | Install Burp and find your way to `Proxy` → `Options` and click on the `Import / export CA certificate` button and export the `Certificate in DER format` to a convenient place. While on the options page also add a proxy listener bound to your computer's ip address and a suitable port and remember this for later.
29 |
30 | {% include image.html url="/learning-frida/assets/https-sniffing/burp-setup.png" description="Create a proxy listener and export the certificate." %}
31 |
32 | ### Converting the certificate to the correct format
33 |
34 | If you're working with Android 6 or lower you can skip this section, but on newer Android versions we will have to install the proxy's certificate as a system certificate. On Android, the system certificates are stored in PEM format in the folder `/system/etc/security/cacerts/` with the filename `.0`.
35 |
36 | The certificate we exported from Burp is in DER format, so we need to convert it to PEM using openssl and make sure that it has the correct filename:
37 |
38 | ```
39 | openssl x509 -inform der -in burp.cer -out certificate.pem
40 | cp certificate.pem `openssl x509 -inform pem -subject_hash_old -in certificate.pem | head -1`.0
41 | ```
42 |
43 |
44 | ## Emulator setup
45 |
46 | For maximum availability I'm using the emulator bundled with [Android Studio][android-studio]. [Genymotion][genymotion] is another option, but that's only available for free for personal use so that comes with some limitations or additional cost.
47 |
48 | When it comes to choosing which image to use for the emulator there are three main choices: An image without any Google APIs, one with Google APIs and one with Google Play. The Google Play images includes the Google Play app and access to Google Play services. This would be ideal as it's very similar to real phones, however they are considered [production builds][google-play-images] and root is not available on them.
49 |
50 | Google API images lacks the Google Play app, but includes access to Google Play services and are not considered production builds which means root access is available. There is rarely any reason to pick a plain Android version without Google APIs.
51 |
52 | It's possible to intercept https traffic on any Android version from at least 4 and up to at least 11, so let's go for the `Android 11.0 (Google APIs)` image.
53 |
54 | {% include image.html url="/learning-frida/assets/https-sniffing/system-image.png" description="An Android 11.0 (Google APIs) image is a good choice." %}
55 |
56 |
57 |
58 | ## Installing the certificate on the emulator
59 |
60 | ### Android 4 - 6
61 |
62 | On Android versions 4 to 6 the setup is very easy, you don't even need root access for this. This makes these Android versions a great choice when testing real non-rooted phones with access to all Google apps and services that still support these versions.
63 |
64 | Start by copying the certificate exported from Burp to the device or emulator. Then open up settings and find your way to `Security` → `Install from storage` and select the proxy's certificate. You then give the certificate any name, select `VPN and apps` as "Credential use", press OK and you're done.
65 |
66 | You can check that the certificate has been installed by going to `Security` → `Trusted credentials` → `User`. It's also possible to remove the certificate from here if you want to.
67 |
68 | ### Android 7 or newer
69 |
70 | On Android 7 (Nougat) or newer, [the system rejects][nougat-change] user added certificates by default and only allows them if the app explicitly opts in to it. To get the phone to use your proxy's certificate with any app, you have to install it as a system certificate. To do this you need root access and a writable system partition.
71 |
72 | #### Starting the emulator with writable system
73 |
74 | To be able to install the certificate, we need to start the emulator with a writable system. This is done by starting the emulator from command line with the parameter `-writable-system`. You also need to specify which system image you want to use, which you do with the `-avd` parameter. I called my image rRoot since that's easy to type and remember.
75 | ```
76 | ./emulator -avd rRoot -writable-system
77 | ```
78 |
79 | The location of the emulator executable depends on where you've installed your Android SDK. For me it is located in `D:\Android\sdk\emulator`.
80 |
81 | #### Disabling verified boot (Android 10 and newer)
82 |
83 | Starting with Android 10 it's [no longer possible][google-answer-writable-system] to get a writable system partition on the images obtained trough Android Studio due to [verified boot][verified-boot]. So before we can do any changes to the system directory we need to disable verified boot. Luckily enough it's [possible to do this][avbctl-change] using the `avbctl` command line tool.
84 |
85 | ```
86 | adb root
87 | adb shell avbctl disable-verification
88 | adb reboot
89 | ```
90 |
91 | #### Installing the certificate
92 |
93 | Before we can install the certificate we need to copy it to the device. The easiest way to do this is to drag-drop it onto the emulator to get it copied into the Download directory. Once that's done we can install it as a system certificate. Pay attention to the name of your certificate, it might not be the same as mine.
94 |
95 | ```
96 | adb root
97 | adb remount
98 | adb shell
99 | cp /sdcard/Download/9a5ba575.0 /system/etc/security/cacerts/
100 | chmod 644 /system/etc/security/cacerts/9a5ba575.0
101 | reboot
102 | ```
103 |
104 |
105 |
106 | **Note**: Android will only see your newly installed certificate if you start the emulator with `-writable-system`, so make sure to always start the emulator with this flag.
107 |
108 | #### Verifying that the certificate has been installed
109 |
110 | To verify that the phone has detected your certificate you can take a look at the list of trusted system credentials at:
111 | `Settings` → `Security` → `Advanced` → `Encryption & credentials` → `Trusted credentials` → `System`
112 |
113 | Scroll down in the very long alphabetic list and see if you can find your certificate. If you're using Burp you should be looking for a certificate named `PortSwigger`. If you can find it everything is working and you're ready to move on to the next step.
114 |
115 | If not, double check that you have installed the correct certificate with the correct name and that the permissions are correct. It's easy to accidentally change the permissions of the wrong certificate if using tab completion, it's happened more than once for me.
116 |
117 | {% include image.html url="/learning-frida/assets/https-sniffing/certificate-installed.png" description="Burp's certificate has been successfully installed as a system certificate." %}
118 |
119 | ## Setup the emulator to use the proxy
120 |
121 | With the certificate set up, the next step is to configure the emulator to connect to our proxy. There are several ways to do this, but the method that's been most reliable for me is to set up a new APN that uses the proxy. If you are using a physical device, it's probably better to edit the WiFi settings instead so that it uses a proxy.
122 |
123 | In the emulator, open up settings and navigate to `Network & internet` → `Mobile network` → `Advanced` → `Access Point Names` and press the plus button to add a new APN. On the Edit access point view that's shown you can provide anything as Name and APN. I use "Burp" to easily recognize it. Under Proxy and Port you enter the IP address and port of your proxy server from earlier. Now you open the three dot menu and press save and then select the new APN.
124 |
125 | With this setup all mobile traffic will go trough configured proxy. WiFi traffic will not go trough the proxy, so it's good to make sure that WiFi is disabled.
126 |
127 | {% include image.html url="/learning-frida/assets/https-sniffing/apn-setup.png" description="Configure the phone to connect to your proxy server." %}
128 |
129 | ## Setup complete
130 | Now we're finally ready and should be able to intercept both http and https traffic from the emulator. To verify that everything is working we can fire up the browser on the emulator and load and make sure that we can see the traffic in Burp.
131 |
132 | {% include image.html url="/learning-frida/assets/https-sniffing/it-works.png" description="Setup complete, we can now capture https traffic on a rooted Android 11 emulator." %}
133 |
134 | When analyzing network traffic you might notice that some apps works fine when you're not looking at the traffic, but refuse to communicate with their server when you are trying to intercept the traffic. This is usually because they have implemented [certificate pinning][pinning] as a method to prevent MITM attacks. This can be bypassed using Frida, but that's a topic for [another post][pinning-bypass].
135 |
136 |
137 | ### Attribution
138 | The [MITM illustration]({{ site.baseurl }}/assets/https-sniffing/mitm.png) in the beginning of the article is created by me (Niklas Barsk) and is available under the Creative Commons [CCBY 4.0][ccby] license. It is a derivative work based on [girl][girl-icon] and [Mmm][mmm-icon] by Xinh Studio, [freelancer][freelancer-icon] by Andrei Yushchenko and [Eye][eye-icon] by Flatart all from the [Noun Project][noun-project].
139 |
140 | [mitm-attack]: https://en.wikipedia.org/wiki/Man-in-the-middle_attack
141 | [nougat-change]: https://android-developers.googleblog.com/2016/07/changes-to-trusted-certificate.html
142 | [android-studio]: https://developer.android.com/studio
143 | [genymotion]: https://www.genymotion.com/desktop/
144 | [google-play-images]: https://developer.android.com/studio/run/managing-avds#system-image
145 | [google-answer-writable-system]: https://issuetracker.google.com/issues/144891973#comment23
146 | [avbctl-change]: https://android-review.googlesource.com/c/platform/external/avb/+/418399
147 | [verified-boot]: https://source.android.com/security/verifiedboot
148 | [fiddler]: https://www.telerik.com/download/fiddler
149 | [burp]: https://portswigger.net/burp/communitydownload
150 | [pinning]: https://expeditedsecurity.com/blog/what-is-certificate-pinning/
151 | [pinning-bypass]: {{ site.baseurl }}{% post_url 2022-11-18-bypassing-pinning %}
152 | [girl-icon]: https://thenounproject.com/icon/2095071/
153 | [freelancer-icon]: https://thenounproject.com/icon/3095387/
154 | [mmm-icon]: https://thenounproject.com/icon/352401/
155 | [eye-icon]: https://thenounproject.com/icon/2697635/
156 | [noun-project]: https://thenounproject.com/
157 | [ccby]: https://creativecommons.org/licenses/by/4.0/
--------------------------------------------------------------------------------
/docs/_posts/2021-02-08-cybergym.markdown:
--------------------------------------------------------------------------------
1 | ---
2 | layout: post
3 | title: "Cybergym 3.0 mobile challenges"
4 | date: 2021-02-08
5 | comment_issue_id: 13
6 | ---
7 |
8 | I recently came across the [mobile challenges][cyber-mobile] of the [Cybergym 3.0 CTF][cybergym-3] and decided to give the parts that can be solved by Frida a chance.
9 |
10 | ## Lab 1 - Planet X
11 |
12 | In the [first challenge][lab1] we're presented with lock screen where we're have to provide the correct pin to see the flag.
13 |
14 | {% include image.html url="/learning-frida/assets/cybergym/lab1-pin.png" description="The flag is protected by a pin code" %}
15 |
16 | If we open up the APK file in [Jadx][jadx] we can see that a random pin code is generated every time the app is run and that it's stored in a database. Unfortunately it's using [SQLChiper][sqlchiper] instead of the standard Android database APIs, which means the database can't be easily read. But this doesn't have to stop us, when looking closer at the code we can see that it's using `equalsIgnoreCase` to compare the input with the given pin code.
17 |
18 | {% include image.html url="/learning-frida/assets/cybergym/lab1-code.png" description="The pin checking part of the code" %}
19 |
20 | So if we just replace the `equalsIgnoreCase` implementation with something that returns true when we provide our own magic code we'll get the flag.
21 |
22 | {% highlight javascript %}
23 | Java.perform(function(){
24 | Java.use("java.lang.String").equalsIgnoreCase.implementation = function(a) {
25 | if (this == "5555") {
26 | console.log("Real passcode was: " + a);
27 | return true;
28 | }
29 | return this.equalsIgnoreCase(a);
30 | };
31 | });
32 | {% endhighlight %}
33 |
34 | Now we just run Frida with this script and provide the pin 5555 and we'll be rewarded the flag.
35 |
36 | {% include image.html url="/learning-frida/assets/cybergym/lab1-frida.png" description="Launching the app with Frida reveals the actual pin code" %}
37 |
38 | ## Lab 2 - Sherlocked
39 |
40 | The [second app][lab2] just show a message without really doing anything, so the first thing to do is to take a look at the code.
41 |
42 | {% include image.html url="/learning-frida/assets/cybergym/lab2-code.png" description="The MainActivity does not do much" %}
43 |
44 | The `MainActivity` doesn't do much, but it does call the method `ja.a` while ignoring the return value. So it could be interesting to take a look at what this method returns.
45 |
46 | {% highlight javascript %}
47 | Java.perform(function(){
48 | Java.use("ja").a.overload('java.lang.String', 'java.lang.String').implementation = function(a, b) {
49 | var ret = this.a(a, b);
50 | console.log("flag: " + ret);
51 | return ret;
52 | };
53 | });
54 | {% endhighlight %}
55 |
56 | {% include image.html url="/learning-frida/assets/cybergym/lab2-frida.png" description="This was not what we were hoping for" %}
57 |
58 | It turned this method was a red herring and a false flag. The real flag is a hard coded secret that's not referred to from anywhere in the code and can be found by searching for `cygym3{` in the content included in the APK.
59 |
60 | # Lab 3 - Morty's New Tool
61 |
62 | The [third challenge][lab3] presents a button labeled "Login" which prints a hashed version of the flag. Since we need the original flag, we have to take a look at the code and try to find the flag before it's hashed.
63 |
64 | {% include image.html url="/learning-frida/assets/cybergym/lab3-code.png" description="The interesting part of the code for lab 3" %}
65 |
66 | The `MainActivity` basically just calls `t().a()` to get the hashed flag. The `a()` method does some calculations that involves native code to create both a dummy flag that's not used at all and the real flag. Just before the method returns, on line 41, the flag is passed, as a byte array, to another method that creates the hash.
67 |
68 | The `String.getBytes()` function looks like an ideal candidate for hooking, so let's hook it and see what strings it's called on.
69 |
70 | {% highlight javascript %}
71 | Java.perform(function(){
72 | Java.use("java.lang.String").getBytes.overload('java.lang.String').implementation= function(a) {
73 | console.log("getBytes called on: " + this);
74 | return this.getBytes(a);
75 | };
76 | });
77 | {% endhighlight %}
78 |
79 | With this small script ready, we can just pass it on to Frida to get the flag.
80 |
81 | {% include image.html url="/learning-frida/assets/cybergym/lab3-frida.png" description="Running our script provides both the decoy flag and the real the flag" %}
82 |
83 | ## Lab 4 - Universe Weird C-132
84 |
85 | [Lab 4][lab4] is another problem where you're supposed to find hard coded secrets in the app resources that are not used at all by the app itself and then use that information to find the flag, so that is out of scope for this blog post. But if you're up to it, please go ahead and poke around in the resources and see if you can find something that points to the real location of the flag.
86 |
87 | [The code for labs 1-3 is available on GitHub](https://github.com/nibarius/learning-frida/blob/master/src/cybergym/)
88 |
89 | [cyber-mobile]: https://github.com/lucideus-repo/cybergym/tree/master/cybergym1/mobile
90 | [cybergym-3]: https://geekfreak18.wordpress.com/2020/10/10/cyber-gym-3-0-ctf-writeup/
91 | [jadx]: https://github.com/skylot/jadx
92 | [sqlchiper]: https://www.zetetic.net/sqlcipher/
93 | [lab1]: https://github.com/lucideus-repo/cybergym/tree/master/cybergym1/mobile/lab1
94 | [lab2]: https://github.com/lucideus-repo/cybergym/tree/master/cybergym1/mobile/lab2
95 | [lab3]: https://github.com/lucideus-repo/cybergym/tree/master/cybergym1/mobile/lab3
96 | [lab4]: https://github.com/lucideus-repo/cybergym/tree/master/cybergym1/mobile/lab4
97 |
--------------------------------------------------------------------------------
/docs/_posts/2021-08-26-hpandro-part1.markdown:
--------------------------------------------------------------------------------
1 | ---
2 | layout: post
3 | title: "hpAndro Vulnerable Application Challenges - part 1"
4 | date: 2021-08-26
5 | comment_issue_id: 18
6 | ---
7 |
8 | [hpAndro Vulnerable Application][hpandro] is an Android CTF with a lot of challenges (100 at the time of writing) and new challenges are added every now and then. The challenges are based on the [OWASP Mobile Security Testing Guide][mstg] and there are many different types of challenges available. I've solved all the challenges and in this and the next two blog posts ([part 2]({{ site.baseurl }}{% post_url 2021-08-28-hpandro-part2 %}), [part 3]({{ site.baseurl }}{% post_url 2021-08-29-hpandro-hidden-levels %})), I'm going to write about the ones I used Frida to solve.
9 |
10 | I've solved these challenges over several versions of the app, so the code in this post might require some changes to work with the latest version, especially when it comes to obfuscated names.
11 |
12 | ## Emulator detection
13 |
14 | Let's get started with one of the easier challenges for a couple of quick flags. The concept behind the emulator detection challenges is simple, the app does a series of emulator checks on startup and remembers the results. If you're not running on an emulator you won't be able to do the challenge. Then later on in each individual challenge the app does the same check one more time and if the emulator is not detected this time you are given the flag.
15 |
16 | This made solving the level fairly easy with Frida, you just have to find the correct check methods and make them return false to prevent the emulator detection.
17 |
18 | {% include image.html url="/learning-frida/assets/hpandro/emulator_check.png" description="If there only was some way to make this return false every time" %}
19 |
20 | One important thing is of course to only modify the implementation of the check methods after you have started the application so that the emulator is detected on startup. So make sure you load the script after you have started the app.
21 |
22 | {% highlight javascript %}
23 | Java.perform(function(){
24 | var emu = Java.use('com.hpandro.androidsecurity.utils.emulatorDetection.EmulatorDetector');
25 | emu.checkFiles.implementation = function(a, b){ return false }
26 | emu.checkIp.implementation = function(){ return false }
27 | ...
28 | });
29 | {% endhighlight %}
30 |
31 | ## Root Detection
32 | With the emulator detection challenges completed it's good to continue with the root detection challenges. These are extremely similar only that it checks for root instead of emulator. So on app startup root has to be detected and when trying to get each individual flag root must not be detected.
33 |
34 | {% include image.html url="/learning-frida/assets/hpandro/emulator_check.png" description="Some of the root detection methods" %}
35 |
36 | For me several root check methods failed initially, so I had to first make them all pass when starting the app. So I hooked all the relevant check methods and made them always return true. Then I started Frida using the `-f` flag without having the app running to make sure my script was loaded before Frida spawns the target activity: `frida -U --no-pause -l root.js -f com.hpandro.androidsecurity`
37 |
38 | {% highlight javascript %}
39 | Java.perform(function(){
40 | var root = Java.use('com.hpandro.androidsecurity.utils.rootDetection.RootDetectionUtils$Companion');
41 | root.checkFlagBusyBoxBinaries.implementation = function() { return true; }
42 | root.checkFlagSUExists.implementation = function() { return true; }
43 | ...
44 | });
45 | {% endhighlight %}
46 |
47 | Once the app has started it's just a matter of either commenting out the functions or changing them to return false instead and save the file. Since Frida automatically reloads the script when it changes, all that's left is to go through each root detection challenge and collect the flags.
48 |
49 | ## Device ID
50 | After completing the root detection challenges the device ID category is a good next step. In one way they are fairly similar to the previous challenges, you find the suitable method and change their return value. The main difference here is that you hook Android APIs instead of methods in the app itself.
51 |
52 | The main challenge is basically to find the right system api to hook, which can either be done by analyzing the app's source code or just by some simple queries using your favorite search engine. Here's an example of how you can modify the mac address which is required for one of the flags.
53 |
54 | {% highlight javascript %}
55 | Java.perform(function(){
56 | var wifi = Java.use('android.net.wifi.WifiInfo');
57 | wifi.getMacAddress.implementation = function() {
58 | return "01:02:03:04:05:06";
59 | };
60 | ...
61 | });
62 | {% endhighlight %}
63 |
64 | In some of the challenges there was no functionality to copy the flag which was fairly annoying. To avoid having to type the flag manually I wrote a small Frida script that intercepted the flag when it was presented and logged it to the console for easy copying. Here's an example from the WiFi level:
65 |
66 | {% highlight javascript %}
67 | var macLevel = Java.use('com.hpandro.androidsecurity.ui.activity.task.deviceID.DeviceMacTaskActivity');
68 | macLevel.showFlag.implementation = function(flag) {
69 | console.log("Flag: " + flag);
70 | this.showFlag(flag);
71 | }
72 | {% endhighlight %}
73 |
74 | ## Device ID - an extra challenge
75 |
76 | The IMSI level was a bit trickier than the others for me. I'm working with an Android 11 emulator and on Android 10 and newer the IMSI and a couple of other persistent device identifiers are no [longer available][device-id] at all. To complicate things [jadx-gui][jadx] failed to decompile the crucial `checkData` method so I couldn't easily see what it was doing or what to modify.
77 |
78 | Since the decompilation failed I had to analyze the Smali code for `checkData` instead. Searching for 431337133713373 in the code reveals the IMSI comparison part of the code.
79 |
80 | {% include image.html url="/learning-frida/assets/hpandro/imsi_smali.png" description="Smali code for the IMSI comparison" %}
81 |
82 | Here we can see that the comparison is done using the version of `kotlin.jvm.internal.Intrinsics` that takes two objects as arguments. By hooking this particular method we can make sure that it always returns true when the second argument is "431337133713373".
83 |
84 | {% highlight javascript %}
85 | Java.perform(function(){
86 | var intrinsics = Java.use('kotlin.jvm.internal.Intrinsics');
87 | intrinsics.areEqual.overload('java.lang.Object', 'java.lang.Object').implementation = function(a, b){
88 | if (b.toString() == "431337133713373") {
89 | return true;
90 | }
91 | return this.areEqual(a, b);
92 | }
93 | });
94 | {% endhighlight %}
95 |
96 | So rather than modify what value the app gets from the system we modify the comparison method so that everything is equal to 431337133713373 and the flag is ours.
97 |
98 | ## Authentication: 2FA - Response Manipulation
99 |
100 | Now for some change, let's tackle the 2FA - Response Manipulation challenge. In this challenge you receive a hashed OTP code from the server which is compared to the hashed version of the OTP code you provide.
101 |
102 | {% include image.html url="/learning-frida/assets/hpandro/response_manipulation.png" description="OTP input screen" %}
103 |
104 | Since the data we got from the server is hashed we can't see what the correct OTP code is, but that isn't actually needed. The OTP verification happens on the client side, so if we manipulate the response from the server (using for example [Burp](https://portswigger.net/burp)) to provide the hash of whatever OTP we provide the verification will succeed.
105 |
106 | We can create a small Frida script to see what the calculated hash of our input is and then just modify the response from the server to match this hash.
107 |
108 | {% highlight javascript %}
109 | Java.perform(function(){
110 | var otp = Java.use('com.hpandro.androidsecurity.ui.activity.task.authentication.responseMani.ResponseManipOTPActivity');
111 | otp.otpToSHA1.implementation = function(a) {
112 | var ret = this.otpToSHA1(a);
113 | console.log("input: " + a + " return: " + ret);
114 | return ret;
115 | }
116 | });
117 | {% endhighlight %}
118 |
119 | With this in place all that's left is a little bit of Burp magic to manipulate the response, but since this is a blog about Frida and not Burp I won't go into details on how to do that.
120 |
121 | With another flag claimed with the help of Frida it's time to wrap up for this time. When you're ready, [continue to part 2]({{ site.baseurl }}{% post_url 2021-08-28-hpandro-part2 %}) where we'll take on a couple of more challenges from the hpAndro vulnerable application.
122 |
123 | [All the Frida scripts written for these challenges are available on GitHub](https://github.com/nibarius/learning-frida/blob/master/src/hpandro/)
124 |
125 | [hpandro]: http://ctf.hpandro.raviramesh.info
126 | [mstg]: https://owasp.org/www-project-mobile-security-testing-guide/
127 | [device-id]: https://source.android.com/devices/tech/config/device-identifiers
128 | [jadx]: https://github.com/skylot/jadx
129 | [burp]: https://portswigger.net/burp
--------------------------------------------------------------------------------
/docs/_posts/2021-08-28-hpandro-part2.markdown:
--------------------------------------------------------------------------------
1 | ---
2 | layout: post
3 | title: "hpAndro Vulnerable Application Challenges - part 2"
4 | date: 2021-08-28
5 | comment_issue_id: 19
6 | ---
7 |
8 | [Last time]({{ site.baseurl }}{% post_url 2021-08-26-hpandro-part1 %}) we started with the [hpAndro Vulnerable Application][hpandro] CTF and solved several different challenges. Now it's time to take on another batch of challenges. Like last time I've been working on these challenges using several different versions of the app, so my code might not work with the latest version.
9 |
10 | ## Binary Protection: Native Function Call
11 | The first challenge we take on this time is the Native Function Call challenge from the binary protection category. This challenge has a native function called `hello()` which is called to provide instructions for the challenge. There is another native function called `flag()` that returns the flag if called, the only problem is that it's never called by the app.
12 |
13 | {% include image.html url="/learning-frida/assets/hpandro/native.png" description="The instructions given by hello()" %}
14 |
15 | You could of course reverse the native code and find the flag there, but I usually try to avoid that whenever possible. Instead, let's just do what the instructions say and call the `flag()` function to get the flag. One easy way to do that is to change the implementation of the `hello()` function so that it calls and returns the value of `flag()`. Then when you click the "Call hello() function" button you will be presented with the flag.
16 |
17 | {% highlight javascript %}
18 | Java.perform(function(){
19 | var bin = Java.use("com.hpandro.androidsecurity.ui.activity.task.binary.NativeFunTaskActivity");
20 | bin.hello.implementation = function() {
21 | var ret = this.flag();
22 | console.log("flag: " + ret);
23 | return ret;
24 | }
25 | });
26 | {% endhighlight %}
27 |
28 |
29 | ## Miscellaneous: Backdoor 6
30 |
31 | Next up is the 6th backdoor challenge. Unlike the other backdoor challenges this one is very well suited for Frida so let's tackle this now. In this challenge you have a dialog where you provide a pin code that's validated in native code. Like in the previous challenge you could reverse the native code and figure out the correct pin that way, but that's not what I'm doing.
32 |
33 | {% include image.html url="/learning-frida/assets/hpandro/pin.png" description="Please enter the correct PIN" %}
34 |
35 | When you submit your pin code, the native function `hello()` is called. If the pin is wrong it returns "NO", if it's correct it returns the flag. Instead of manually testing all the different possibilities let's automate it with Frida.
36 |
37 | To brute force the pin code we can change the implementation of the `hello()` function to a for loop that repeatedly calls `hello()` with all values between 0000 and 9999 until it returns something else than "NO".
38 |
39 | {% highlight javascript %}
40 | Java.perform(function(){
41 | var bd6 = Java.use("com.hpandro.androidsecurity.ui.activity.task.misc.Backdoor6Activity");
42 | bd6.hello.implementation = function(a) {
43 | var ret = "NO";
44 | for (let i = 0; i <= 9999; i++) {
45 | ret = this.hello(("000" + i).substr(-4,4));
46 | if (ret != "NO") {
47 | console.log("correct pin found at: " + i);
48 | break;
49 | }
50 | }
51 | return ret;
52 | }
53 | });
54 | {% endhighlight %}
55 |
56 | With this script running you can enter anything in the pin field, press the check button and you'll have the flag after a while.
57 |
58 | ## Symmetric Encryption
59 |
60 | Let's put the native code behind us and move on to network related things instead. Most of the symmetric encryption challenges worked the same way: an encrypted version of the flag is sent to us from the server. Since the flag is encrypted, intercepting the network traffic won't help. The good news is that the app decrypts the flag for us, the bad news is that the decrypted flag is not used anywhere and definitely not shown to us.
61 |
62 | {% include image.html url="/learning-frida/assets/hpandro/aes_code.png" description="The decryption method from the AES task" %}
63 |
64 |
65 | When you have Frida at your hands that doesn't have to be a problem. All you do is hook the decryption method and check what value it returns. Here's one example from the AES challenge:
66 |
67 | {% highlight javascript %}
68 | Java.perform(function(){
69 | var aes = Java.use('com.hpandro.androidsecurity.ui.activity.task.encryption.AESActivity');
70 | aes.decrypt.overload('java.lang.String', 'java.lang.String', '[B').implementation = function(a, b, c) {
71 | var decrypted = this.decrypt(a, b, c);
72 | console.log("Decrypted: " + Java.use('java.lang.String').$new(decrypted));
73 | return decrypted;
74 | }
75 | });
76 | {% endhighlight %}
77 |
78 | One thing worth noting here is that some of the decryption methods return a byte array instead of a string. So to get a readable version of the flag I pass the return value through the `String` constructor which has overloads for both strings and byte arrays.
79 |
80 | ### Honorable mention: Predictable Initialization Vector
81 |
82 | The "Predictable Initialization Vector" challenge is a bit special though. Even if there is a decode function here, it is never called at all. To solve this you have to call it yourself with the correct IV. I didn't actually use Frida for this, so I won't go into much details on how I solved it, but I basically copied the decompiled decrypt methods to my own small java program and brute forced the 4 digit pin.
83 |
84 | {% include image.html url="/learning-frida/assets/hpandro/predict.png" description="Predictable Initialization Vector task" %}
85 |
86 | One thing I found really hilarious about this challenge was that you are supposed to "Predict 4 digit IV" and that the challenge is called "Predictable Initialization Vector", so before running my brute force code I just tried one 4 digit code that felt probable and it turned out to be correct. It was indeed very predictable.
87 |
88 | Even though I didn't end up using Frida for this one it was probably one of my favorite challenges.
89 |
90 | ## WebSocket Traffic - Web Socket Secure (WSS)
91 |
92 | Let's move on to the last challenge for this time, the Web Socket Secure challenge. Like in the previous challenge the flag is sent to us from the server, but this time web sockets are used instead of http(s). This challenge was interesting to me since I've never tried to intercept web socket traffic before. I wasn't able to get the web socket traffic tunneled through my Burp Suite proxy so I used [Wireshark][wireshark] to capture the plain text traffic for the Web Socket task.
93 |
94 | In the WSS challenge the traffic is encrypted and I have no way of intercepting encrypted web socket traffic, so this is where Frida comes in. If you can't intercept the traffic on it's way to the app you can look at it after the app has received it instead. Having multiple options at hand is always great.
95 |
96 | The main challenge is to find where the requested data ends up after the request finishes. With some digging it's possible to track down `WebSocketSecureActivity$createWebSocketClient$1$onTextReceived$1` where the response is handled.
97 |
98 | {% include image.html url="/learning-frida/assets/hpandro/wss_code.png" description="WebSocketSecureActivity$createWebSocketClient$1$onTextReceived$1" %}
99 |
100 | Now that we know where the data ends up, we can hook the constructor of this class and log the data:
101 |
102 | {% highlight javascript %}
103 | Java.perform(function(){
104 | // Hook the method that is called when data is submitted over the websocket and take a look at it there.
105 | var ws = Java.use('com.hpandro.androidsecurity.ui.activity.task.websocket.WebSocketSecureActivity$createWebSocketClient$1$onTextReceived$1');
106 | ws.$init.implementation = function(a, b) {
107 | console.log("data retrieved: " + b);
108 | this.$init(a,b);
109 | }
110 | })
111 | {% endhighlight %}
112 |
113 | That's all for this time, but when you're ready please [continue to the third and final part]({{ site.baseurl }}{% post_url 2021-08-29-hpandro-hidden-levels %}) where I cover the last two challenges I solved using Frida.
114 |
115 | Also if you have some good guides on how to intercept and analyze secure non-http(s) traffic such as wss and other protocols using TLS I would be really happy if you could share it.
116 |
117 | [All the Frida scripts written for these challenges are available on GitHub](https://github.com/nibarius/learning-frida/blob/master/src/hpandro/)
118 |
119 | [hpandro]: http://ctf.hpandro.raviramesh.info
120 | [wireshark]: https://www.wireshark.org
--------------------------------------------------------------------------------
/docs/_posts/2021-08-29-hpandro-hidden-levels.markdown:
--------------------------------------------------------------------------------
1 | ---
2 | layout: post
3 | title: "hpAndro Vulnerable Application Challenges - part 3: the hidden levels"
4 | date: 2021-08-29
5 | comment_issue_id: 20
6 | ---
7 |
8 | In the two previous blog posts ([part1]({{ site.baseurl }}{% post_url 2021-08-26-hpandro-part1 %}), [part 2]({{ site.baseurl }}{% post_url 2021-08-28-hpandro-part2 %})) we've tackled a bunch of the [hpAndro Vulnerable Application][hpandro] challenges, but there's still a bit remaining.
9 |
10 | After working with a couple challenges you'll start to understand how the app works. Challenges that are available have a pale green background in the menu while challenges that are planned but have not yet been created have a white background. If you compare the list of challenges on the flag submission page with the levels available in the app you'll spot something odd.
11 |
12 | There are two levels that you can submit flags for even though they look unavailable in the app (in versions 1.1.12 - 1.1.15 at least). This is what we are going to be focusing on today.
13 |
14 | {% include image.html url="/learning-frida/assets/hpandro/hidden_levels.png" description="This doesn't add up" %}
15 |
16 | ## Looking closer
17 |
18 | If you open up the "QEMU" or the "Check Package Name" challenges they behave like many of the other incomplete challenges. There is a description there, but the task button does nothing when clicked. If we analyze the apk we can see that there are classes and an implementation available for both these challenges.
19 |
20 | {% include image.html url="/learning-frida/assets/hpandro/qemu_code.png" description="There is some kind of implementation available for the QEMU challenge" %}
21 |
22 | It seems like the challenge is available, it's just that the task button is not yet hooked up to the actual task activity. So to be able to solve these challenges we have to first implement the missing implementation and connect the task button with the appropriate activities.
23 |
24 | ## Figuring out what to do
25 |
26 | If you analyze the code and track down where the task button is handled you will eventually find your way to `HomeWebViewFragment.redirectToTask()`. Even though it's a huge method with if statements for all challenges, it's fairly simple. It basically just calls `startActivity` with an intent pointing to the correct activity.
27 |
28 | {% include image.html url="/learning-frida/assets/hpandro/redirect_to_task.png" description="The start of the redirectToTask method" %}
29 |
30 | What we want to do is to replace the `redirectToTask()` method with one that can open the two hidden challenges. In Java it would look something like this:
31 |
32 | {% highlight java %}
33 | private final void redirectToTask() {
34 | String menuName = this.mMenuModel.getMenuName();
35 | if (Intrinsics.areEqual(menuName, "Check Package Name")) {
36 | startActivity(new Intent(getActivity(), PackageNamesActivity.class));
37 | } else if (Intrinsics.areEqual(menuName, "QEMU")) {
38 | startActivity(new Intent(getActivity(), QEMUDetectionActivity.class));
39 | } else {
40 | // the original implementation
41 | }
42 | }
43 | {% endhighlight %}
44 |
45 | ## Implementing the missing functionality
46 |
47 | ### Detecting which challenge is open
48 |
49 | The first thing we need here is the menu name to know which task we are on. To get this we can start by calling the original `redirectToTask()` method and hook the `getMenuName()` method to pick up the return value from there.
50 |
51 | {% highlight javascript %}
52 | var lastMenuName = "";
53 | var act = Java.use('com.hpandro.androidsecurity.utils.fragment.HomeWebViewFragment');
54 | act.redirectToTask.implementation = function() {
55 | this.redirectToTask();
56 | ...
57 | var targetClass = null;
58 | if (lastMenuName == "Check Package Name") {
59 | targetClass = Java.use('com.hpandro.androidsecurity.ui.activity.task.emulatorDetection.PackageNamesActivity');
60 | }
61 | else if (lastMenuName == "QEMU") {
62 | targetClass = Java.use('com.hpandro.androidsecurity.ui.activity.task.emulatorDetection.QEMUDetectionActivity');
63 | }
64 | ...
65 | }
66 |
67 | Java.use('com.hpandro.androidsecurity.ui.menu.MenuModel').getMenuName.implementation = function() {
68 | var ret = this.getMenuName();
69 | lastMenuName = ret;
70 | return ret;
71 | }
72 | {% endhighlight %}
73 |
74 | The nice thing with doing this is that since we call the original implementation all other challenges will be handled normally and we won't break any other functionality. We only add new functionality for the two challenges that weren't implemented.
75 |
76 | ### Connecting the Task button with the correct activity
77 |
78 | With this in place we now need to call the `getActivity()` method to get the activity, create an Intent and then call `startActivity()` on it. To be able to call class methods a live instance of the `HomeWebViewFragment` class is needed which can be obtained by using the Frida function `Java.choose()`. With access to a live instance of the class it's pretty straightforward to create the intent and start the activity.
79 |
80 | {% highlight javascript %}
81 | Java.choose('com.hpandro.androidsecurity.utils.fragment.HomeWebViewFragment', {
82 | onMatch: function(instance) {
83 | var activity = instance.getActivity();
84 |
85 | ... // Select targetClass as described above
86 |
87 | if (activity != null && targetClass != null) {
88 | var intent = Java.use('android.content.Intent').$new(activity, targetClass.class);
89 | instance.startActivity(intent);
90 | }
91 | },
92 | onComplete: function() {}
93 | });
94 | {% endhighlight %}
95 |
96 | With all of this in place it's now possible to run the script with Frida and open the hidden challenges.
97 |
98 | ## Solving the actual challenges
99 |
100 | First up is the QEMU challenge. Let's click the "Detect emulator" button and see what happens.
101 |
102 | {% include image.html url="/learning-frida/assets/hpandro/qemu_flag.png" description="We got the flag!?" %}
103 |
104 | Okay, we were given the flag right away without having to do any emulator check bypass. Let's try with the package name check challenge... oh, same there as well. Maybe there's a reason why these challenges are not generally available yet?
105 |
106 | But hey, a flag is a flag and finding and solving these two challenges was probably my favorite part of the whole hpAndro CTF. I felt a great sense of accomplishment when I managed to solve these and being the first person who found these flags definitely added to the feeling.
107 |
108 | {% include image.html url="/learning-frida/assets/hpandro/meme.png" description="This was my feeling when the flags were accepted." %}
109 |
110 |
111 |
112 | [The full code for these levels are available on GitHub](https://github.com/nibarius/learning-frida/blob/master/src/hpandro/emulator.js)
113 |
114 | [hpandro]: http://ctf.hpandro.raviramesh.info
115 |
--------------------------------------------------------------------------------
/docs/_posts/2022-05-21-sniffing-tls-traffic.markdown:
--------------------------------------------------------------------------------
1 | ---
2 | layout: post
3 | title: "Sniffing TLS traffic on Android"
4 | date: 2022-05-21
5 | comment_issue_id: 25
6 | ---
7 |
8 | In the article [Sniffing https traffic on Android 11][burp-article] I described how you can intercept https traffic on Android. This is often very convenient, but sometimes you need to go deeper and look at the raw network packets. If TLS is used things get complicated, so in this article I'm going to explain how to intercept generic TLS traffic that goes to and from an Android device.
9 |
10 | To do this you will need a rooted Android device (or emulator) that's connected to a computer using adb.
11 |
12 | ## Wireshark
13 |
14 | [Wireshark][wireshark] is a great tool for capturing raw network packets, but if the traffic is encrypted with TLS it makes things complicated.
15 |
16 | {% include image.html url="/learning-frida/assets/tls-sniffing/wireshark-encrypted.png" description="The traffic is all encrypted, you can't really see much more than the domain" %}
17 |
18 | There are two ways that Wireshark can decrypt TLS traffic. The first is using the private key the server is using to encrypt the traffic, but this is something you generally don't have access to when analyzing Android applications.
19 |
20 | The other way is to provide Wireshark with the [pre-master secret][pre-master]. This is generated by the client when setting up a secure connection with the server. If Wireshark has the pre-master secret it will be able to decrypt the traffic.
21 |
22 | Curl and browsers such as Chrome and Firefox for computers can generate these secrets when the connection is set up. But if you want to intercept traffic from other programs or from Android you will generally be out of luck.
23 |
24 | Since we're interested in intercepting TLS traffic on Android this means we can't use Wireshark to decrypt the traffic. What we need is a TLS proxy that is capable of decrypting TLS encrypted traffic.
25 |
26 | ## PolarProxy
27 | [PolarProxy][polarproxy] is a neat tool that can help us. It is a transparent TLS proxy that decrypts TLS traffic and can save the decrypted traffic as pcap files. These can later be analyzed using Wireshark. PolarProxy is free to use and available for both Linux and Windows so it should be available to most people.
28 |
29 | Since [version 0.9][version09] PolarProxy can be run as a transparent proxy, a SOCKS proxy or a HTTP CONNECT proxy. I haven't been able to get the SOCKS proxy or HTTP CONNECT proxy approach to work for my use cases so I'm going with the transparent proxy approach. Feel free to leave a comment if you can get the SOCKS or HTTP CONNECT proxy working together with Android.
30 |
31 | ### The PolarProxy certificate
32 |
33 | Since PolarProxy intercepts and decrypts all TLS traffic going through it, it encrypts the traffic with its own certificate. To make sure that the Android device doesn't reject the traffic we need to install PolarProxy's certificate as a trusted certificate.
34 |
35 | The first step is to get the actual certificate. To do this you need to start PolarProxy with the `--certhttp` argument. In the documentation on their web page they are using port 10080 for this, but you should avoid it. Many modern browsers are [blocking port 10080][10080], so use something else, like for example 10011.
36 |
37 | Start PolarProxy with `PolarProxy -p 443,80 --certhttp 10011` and load `localhost:10011` in a browser to download PolarProxy's certificate.
38 |
39 | After you've downloaded the certificate you can shut down PolarProxy again and install the certificate on your Android device in the same way as described in [Sniffing https traffic on Android 11][burp-article].
40 |
41 | ### Running PolarProxy
42 |
43 | Decide which ports you want PolarProxy to listen to and use the `-p` argument to specify this. If you want PolarProxy to listen to several ports you can use several `-p` arguments. This argument takes a comma separated list of numbers. The first specifies which port to listen to, the second which port to use in the decrypted pcap file. The third is optional and specifies the outgoing port, if not specified the listening port is used.
44 |
45 | You also want to use the `-w` argument and provide a filename to specify where the decrypted traffic should be saved.
46 |
47 | As an example `PolarProxy -p 3883,3883 -p 443,80 -w polarproxy.pcap` would start PolarProxy and listen to port 3883 and 443 and save the decrypted traffic in the file `polarproxy.pcap`.
48 |
49 | {% include image.html url="/learning-frida/assets/tls-sniffing/running-polarproxy.png" description="Running PolarProxy and listening on two ports" %}
50 |
51 | ## Android setup
52 | Since PolarProxy is run as a transparent proxy we can't use the normal proxy settings and set up PolarProxy as a proxy. Instead we need to route the device's traffic on the desired ports through PolarProxy. This can be done by using `adb reverse` and modifying the `iptables` on the device.
53 |
54 | ### adb reverse
55 | `adb reverse` is used to set up a reverse socket connection from the Android device to the computer that the device is connected to. Since PolarProxy is listening to port 443 and 3883 we want to set up two reverse connections:
56 |
57 | ```
58 | adb reverse tcp:443 tcp:443
59 | adb reverse tcp:3883 tcp:3883
60 | ```
61 |
62 | The first argument specifies the port on the Android device while the second specifies the port on the computer the device is connected to. After running this any connection made to port 443 or 3883 on the Android device will be forwarded to the matching port on the computer.
63 |
64 | Please keep in mind that this forwarding is only active as long as the adb server is running. If it's restarted you'll have to set up the connection again.
65 |
66 | {% include image.html url="/learning-frida/assets/tls-sniffing/adb-reverse.png" description="A reverse connection set up between Android and the computer" %}
67 |
68 | ### iptables
69 |
70 | With the port forwarding in place we now need to make sure that all outgoing traffic on port 443 and 3883 on the Android device gets routed to port 443 and 3883 on the device. We'll do this by modifying `iptables`, which requires root access.
71 |
72 | ```
73 | adb root
74 | adb shell
75 | iptables -t nat -A OUTPUT -p tcp --dport 443 -j DNAT --to-destination 127.0.0.1:443
76 | iptables -t nat -A OUTPUT -p tcp --dport 3883 -j DNAT --to-destination 127.0.0.1:3883
77 | ```
78 |
79 | If you're not familiar with iptables the [man pages][man iptables] is a good source of information regarding what the options does. In this case the options we use are:
80 |
81 | * `-t nat` to specify that we want to work with the nat table.
82 | * `-A OUTPUT`, which means that we are going to append a rule to the `OUTPUT` chain which is used to alter locally generated packets before routing.
83 | * `-p tcp` to specify the protocol
84 | * `--dport` to specify the port
85 | * `-j DNAT --to-destination 127.0.0.1:443` to define the new target for the packets being routed.
86 |
87 | Basically we're adding two rules that say that any outgoing packets on port 443 and 3883 should be routed to 127.0.0.1 using the same port.
88 |
89 | If you want to experiment more with iptables two useful commands are `iptables -t nat -L` to list all rules on the nat table and `iptables -t nat -D OUTPUT 1` to delete the first rule in the OUTPUT chain of the nat table. You don't have to be afraid of breaking things. If you reboot your device the iptables will be reset to the original state.
90 |
91 | {% include image.html url="/learning-frida/assets/tls-sniffing/iptables-setup.png" description="iptables has been configured to redirect outgoing traffic to localhost" %}
92 |
93 | ## Putting it all together
94 |
95 | With this setup, any outgoing request on port 443 and 3883 on the Android device will be redirected to localhost, or 127.0.0.1, on the Android device thanks to the `iptables` configuration. The `adb reverse` connection forwards these ports to the same ports on the connected computer on which PolarProxy is listening.
96 |
97 | {% include image.html url="/learning-frida/assets/tls-sniffing/chart.png" description="Request flow when making a request on the Android device" %}
98 |
99 | ## Making sure it works
100 |
101 | Now it's time to test that it works. Open up a browser on your phone and load `https://example.com/`. As the page is loaded and requests are made you should see that PolarProxy is decoding the traffic.
102 |
103 | {% include image.html url="/learning-frida/assets/tls-sniffing/polar-proxy-working.png" description="PolarProxy is now intercepting traffic on port 443 and 3883" %}
104 |
105 | Close down PolarProxy to get the file with the decrypted traffic flushed to disk and open it up in Wireshark to analyze the traffic.
106 |
107 | {% include image.html url="/learning-frida/assets/tls-sniffing/wireshark-decrypted.png" description="The decrypted network traffic can now be viewed in Wireshark" %}
108 |
109 | ## Summary
110 | If you know what you're doing it's actually not that hard to intercept generic TLS traffic from a rooted Android device and this article has hopefully provided you with that knowledge. As a quick reference, here are the commands needed to intercept traffic on port 443 once you've done the initial setup and installed the PolarProxy certificate on your device.
111 |
112 | ```
113 | # Start PolarProxy
114 | PolarProxy -p 443,80 -w polarproxy.pcap
115 |
116 | # Setup adb reverse connection
117 | adb reverse tcp:443 tcp:443
118 |
119 | # iptables setup
120 | adb root
121 | adb shell
122 | iptables -t nat -A OUTPUT -p tcp --dport 443 -j DNAT --to-destination 127.0.0.1:443
123 | ```
124 |
125 |
126 |
127 | [burp-article]: {{ site.baseurl }}{% post_url 2021-01-23-sniffing-https-traffic %}
128 | [wireshark]: https://www.wireshark.org
129 | [pre-master]: https://wiki.wireshark.org/TLS#using-the-pre-master-secret
130 | [polarproxy]: https://www.netresec.com/?page=PolarProxy
131 | [version09]: https://www.netresec.com/?page=Blog&month=2022-01&post=PolarProxy-0-9-Released
132 | [10080]: https://www.seidengroup.com/2021/06/02/issues-with-port-10080/
133 | [man iptables]: https://linux.die.net/man/8/iptables
--------------------------------------------------------------------------------
/docs/_posts/2022-05-24-nahamcon.markdown:
--------------------------------------------------------------------------------
1 | ---
2 | layout: post
3 | title: "Solving the 2022 NahamCon's CTFs"
4 | date: 2022-05-24
5 | comment_issue_id: 26
6 | ---
7 |
8 | Recently I stumbled upon the [mobile challenges][challenges] from the [NahamCon][nahamcon] CTF. Two of them were suitable to solve with [Frida][frida], so I decided to give it a try.
9 |
10 | ## Click Me
11 |
12 | The Click Me challenge is a small application with a picture of a cookie and a "Get flag" button. If you click on the cookie a counter is increased and if you click the button it tells you that you do not have enough cookies.
13 |
14 | To get started, let's take a look at the code with [jadx-gui][jadx]. Just open up the click_me.apk and look for the `MainActivity`. Here we find an interesting method called `getFlagButtonClick`
15 |
16 | {% include image.html url="/learning-frida/assets/2022-nahamcon/clickme-code.png" description="The on click handler for the flag button" %}
17 |
18 | Here we can see that the cookie has to be clicked almost hundred million times before the flag is presented. In the `cookieViewClick` method we can also see that the counter will stop increasing at roughly 13 million clicks. So clicking the cookie is obviously not the way to get the flag.
19 |
20 | What we do instead is to write a small Frida script that sets the `CLICKS` member to the desired value when the flag button is clicked.
21 |
22 | {% highlight javascript %}
23 | Java.perform(function(){
24 | var ma = Java.use("com.example.clickme.MainActivity");
25 | ma.getFlagButtonClick.implementation = function(view){
26 | this.CLICKS.value = 99999999;
27 | this.getFlagButtonClick(view);
28 | }
29 | });
30 | {% endhighlight %}
31 |
32 | Now we just launch Frida and load this script while the app is running (`frida -U -l clickme.js com.example.clickme`) and click the button to get the flag.
33 |
34 | {% include image.html url="/learning-frida/assets/2022-nahamcon/clickme-flag.png" description="Flag acquired" %}
35 |
36 | ## Secure Notes
37 |
38 | The Click Me challenge was a good warm up before taking on the Secure Notes challenge, which is a bit more of a challenge. This app asks you to provide a 4 digit pin, and will most likely tell you that it's the wrong password.
39 |
40 | {% include image.html url="/learning-frida/assets/2022-nahamcon/notes-login.png" description="The login activity" %}
41 |
42 | ### Looking at the code
43 |
44 | Let's take a look at the code using jadx-gui again. Here it's the `LoginActivity` that's most interesting to start with.
45 |
46 | {% include image.html url="/learning-frida/assets/2022-nahamcon/notes-onclick.png" description="The onClick handler" %}
47 |
48 | Here we can see that the onClick handler for the button calls `d.k` with the provided pin code repeated four times and two files as arguments. If an exception is thrown the "Wrong password" error message is shown, if no exception is thrown the main activity is launched.
49 |
50 | Time to take a look at what the `d.k` method does.
51 |
52 | {% include image.html url="/learning-frida/assets/2022-nahamcon/notes-dk.png" description="The d.k method" %}
53 |
54 | This method uses AES to decrypt one of the files with the stretched pin as key. If the decryption fails, for example due to an invalid key, an exception is thrown. If it succeeds the decrypted data is written to the other file.
55 |
56 | When the main activity is later opened it will read the decrypted data and show it on the screen. So to get the flag we need to guess the right pin code. With only 10,000 different possible pins a brute force approach works really well.
57 |
58 | ### Hooking the decryption method
59 |
60 | Let's hook the `d.k` method since that provides convenient access to the encrypted file. Then instead of calling it with the provided pin, we just call it with all pins sequentially until we find a pin that doesn't cause an exception to be thrown.
61 |
62 | {% highlight javascript %}
63 | Java.use("o.d").k.implementation = function(pin, input, output) {
64 | console.log("Starting pin brute force attempt");
65 | for(let i = 0; i < 10000; ++i) {
66 | let toTry = String(i).padStart(4, '0');
67 | let nextPin = "" + toTry + toTry + toTry + toTry;
68 | try {
69 | this.k(nextPin, input, output);
70 | console.log("Correct pin found: " + toTry);
71 | return;
72 | } catch (ex) {}
73 | }
74 | }
75 | {% endhighlight %}
76 |
77 | {% include image.html url="/learning-frida/assets/2022-nahamcon/notes-v1-frida.png" description="The pin was found surprisingly fast" %}
78 |
79 | Running this finds a pin quickly, but something is wrong. The main activity opens up, but it doesn't show anything. It seems like we found an incorrect pin that can decrypt the data without throwing an exception. But, since the decryption key is wrong, the decrypted data is just garbage.
80 |
81 | ### Finding the correct pin
82 |
83 | We need to find a way to check if the data looks reasonable after decryption. To do that, we have to know what kind of data we're looking for, so let's open up the main activity and see what it expects.
84 |
85 | {% include image.html url="/learning-frida/assets/2022-nahamcon/notes-mainactivity.png" description="Part of the MainActivty's onCreate() method" %}
86 |
87 | By looking at the `onCreate()` method we can see that the data is supposed to be a JSON object. A quick and easy heuristic for detecting JSON is to check if the first byte is `{` (or 0x7b in hex).
88 |
89 | Now that we know what to look for, we need to find a suitable place to do the check. Looking back at `d.k` we can see that the decrypted data is written to another file with `FileOutputStream.write()`. This is a convenient method to hook since it's called with the decrypted data.
90 |
91 | {% highlight javascript %}
92 | let fos = Java.use("java.io.FileOutputStream");
93 | fos.write.overload('[B').implementation = function(bArr) {
94 | if (bArr[0] == 0x7b) {
95 | this.write(bArr);
96 | success = true;
97 | }
98 | }
99 | {% endhighlight %}
100 |
101 | Here we hook the `write()` method that takes a byte array as input. If the first byte is 0x7b (or `{`) we call the original write method and set a `success` variable to true so that we can use this to break the brute force attempt in the `d.k` hook.
102 |
103 | ### Iteration optimizations
104 |
105 | Now we should be able to run through all pins again until we find the right one to get the flag. But I felt like writing a bit more code so I decided to try to minimize the number of iterations needed to find the correct pin.
106 |
107 | There are two obvious ways of iterating through 10,000 pins. The first is that you start at 0000 and go up to 9999. The other is starting at 9999 and going down.
108 |
109 | I'm guessing that the pin is not a completely random pin, but something chosen by the creator of the CTF. If they would choose a pin close to 0000 or close to 9999 it could be found even with a very inefficient brute force method, or even by manual testing using one of the obvious methods. I'm assuming that the CTF maker has taken this into consideration and chosen a pin that's somewhere close to the middle so that you have to try several thousand pins regardless of what direction you iterate in.
110 |
111 | Due to this I'm taking a different approach and start with the pin 5000 and iterate outwards in both directions. That way I'll find pins close to the middle faster than pins toward the end.
112 |
113 | To be able to do this let's write a small function that calculates the next offset from the start pin given the current one:
114 |
115 | {% highlight javascript %}
116 | function nextOffset(offset) {
117 | if (offset >= 0) {
118 | offset++;
119 | }
120 | return offset * -1;
121 | }
122 | {% endhighlight %}
123 |
124 | This function basically flips the sign every time it's called and increases the value by one every second time. So when called a couple of times, with the previous offset as a parameter it will return: `-1, 1, -2, 2, -3, 3, ...`.
125 |
126 | Now we can use this function to iterate outwards from 5000 until we find the pin:
127 |
128 | {% highlight javascript %}
129 | let success = false;
130 | Java.use("o.d").k.implementation = function(pin, input, output) {
131 | let start = 5000;
132 | let offset = 0;
133 | success = false;
134 | console.log("Starting pin brute force attempt");
135 | while (Math.abs(offset) <= 5000) {
136 | toTry = String(start + offset).padStart(4, '0');
137 | let nextPin = "" + toTry + toTry + toTry + toTry;
138 | try {
139 | this.k(nextPin, input, output);
140 | if (success) return;
141 | } catch (ex) {}
142 | offset = nextOffset(offset);
143 | }
144 | }
145 | {% endhighlight %}
146 |
147 | ### Getting the flag
148 |
149 | With this, all the code is in place and we can just load the script and tap the submit button to find the correct one. With some additional printouts added we also get some progress details from the Frida script.
150 |
151 | {% include image.html url="/learning-frida/assets/2022-nahamcon/notes-v2-frida.png" description="Running the Frida script now finds the correct pin" %}
152 |
153 | Since we abort the brute force attempt when we find the correct pin there will be no exception thrown and the main activity will be opened automatically showing us the flag.
154 |
155 | {% include image.html url="/learning-frida/assets/2022-nahamcon/notes-flag.png" description="Secure Notes flag found" %}
156 |
157 | The complete code for both these challenges is [available on GitHub][my-code].
158 |
159 | [nahamcon]: https://www.nahamcon.com
160 | [challenges]: https://github.com/evyatar9/Writeups/tree/master/CTFs/2022-NahamCon_CTF/Mobile
161 | [frida]: https://frida.re
162 | [jadx]: https://github.com/skylot/jadx
163 | [my-code]: https://github.com/nibarius/learning-frida/tree/master/src/2022-nahamcon/
--------------------------------------------------------------------------------
/docs/_posts/2022-08-07-fridalab.markdown:
--------------------------------------------------------------------------------
1 | ---
2 | layout: post
3 | title: "Taking a look at FridaLab"
4 | date: 2022-08-07
5 | comment_issue_id: 28
6 | ---
7 |
8 | Someone shared the [Awesome-Android-Security][aas] repository on Twitter recently and I've started looking at some of the resources there. One thing that caught my attention was the [FridaLab][fridalab] which is a beginner friendly [Frida][frida] intro. It consists of eight challenges meant to be used to practice your Frida skills. The first ones are very easy, but it gets a bit more difficult toward the end, but all in all it's a good introduction resource.
9 |
10 | If you're new to Frida I suggest that you give it a try for yourself before continuing reading. You can then compare your solutions with mine or get some help if you get stuck on some challenge.
11 |
12 | ## Solving it all in one go
13 |
14 | When looking at the decompiled code for the application you can see that there is a field called `completeArr` in `MainActivity`. There is also a method called `changeColors` that checks which challenges have been solved and colors the solved ones green.
15 |
16 | So one easy way of solving everything is to just update the array so that it indicates that everything has been solved:
17 |
18 | {% highlight javascript %}
19 | Java.perform(function(){
20 | let lab = Java.use("uk.rossmarks.fridalab.MainActivity");
21 | lab.changeColors.implementation = function() {
22 | this.completeArr.value = Java.array('int', [1, 1, 1, 1, 1, 1, 1, 1]);
23 | this.changeColors();
24 | }
25 | });
26 | {% endhighlight %}
27 |
28 | {% include image.html url="/learning-frida/assets/fridalab/all-solved.png" description="All challenges solved" %}
29 |
30 | ## Solving the challenges the intended way
31 |
32 | However, this doesn't really solve any challenges and while it was fun it robs us from the experience that solving the challenges provides. So let's solve them the right way.
33 |
34 | ### Challenge 1 - setting a static field
35 |
36 | In the first challenge we need to set the value of the static field chall01 to 1. You can set the values of static fields by just assigning the desired value to the `.value` attribute of the field.
37 |
38 | {% highlight javascript %}
39 | Java.use("uk.rossmarks.fridalab.challenge_01").chall01.value = 1;
40 | {% endhighlight %}
41 |
42 | ### Challenge 2 - calling a non static method
43 |
44 | The second challenge requires us to call the main activity's method `chall02()`. To be able to call a method we need a reference to the class. If you're hooking a method you can just use `this` in your method implementation.
45 |
46 | Another way to get hold of a class instance is to enumerate all live instances of a certain class by using `Java.choose(className, callbacks)`. `callbacks` is an object with two functions, the first is `onMatch` which is called for each found instance. If this returns "stop" the enumeration is canceled early. The other fuction is `onComplete` which is called when all instances have been enumerated.
47 |
48 | I'm using the `Java.choose` approach here:
49 |
50 | {% highlight javascript %}
51 | Java.choose("uk.rossmarks.fridalab.MainActivity", {
52 | onMatch : function(instance) {
53 | instance.chall02();
54 | return "stop";
55 | },
56 | onComplete:function() {}
57 | });
58 | {% endhighlight %}
59 |
60 | ### Challenge 3 - changing the return value of a method
61 |
62 | The third challenge is straightforward, here we just need to change the return value of the method `chall03`.
63 |
64 | {% highlight javascript %}
65 | Java.use("uk.rossmarks.fridalab.MainActivity").chall03.implementation = function(){
66 | return true;
67 | };
68 | {% endhighlight %}
69 |
70 | ### Challenge 4 - call a method with an argument
71 | This is very similar to the second challenge. You need to call a method, but this time you need to provide a specific string as an argument. Just take the same solution as for the second challenge but call `chall04("frida")` instead.
72 |
73 | {% highlight javascript %}
74 | Java.choose("uk.rossmarks.fridalab.MainActivity", {
75 | onMatch : function(instance) {
76 | instance.chall04("frida");
77 | return "stop";
78 | },
79 | onComplete:function() {}
80 | });
81 | {% endhighlight %}
82 |
83 | ### Challenge 5 - Override the argument passed to a method
84 | The fifth challenge is similar to the third one, but instead of changing the return value of a method we need to change the argument before it is processed. We can do this by changing the implementation of the `chall05` method so that it calls the original implementation with the desired argument regardless of what's given.
85 |
86 | {% highlight javascript %}
87 | Java.use("uk.rossmarks.fridalab.MainActivity").chall05.implementation = function(a){
88 | this.chall05("frida");
89 | };
90 | {% endhighlight %}
91 |
92 | ### Challenge 6 - Some reverse engineering required
93 | Challenge 6 is a bit more difficult and the description isn't obvious. It says that `chall06` needs to be called after 10 seconds with the correct value. But it doesn't say 10 seconds from what point in time, or what the correct value is. So we need to study the code to understand it.
94 |
95 | In `MainActivity`'s `onCreate` we can see that the start time is set to when the activity is created. So we need to make sure we wait 10 seconds after starting the app until we solve the 6th challenge. That's not a problem.
96 |
97 | The `onCreate` method also sets up a timer that updates the expected value every 50 milliseconds. This means that we'll have to read the correct value and call `chall06` with it before the value gets reset again.
98 |
99 | This makes this a combination of challenge one and four. Just instead of changing the value of a static field we just read it and pass it along as an argument to the desired method. Here I'm also calling the desired method when the `changeColors` method is run. This is being called when the check button is pressed. This means that I can just wait with pressing the button until 10 seconds after startup and all conditions will be fulfilled.
100 |
101 | {% highlight javascript %}
102 | Java.use("uk.rossmarks.fridalab.MainActivity").changeColors.implementation = function() {
103 | this.chall06(Java.use("uk.rossmarks.fridalab.challenge_06").chall06.value);
104 | this.changeColors();
105 | }
106 | {% endhighlight %}
107 |
108 | ### Challenge 7 - bruteforce a pin code
109 | The seventh challenge asks us to bruteforce a pin code. The pin code is generated at random when starting the app. Instead of brute forcing it, we could just read the correct pin like we did in the sixth challenge. But brute forcing is fun, so let's do it as intended.
110 |
111 | We just need to call `check07Pin` repeatedly until we find the correct pin and then call `chall07` with it.
112 |
113 | {% highlight javascript %}
114 | let pin = ""
115 | for (let i = 1000; i < 10000; i++) {
116 | pin = "" + i;
117 | if (Java.use("uk.rossmarks.fridalab.challenge_07").check07Pin(pin)) {
118 | break;
119 | }
120 | }
121 | Java.choose("uk.rossmarks.fridalab.MainActivity", {
122 | onMatch : function(instance) {
123 | instance.chall07(pin);
124 | return "stop";
125 | },
126 | onComplete:function() {}
127 | });
128 | {% endhighlight %}
129 |
130 | ### Challenge 8 - Change the text of a button
131 |
132 | The eighth and final challenge is a bit different from the other challenges. Here we need to change the text of the button. So we need to modify a UI component rather than just manipulating class members.
133 |
134 | If you want to change the text of a button in Android you need to first find a reference to it. You can often do this by calling the `findViewById` method inside your activity and giving it the correct resource ID. The resource ID can be found fairly easily by looking at the decompiled APK, in our case it's `2131165231`.
135 |
136 | Since the view returned can be any view it needs to be casted to the appropriate type before it can be used. Once you have the button you can just call `setText` to set the text. This means that the code we would like to write would look something like this in Java:
137 |
138 | {% highlight java %}
139 | AppCompatButton button = (AppCompatButton)findViewById(2131165231);
140 | button.setText("Confirm");
141 | {% endhighlight %}
142 |
143 | We now need to write the equivalent JavaScript code that Frida will execute. Calling a method is easy, but casting the result is something that's new to us. This can be done by using `Java.cast()` as follows: `Java.cast(button, Java.use('android.support.v7.widget.AppCompatButton'));`
144 |
145 | With a reference to the button at hand, the next step is to set the text. However we'll run into a small complication here. The `setText` method has various overloads, but none for a plain string so we can't pass a JavaScript string to it. One of the overloads is `CharSequence` and the Java `String` class implements this interface.
146 |
147 | We need to manually create a Java string object (`Java.use('java.lang.String').$new('Confirm')`) and pass this to the `setText` method.
148 |
149 | We can now put all of this together and solve the last challenge.
150 |
151 | {% highlight javascript %}
152 | Java.choose("uk.rossmarks.fridalab.MainActivity", {
153 | onMatch : function(instance) {
154 | let button = instance.findViewById(2131165231);
155 | let casted = Java.cast(button, Java.use('android.support.v7.widget.AppCompatButton'));
156 | let text = Java.use('java.lang.String').$new('Confirm');
157 | casted.setText(text);
158 |
159 | return "stop";
160 | },
161 | onComplete:function() {}
162 | });
163 | {% endhighlight %}
164 |
165 | And with that we've been able to solve all the challenges for real, and hopefully learned something along the way.
166 |
167 | The complete code for both these challenges is [available on GitHub][my-code].
168 |
169 | [aas]: https://github.com/saeidshirazi/awesome-android-security
170 | [fridalab]: https://rossmarks.uk/blog/fridalab/
171 | [frida]: https://frida.re
172 | [jadx]: https://github.com/skylot/jadx
173 | [my-code]: https://github.com/nibarius/learning-frida/tree/master/src/fridalab/
--------------------------------------------------------------------------------
/docs/_posts/2022-08-17-h1-702.markdown:
--------------------------------------------------------------------------------
1 | ---
2 | layout: post
3 | title: "Solving the mobile 2 challenge from HackerOne h1-702 2018 CTF"
4 | date: 2022-08-17
5 | comment_issue_id: 29
6 | ---
7 |
8 | While going through the [Awesome Android security][aas] git repository I found an old [HackerOne CTF called H1-702 2018][h1] which contained a few [mobile challenges][ctf-apks]. The second challenge is somewhat [Frida][frida] friendly, so I decided to give it a try.
9 |
10 | ## Taking a first look
11 |
12 | When you start the app you're welcomed by a pin code view. When you input a 6 digit pin it will most likely print out an error to logcat saying that the pin was wrong. The task is to figure out what the correct pin is.
13 |
14 | {% include image.html url="/learning-frida/assets/h1-702/app.png" description="The app just presents a pin code view" %}
15 |
16 | The Java code is fairly easy and the most interesting method is the `onComplete` method in the main activity's `PinLockListener` implementation.
17 |
18 | {% include image.html url="/learning-frida/assets/h1-702/code.png" description="The most interesting parts of MainActivity" %}
19 |
20 | ## Understanding the app
21 |
22 | By analyzing the code of the app we can learn that it's using [libsodium][libsodium] to decrypt the flag using native code. There is also a native function `getKey` that converts the given pin code to the decryption key.
23 |
24 | I don't feel like trying to reverse engineering any of this native code so I'm going to brute force the pin code instead. To do this I'm going to use Frida to hook the `onComplete` method and call it repeatedly.
25 |
26 | The `PinLockListener` that we want to hook is implemented as an anonymous class. Anonymous classes have a generated class name similar to `$`, where `` starts at one and is incremented by one for each anonymous class. So in this case we are looking for `com.hackerone.mobile.challenge2.MainActivity$1`.
27 |
28 | Let's write a small hook that tries to call `onComplete` a couple of times to make sure we're on the right track.
29 |
30 | {% highlight javascript %}
31 | Java.perform(function(){
32 | let listener = Java.use('com.hackerone.mobile.challenge2.MainActivity$1');
33 | listener.onComplete.implementation = function(key) {
34 | console.log("on complete called with key: " + key);
35 | for (let i = 0; i < 100; i++) {
36 | let toTry = String(i).padStart(6, '0');
37 | console.log("trying " + toTry);
38 | this.onComplete(toTry);
39 | }
40 | console.log("Done");
41 | };
42 | });
43 | {% endhighlight %}
44 |
45 | Running this seems to work at first, but if you pay close attention you can see that it stalls for a long time after 50 attempts before it continues with the next pin. There seems to be some brute force protection that adds periodic delays to prevent brute force attempts.
46 |
47 | ## Brute force protection bypass
48 |
49 | By going back and taking another look at the code we can see that there is a native function called `resetCoolDown()` that sounds interesting. Maybe we can call that to reset the cooldown counter and prevent the cool down from triggering.
50 |
51 | We've hooked a method in the anonymous listener so to call `resetCoolDown()` from there we need to access the outer class, which is done using `this.this$0`. Let's call the function once every 50 iterations and try running the code again.
52 |
53 | {% highlight javascript %}
54 | if (i % 50 == 0) {
55 | this.this$0.value.resetCoolDown();
56 | }
57 | {% endhighlight %}
58 |
59 | ## Checking for success
60 | The cool down reset worked fine, now the 100 iterations finishes in no time. We can now move on to the next problem: how do we know if the pin code is correct or not? `onComplete` doesn't return anything, it just prints to logcat.
61 |
62 | What the method does is to call `SecretBox.decrypt()` which in turn returns the decrypted data if it could be decrypted successfully and throws an exception if it couldn't. So let's hook that method and check the status there.
63 |
64 | {% highlight javascript %}
65 | let success = false;
66 | let sb = Java.use('org.libsodium.jni.crypto.SecretBox');
67 | sb.decrypt.implementation = function (bArr, bArr2) {
68 | let ret ="";
69 | try {
70 | ret = this.decrypt(bArr, bArr2);
71 | success = true;
72 | console.log("found the flag: " + Java.use('java.lang.String').$new(ret));
73 | } catch (ex) {
74 | success = false;
75 | ret = Java.array('byte', []);
76 | }
77 | return ret;
78 | }
79 | {% endhighlight %}
80 |
81 | Here we create a `success` variable outside the function we're replacing so that we can read it from our `onComplete` function. We're also preventing the function from throwing an exception and instead return an empty byte array when the decryption fails. This will prevent the original `onComplete` method from printing the stack trace for each pin code we're testing.
82 |
83 | After we've tried one pin code we also have to check if it was correct and if so abort the search so we don't end up iterating through all the pins. We do this by modifying the `this.onComplete(toTry);` call in our `onComplete` implementation:
84 |
85 | {% highlight javascript %}
86 | let ret = this.onComplete(toTry);
87 | if (success) {
88 | console.log("the correct pin is: " + toTry);
89 | return ret;
90 | }
91 | {% endhighlight %}
92 |
93 | ## Try all the pins
94 | Now we can let the code run through the one million different combinations and wait for it to finish. We just need to decide in which order we should iterate through all possible combinations: from the start, [from the middle][middle] or from the end. I originally tried to start from the middle, but it turned out starting from the end is better in this case, so I switched to that approach instead.
95 |
96 | To start from the end we need to do a small change in our main for loop:
97 | {% highlight javascript %}
98 | for (let i = 999999; i > 0; i--) {
99 | {% endhighlight %}
100 |
101 | We should also make sure to remove any unnecessary log printouts and then we run the script, wait patiently for it to finish and then finally get the flag and the correct pin.
102 |
103 | {% include image.html url="/learning-frida/assets/h1-702/solution.png" description="We finally found the flag and pin code" %}
104 |
105 | ## Lessons learned
106 |
107 | While solving this challenge I ran into two things that I didn't really know how to do, which required some research. The first was how to access an anonymous class (`$`) and the second was how to access the outer class from an inner class (`this.this$0`). It's always fun to learn something new.
108 |
109 | Another lesson is to always make sure your code is doing what you think it is doing before you start brute forcing a large set of combinations. On my first attempt I ran through all the one million different combinations, which took about thirty minutes, without finding the correct pin. When looking more closely at the code I realized I generated a million different pins, but I never used them and instead tried the same single pin over and over again a million times. That was a bit of a waste of time.
110 |
111 | You can find the complete code for this challenge [on GitHub][my-code].
112 |
113 | [aas]: https://github.com/saeidshirazi/awesome-android-security
114 | [h1]: https://www.hackerone.com/ethical-hacker/jackpot-h1-702-2018-ctf-here-win-trip-biggest-live-hacking-event-2018
115 | [ctf-apks]: https://github.com/aadityapurani/h1-702-ctf-2018-solutions/tree/master/challenges
116 | [frida]: https://frida.re
117 | [libsodium]: https://doc.libsodium.org
118 | [middle]: {{ site.baseurl }}{% post_url 2022-05-24-nahamcon %}#iteration-optimizations
119 | [my-code]: https://github.com/nibarius/learning-frida/tree/master/src/h1-702/
--------------------------------------------------------------------------------
/docs/_posts/2022-11-18-bypassing-pinning.markdown:
--------------------------------------------------------------------------------
1 | ---
2 | layout: post
3 | title: "Bypassing certificate pinning with Frida"
4 | date: 2022-11-18
5 | comment_issue_id: 32
6 | ---
7 |
8 | Being able to [intercept https traffic][https] can often be useful when analyzing applications. Some apps are however using certificate pinning as a defense-in-depth strategy which makes this more difficult. In this article I'm going to show how you can bypass the certificate pinning using [Frida][frida]. To follow along you need to have Frida installed, if you don't have it installed, please start by reading [Installing Frida][installing].
9 |
10 | The screenshots in this article shows how I'm bypassing certificate pinning in Whatsapp, but the process I describe is general and should work on most other apps as well.
11 |
12 | ## What is certificate pinning?
13 |
14 | When an app connects to a remote server over https an encrypted connection is established between the app and the server as long as the server's certificate is signed by a trusted authority. Most apps are using the underlying operating system's list of trusted certificates. This means that if you can install your certificate on the operating system you can do a [man in the middle attack][mitm] and intercept the traffic and present your certificate when the app is connecting to the remote server.
15 |
16 | To mitigate the risk for these kinds of attacks some apps use certificate pinning. This means that they have a list of trusted certificates and that they will only establish a connection if the remote server has a certificate that is on that list. Even if the server's certificate is trusted by the system it will be rejected if it's not on the app's allow list. This ensures that the app is communicating with a trusted server.
17 |
18 | {% include image.html url="/learning-frida/assets/bypassing-pinning/connection-rejected.png" description="Trying to do a MITM attack on Whatsapp just results in a connection error since Burp's certificate is not trusted by Whatsapp" %}
19 |
20 | # How does bypassing work?
21 |
22 | To be able to bypass certificate pinning we need to get the app to accept our certificate. There are many different possibilities here. You could for example decompile the app, strip out the certificate pinning code and recompile it. Finding the list of trusted certificates and updating it to include your certificate could also be an option in some cases.
23 |
24 | These methods are complicated and time consuming, so a better approach is often to bypass the pinning dynamically with Frida while the app is running. The basic concept is to find the method that checks the server's certificate against the list of trusted certificates and modify it so that it say that any certificate is trusted.
25 |
26 | # Bypassing certificate pinning
27 |
28 | It's fairly complicated to implement certificate pinning completely on your own and many frameworks have support for certificate pinning. This means that most apps use one of a fairly limited set of libraries to do certificate pinning. Bypassing certificate pinning is also something that many people want to do, so there are many different solutions available for bypassing certificate pinning.
29 |
30 | This means that we don't really have to write any code at all, we just need to find a suitable Frida script and run it. The [frida-multiple-unpinning script][fmu] is one script I've had success with in the past so that's what I'm going to use.
31 |
32 | Download the script, start Frida with this script and launch the app you want to bypass certificate pinning on.
33 |
34 | {% include image.html url="/learning-frida/assets/bypassing-pinning/bypassing-pinning.png" description="Start Frida, load the script and spawn the app to bypass certificate pinning" %}
35 |
36 | With this, the app's certificate pinning should be bypassed and you can intercept the network traffic.
37 |
38 | {% include image.html url="/learning-frida/assets/bypassing-pinning/traffic-intercepted.png" description="Pinning bypass succeeded, now we can see the traffic. However as Whatsapp is using end to end encryption in addition to TLS much of the payload is encrypted and not readable." %}
39 |
40 | If this doesn't work on the app you are working with it might be using some other certificate pinning method that the frida-multiple-unpinning script does not support. In that case you could try some other unpinning scripts or try to find some other ways of bypassing certificate pinning that might work for your app.
41 |
42 | ## Lessons learned
43 |
44 | Bypassing certificate pinning using Frida can often be quite easy if you're familiar with using Frida. Since it's a common use case there are many scripts and tools available for bypassing certificate pinning and we don't even have to write any code. Don't let certificate pinning stop you in the future.
45 |
46 |
47 |
48 | [https]: {{ site.baseurl }}{% post_url 2021-01-23-sniffing-https-traffic %}
49 | [installing]: {{ site.baseurl }}{% post_url 2020-05-15-installing-frida %}
50 | [frida]: https://frida.re
51 | [mitm]: {{ site.baseurl }}{% post_url 2021-01-23-sniffing-https-traffic %}#basics
52 | [fmu]: https://codeshare.frida.re/@akabe1/frida-multiple-unpinning/
53 |
--------------------------------------------------------------------------------
/docs/about.markdown:
--------------------------------------------------------------------------------
1 | ---
2 | layout: page
3 | title: About
4 | permalink: /about/
5 | ---
6 |
7 | Android security is a topic of great interest to me and the dynamic instrumentation toolkit [Frida](https://frida.re) is an interesting tool that I've been wanting to learn more about. A good way to learn new tools is to actually use them, another is to teach others how to use them. On this blog I combine the two approaches by using Frida to solve various reverse engineering challenges and similar and writing about how I solve them.
8 |
9 | I'm working on a Windows computer, but what I do should be fairly similar on other operating systems as well. When I started this journey I had no experience at all with Frida. So even if you are completely new to Frida you should hopefully be able to follow along.
10 |
11 | When you are ready, start your journey by [installing Frida]({{ site.baseurl }}{% post_url 2020-05-15-installing-frida %}).
12 |
--------------------------------------------------------------------------------
/docs/assets/2022-nahamcon/clickme-code.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nibarius/learning-frida/c2591abd4c526f88ffcbab932ecf9d7e30dd6e42/docs/assets/2022-nahamcon/clickme-code.png
--------------------------------------------------------------------------------
/docs/assets/2022-nahamcon/clickme-flag.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nibarius/learning-frida/c2591abd4c526f88ffcbab932ecf9d7e30dd6e42/docs/assets/2022-nahamcon/clickme-flag.png
--------------------------------------------------------------------------------
/docs/assets/2022-nahamcon/notes-dk.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nibarius/learning-frida/c2591abd4c526f88ffcbab932ecf9d7e30dd6e42/docs/assets/2022-nahamcon/notes-dk.png
--------------------------------------------------------------------------------
/docs/assets/2022-nahamcon/notes-flag.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nibarius/learning-frida/c2591abd4c526f88ffcbab932ecf9d7e30dd6e42/docs/assets/2022-nahamcon/notes-flag.png
--------------------------------------------------------------------------------
/docs/assets/2022-nahamcon/notes-login.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nibarius/learning-frida/c2591abd4c526f88ffcbab932ecf9d7e30dd6e42/docs/assets/2022-nahamcon/notes-login.png
--------------------------------------------------------------------------------
/docs/assets/2022-nahamcon/notes-mainactivity.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nibarius/learning-frida/c2591abd4c526f88ffcbab932ecf9d7e30dd6e42/docs/assets/2022-nahamcon/notes-mainactivity.png
--------------------------------------------------------------------------------
/docs/assets/2022-nahamcon/notes-onclick.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nibarius/learning-frida/c2591abd4c526f88ffcbab932ecf9d7e30dd6e42/docs/assets/2022-nahamcon/notes-onclick.png
--------------------------------------------------------------------------------
/docs/assets/2022-nahamcon/notes-v1-frida.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nibarius/learning-frida/c2591abd4c526f88ffcbab932ecf9d7e30dd6e42/docs/assets/2022-nahamcon/notes-v1-frida.png
--------------------------------------------------------------------------------
/docs/assets/2022-nahamcon/notes-v2-frida.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nibarius/learning-frida/c2591abd4c526f88ffcbab932ecf9d7e30dd6e42/docs/assets/2022-nahamcon/notes-v2-frida.png
--------------------------------------------------------------------------------
/docs/assets/bypassing-pinning/bypassing-pinning.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nibarius/learning-frida/c2591abd4c526f88ffcbab932ecf9d7e30dd6e42/docs/assets/bypassing-pinning/bypassing-pinning.png
--------------------------------------------------------------------------------
/docs/assets/bypassing-pinning/connection-rejected.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nibarius/learning-frida/c2591abd4c526f88ffcbab932ecf9d7e30dd6e42/docs/assets/bypassing-pinning/connection-rejected.png
--------------------------------------------------------------------------------
/docs/assets/bypassing-pinning/traffic-intercepted.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nibarius/learning-frida/c2591abd4c526f88ffcbab932ecf9d7e30dd6e42/docs/assets/bypassing-pinning/traffic-intercepted.png
--------------------------------------------------------------------------------
/docs/assets/cybergym/lab1-code.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nibarius/learning-frida/c2591abd4c526f88ffcbab932ecf9d7e30dd6e42/docs/assets/cybergym/lab1-code.png
--------------------------------------------------------------------------------
/docs/assets/cybergym/lab1-frida.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nibarius/learning-frida/c2591abd4c526f88ffcbab932ecf9d7e30dd6e42/docs/assets/cybergym/lab1-frida.png
--------------------------------------------------------------------------------
/docs/assets/cybergym/lab1-pin.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nibarius/learning-frida/c2591abd4c526f88ffcbab932ecf9d7e30dd6e42/docs/assets/cybergym/lab1-pin.png
--------------------------------------------------------------------------------
/docs/assets/cybergym/lab2-code.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nibarius/learning-frida/c2591abd4c526f88ffcbab932ecf9d7e30dd6e42/docs/assets/cybergym/lab2-code.png
--------------------------------------------------------------------------------
/docs/assets/cybergym/lab2-frida.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nibarius/learning-frida/c2591abd4c526f88ffcbab932ecf9d7e30dd6e42/docs/assets/cybergym/lab2-frida.png
--------------------------------------------------------------------------------
/docs/assets/cybergym/lab3-code.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nibarius/learning-frida/c2591abd4c526f88ffcbab932ecf9d7e30dd6e42/docs/assets/cybergym/lab3-code.png
--------------------------------------------------------------------------------
/docs/assets/cybergym/lab3-frida.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nibarius/learning-frida/c2591abd4c526f88ffcbab932ecf9d7e30dd6e42/docs/assets/cybergym/lab3-frida.png
--------------------------------------------------------------------------------
/docs/assets/cybertruck/challenge1-code.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nibarius/learning-frida/c2591abd4c526f88ffcbab932ecf9d7e30dd6e42/docs/assets/cybertruck/challenge1-code.png
--------------------------------------------------------------------------------
/docs/assets/cybertruck/challenge2-code.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nibarius/learning-frida/c2591abd4c526f88ffcbab932ecf9d7e30dd6e42/docs/assets/cybertruck/challenge2-code.png
--------------------------------------------------------------------------------
/docs/assets/cybertruck/hook-detector.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nibarius/learning-frida/c2591abd4c526f88ffcbab932ecf9d7e30dd6e42/docs/assets/cybertruck/hook-detector.png
--------------------------------------------------------------------------------
/docs/assets/cybertruck/lea.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nibarius/learning-frida/c2591abd4c526f88ffcbab932ecf9d7e30dd6e42/docs/assets/cybertruck/lea.png
--------------------------------------------------------------------------------
/docs/assets/cybertruck/native-code.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nibarius/learning-frida/c2591abd4c526f88ffcbab932ecf9d7e30dd6e42/docs/assets/cybertruck/native-code.png
--------------------------------------------------------------------------------
/docs/assets/cybertruck/native-loop.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nibarius/learning-frida/c2591abd4c526f88ffcbab932ecf9d7e30dd6e42/docs/assets/cybertruck/native-loop.png
--------------------------------------------------------------------------------
/docs/assets/cybertruck/secrets-extracted.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nibarius/learning-frida/c2591abd4c526f88ffcbab932ecf9d7e30dd6e42/docs/assets/cybertruck/secrets-extracted.png
--------------------------------------------------------------------------------
/docs/assets/cybertruck/secrets-extracted2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nibarius/learning-frida/c2591abd4c526f88ffcbab932ecf9d7e30dd6e42/docs/assets/cybertruck/secrets-extracted2.png
--------------------------------------------------------------------------------
/docs/assets/cybertruck/the-app.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nibarius/learning-frida/c2591abd4c526f88ffcbab932ecf9d7e30dd6e42/docs/assets/cybertruck/the-app.png
--------------------------------------------------------------------------------
/docs/assets/cybertruck/xor.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nibarius/learning-frida/c2591abd4c526f88ffcbab932ecf9d7e30dd6e42/docs/assets/cybertruck/xor.png
--------------------------------------------------------------------------------
/docs/assets/evabs/code.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nibarius/learning-frida/c2591abd4c526f88ffcbab932ecf9d7e30dd6e42/docs/assets/evabs/code.png
--------------------------------------------------------------------------------
/docs/assets/evabs/instrument_challenge.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nibarius/learning-frida/c2591abd4c526f88ffcbab932ecf9d7e30dd6e42/docs/assets/evabs/instrument_challenge.png
--------------------------------------------------------------------------------
/docs/assets/evabs/running_frida.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nibarius/learning-frida/c2591abd4c526f88ffcbab932ecf9d7e30dd6e42/docs/assets/evabs/running_frida.png
--------------------------------------------------------------------------------
/docs/assets/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nibarius/learning-frida/c2591abd4c526f88ffcbab932ecf9d7e30dd6e42/docs/assets/favicon.png
--------------------------------------------------------------------------------
/docs/assets/fridalab/all-solved.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nibarius/learning-frida/c2591abd4c526f88ffcbab932ecf9d7e30dd6e42/docs/assets/fridalab/all-solved.png
--------------------------------------------------------------------------------
/docs/assets/h1-702/app.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nibarius/learning-frida/c2591abd4c526f88ffcbab932ecf9d7e30dd6e42/docs/assets/h1-702/app.png
--------------------------------------------------------------------------------
/docs/assets/h1-702/code.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nibarius/learning-frida/c2591abd4c526f88ffcbab932ecf9d7e30dd6e42/docs/assets/h1-702/code.png
--------------------------------------------------------------------------------
/docs/assets/h1-702/first-attempt.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nibarius/learning-frida/c2591abd4c526f88ffcbab932ecf9d7e30dd6e42/docs/assets/h1-702/first-attempt.png
--------------------------------------------------------------------------------
/docs/assets/h1-702/solution.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nibarius/learning-frida/c2591abd4c526f88ffcbab932ecf9d7e30dd6e42/docs/assets/h1-702/solution.png
--------------------------------------------------------------------------------
/docs/assets/h101-oauthbreaker/frida.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nibarius/learning-frida/c2591abd4c526f88ffcbab932ecf9d7e30dd6e42/docs/assets/h101-oauthbreaker/frida.png
--------------------------------------------------------------------------------
/docs/assets/h101-oauthbreaker/webappinterface.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nibarius/learning-frida/c2591abd4c526f88ffcbab932ecf9d7e30dd6e42/docs/assets/h101-oauthbreaker/webappinterface.png
--------------------------------------------------------------------------------
/docs/assets/hpandro/aes_code.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nibarius/learning-frida/c2591abd4c526f88ffcbab932ecf9d7e30dd6e42/docs/assets/hpandro/aes_code.png
--------------------------------------------------------------------------------
/docs/assets/hpandro/emulator_check.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nibarius/learning-frida/c2591abd4c526f88ffcbab932ecf9d7e30dd6e42/docs/assets/hpandro/emulator_check.png
--------------------------------------------------------------------------------
/docs/assets/hpandro/hidden_levels.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nibarius/learning-frida/c2591abd4c526f88ffcbab932ecf9d7e30dd6e42/docs/assets/hpandro/hidden_levels.png
--------------------------------------------------------------------------------
/docs/assets/hpandro/imsi_smali.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nibarius/learning-frida/c2591abd4c526f88ffcbab932ecf9d7e30dd6e42/docs/assets/hpandro/imsi_smali.png
--------------------------------------------------------------------------------
/docs/assets/hpandro/meme.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nibarius/learning-frida/c2591abd4c526f88ffcbab932ecf9d7e30dd6e42/docs/assets/hpandro/meme.png
--------------------------------------------------------------------------------
/docs/assets/hpandro/native.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nibarius/learning-frida/c2591abd4c526f88ffcbab932ecf9d7e30dd6e42/docs/assets/hpandro/native.png
--------------------------------------------------------------------------------
/docs/assets/hpandro/pin.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nibarius/learning-frida/c2591abd4c526f88ffcbab932ecf9d7e30dd6e42/docs/assets/hpandro/pin.png
--------------------------------------------------------------------------------
/docs/assets/hpandro/predict.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nibarius/learning-frida/c2591abd4c526f88ffcbab932ecf9d7e30dd6e42/docs/assets/hpandro/predict.png
--------------------------------------------------------------------------------
/docs/assets/hpandro/qemu_code.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nibarius/learning-frida/c2591abd4c526f88ffcbab932ecf9d7e30dd6e42/docs/assets/hpandro/qemu_code.png
--------------------------------------------------------------------------------
/docs/assets/hpandro/qemu_flag.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nibarius/learning-frida/c2591abd4c526f88ffcbab932ecf9d7e30dd6e42/docs/assets/hpandro/qemu_flag.png
--------------------------------------------------------------------------------
/docs/assets/hpandro/redirect_to_task.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nibarius/learning-frida/c2591abd4c526f88ffcbab932ecf9d7e30dd6e42/docs/assets/hpandro/redirect_to_task.png
--------------------------------------------------------------------------------
/docs/assets/hpandro/response_manipulation.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nibarius/learning-frida/c2591abd4c526f88ffcbab932ecf9d7e30dd6e42/docs/assets/hpandro/response_manipulation.png
--------------------------------------------------------------------------------
/docs/assets/hpandro/root_check.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nibarius/learning-frida/c2591abd4c526f88ffcbab932ecf9d7e30dd6e42/docs/assets/hpandro/root_check.png
--------------------------------------------------------------------------------
/docs/assets/hpandro/wss_code.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nibarius/learning-frida/c2591abd4c526f88ffcbab932ecf9d7e30dd6e42/docs/assets/hpandro/wss_code.png
--------------------------------------------------------------------------------
/docs/assets/https-sniffing/apn-setup.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nibarius/learning-frida/c2591abd4c526f88ffcbab932ecf9d7e30dd6e42/docs/assets/https-sniffing/apn-setup.png
--------------------------------------------------------------------------------
/docs/assets/https-sniffing/burp-setup.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nibarius/learning-frida/c2591abd4c526f88ffcbab932ecf9d7e30dd6e42/docs/assets/https-sniffing/burp-setup.png
--------------------------------------------------------------------------------
/docs/assets/https-sniffing/certificate-installed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nibarius/learning-frida/c2591abd4c526f88ffcbab932ecf9d7e30dd6e42/docs/assets/https-sniffing/certificate-installed.png
--------------------------------------------------------------------------------
/docs/assets/https-sniffing/it-works.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nibarius/learning-frida/c2591abd4c526f88ffcbab932ecf9d7e30dd6e42/docs/assets/https-sniffing/it-works.png
--------------------------------------------------------------------------------
/docs/assets/https-sniffing/mitm.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nibarius/learning-frida/c2591abd4c526f88ffcbab932ecf9d7e30dd6e42/docs/assets/https-sniffing/mitm.png
--------------------------------------------------------------------------------
/docs/assets/https-sniffing/system-image.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nibarius/learning-frida/c2591abd4c526f88ffcbab932ecf9d7e30dd6e42/docs/assets/https-sniffing/system-image.png
--------------------------------------------------------------------------------
/docs/assets/install/adb_shell_root.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nibarius/learning-frida/c2591abd4c526f88ffcbab932ecf9d7e30dd6e42/docs/assets/install/adb_shell_root.png
--------------------------------------------------------------------------------
/docs/assets/install/install_emulator.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nibarius/learning-frida/c2591abd4c526f88ffcbab932ecf9d7e30dd6e42/docs/assets/install/install_emulator.png
--------------------------------------------------------------------------------
/docs/assets/install/listing_running_apps.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nibarius/learning-frida/c2591abd4c526f88ffcbab932ecf9d7e30dd6e42/docs/assets/install/listing_running_apps.png
--------------------------------------------------------------------------------
/docs/assets/tls-sniffing/adb-reverse.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nibarius/learning-frida/c2591abd4c526f88ffcbab932ecf9d7e30dd6e42/docs/assets/tls-sniffing/adb-reverse.png
--------------------------------------------------------------------------------
/docs/assets/tls-sniffing/chart.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nibarius/learning-frida/c2591abd4c526f88ffcbab932ecf9d7e30dd6e42/docs/assets/tls-sniffing/chart.png
--------------------------------------------------------------------------------
/docs/assets/tls-sniffing/iptables-setup.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nibarius/learning-frida/c2591abd4c526f88ffcbab932ecf9d7e30dd6e42/docs/assets/tls-sniffing/iptables-setup.png
--------------------------------------------------------------------------------
/docs/assets/tls-sniffing/polar-proxy-working.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nibarius/learning-frida/c2591abd4c526f88ffcbab932ecf9d7e30dd6e42/docs/assets/tls-sniffing/polar-proxy-working.png
--------------------------------------------------------------------------------
/docs/assets/tls-sniffing/running-polarproxy.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nibarius/learning-frida/c2591abd4c526f88ffcbab932ecf9d7e30dd6e42/docs/assets/tls-sniffing/running-polarproxy.png
--------------------------------------------------------------------------------
/docs/assets/tls-sniffing/wireshark-decrypted.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nibarius/learning-frida/c2591abd4c526f88ffcbab932ecf9d7e30dd6e42/docs/assets/tls-sniffing/wireshark-decrypted.png
--------------------------------------------------------------------------------
/docs/assets/tls-sniffing/wireshark-encrypted.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nibarius/learning-frida/c2591abd4c526f88ffcbab932ecf9d7e30dd6e42/docs/assets/tls-sniffing/wireshark-encrypted.png
--------------------------------------------------------------------------------
/docs/assets/uncrackable1/code_found.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nibarius/learning-frida/c2591abd4c526f88ffcbab932ecf9d7e30dd6e42/docs/assets/uncrackable1/code_found.png
--------------------------------------------------------------------------------
/docs/assets/uncrackable1/root_bypassed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nibarius/learning-frida/c2591abd4c526f88ffcbab932ecf9d7e30dd6e42/docs/assets/uncrackable1/root_bypassed.png
--------------------------------------------------------------------------------
/docs/assets/uncrackable1/root_detection.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nibarius/learning-frida/c2591abd4c526f88ffcbab932ecf9d7e30dd6e42/docs/assets/uncrackable1/root_detection.png
--------------------------------------------------------------------------------
/docs/assets/uncrackable1/string_checker.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nibarius/learning-frida/c2591abd4c526f88ffcbab932ecf9d7e30dd6e42/docs/assets/uncrackable1/string_checker.png
--------------------------------------------------------------------------------
/docs/assets/uncrackable1/verify.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nibarius/learning-frida/c2591abd4c526f88ffcbab932ecf9d7e30dd6e42/docs/assets/uncrackable1/verify.png
--------------------------------------------------------------------------------
/docs/assets/uncrackable2/ghidra_strncmp_usage.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nibarius/learning-frida/c2591abd4c526f88ffcbab932ecf9d7e30dd6e42/docs/assets/uncrackable2/ghidra_strncmp_usage.png
--------------------------------------------------------------------------------
/docs/assets/uncrackable2/problem_solved.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nibarius/learning-frida/c2591abd4c526f88ffcbab932ecf9d7e30dd6e42/docs/assets/uncrackable2/problem_solved.png
--------------------------------------------------------------------------------
/docs/assets/uncrackable3/code_check.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nibarius/learning-frida/c2591abd4c526f88ffcbab932ecf9d7e30dd6e42/docs/assets/uncrackable3/code_check.png
--------------------------------------------------------------------------------
/docs/assets/uncrackable3/frida_detection.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nibarius/learning-frida/c2591abd4c526f88ffcbab932ecf9d7e30dd6e42/docs/assets/uncrackable3/frida_detection.png
--------------------------------------------------------------------------------
/docs/assets/uncrackable3/function_address.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nibarius/learning-frida/c2591abd4c526f88ffcbab932ecf9d7e30dd6e42/docs/assets/uncrackable3/function_address.png
--------------------------------------------------------------------------------
/docs/assets/uncrackable3/native_init_function.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nibarius/learning-frida/c2591abd4c526f88ffcbab932ecf9d7e30dd6e42/docs/assets/uncrackable3/native_init_function.png
--------------------------------------------------------------------------------
/docs/assets/uncrackable3/onCreate.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nibarius/learning-frida/c2591abd4c526f88ffcbab932ecf9d7e30dd6e42/docs/assets/uncrackable3/onCreate.png
--------------------------------------------------------------------------------
/docs/assets/uncrackable3/secret_found.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nibarius/learning-frida/c2591abd4c526f88ffcbab932ecf9d7e30dd6e42/docs/assets/uncrackable3/secret_found.png
--------------------------------------------------------------------------------
/docs/assets/uncrackable3/secret_generation.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nibarius/learning-frida/c2591abd4c526f88ffcbab932ecf9d7e30dd6e42/docs/assets/uncrackable3/secret_generation.png
--------------------------------------------------------------------------------
/docs/assets/veryandroidoso/arg56.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nibarius/learning-frida/c2591abd4c526f88ffcbab932ecf9d7e30dd6e42/docs/assets/veryandroidoso/arg56.png
--------------------------------------------------------------------------------
/docs/assets/veryandroidoso/jdgui-vs-jadx.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nibarius/learning-frida/c2591abd4c526f88ffcbab932ecf9d7e30dd6e42/docs/assets/veryandroidoso/jdgui-vs-jadx.png
--------------------------------------------------------------------------------
/docs/assets/veryandroidoso/solved.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nibarius/learning-frida/c2591abd4c526f88ffcbab932ecf9d7e30dd6e42/docs/assets/veryandroidoso/solved.png
--------------------------------------------------------------------------------
/docs/cheat-sheet.markdown:
--------------------------------------------------------------------------------
1 | ---
2 | layout: page
3 | title: Cheat sheet
4 | permalink: /cheat-sheet/
5 | ---
6 |
7 | # Android emulator setup
8 |
9 | * From [Installing Frida][installing-frida], installing Frida on a device with root access:
10 | ```
11 | adb root
12 | adb push frida-server //data/local/tmp/
13 | adb shell
14 | cd /data/local/tmp
15 | chmod 755 ./frida-server
16 | ./frida-server
17 | ```
18 |
19 | * From [Sniffing https traffic on Android 11]({{ site.baseurl }}{% post_url 2021-01-23-sniffing-https-traffic %}), installing a certificate from Burp Suite on an Android 11 emulator:
20 | ```
21 | openssl x509 -inform der -in burp.cer -out certificate.pem
22 | cp certificate.pem `openssl x509 -inform pem -subject_hash_old -in certificate.pem | head -1`.0
23 | ./emulator -avd rRoot -writable-system
24 | adb push 9a5ba575.0 //sdcard/Download
25 | adb root
26 | adb shell avbctl disable-verification
27 | adb reboot
28 | adb root
29 | adb remount
30 | adb shell
31 | cp /sdcard/Download/9a5ba575.0 /system/etc/security/cacerts/
32 | chmod 644 /system/etc/security/cacerts/9a5ba575.0
33 | reboot
34 | ```
35 |
36 | Note: `//sdcard` and similar in adb push commands is to [make it work](https://stackoverflow.com/a/17139366/1730966) with git bash on Windows.
37 |
38 | * From [Sniffing TLS traffic on Android][tls], setting up PolarProxy for sniffing TLS traffic:
39 | ```
40 | # Start PolarProxy
41 | PolarProxy -p 443,80 -w polarproxy.pcap
42 |
43 | # Setup adb reverse connection
44 | adb reverse tcp:443 tcp:443
45 |
46 | # iptables setup
47 | adb root
48 | adb shell
49 | iptables -t nat -A OUTPUT -p tcp --dport 443 -j DNAT --to-destination 127.0.0.1:443
50 | ```
51 |
52 | # Frida command line
53 |
54 | * From [Installing Frida][installing-frida], list all running applications:
55 | ```
56 | frida-ps -U -a
57 | ```
58 | * From [Solving OWASP MSTG UnCrackable App for Android Level 1][uncrackable1]:
59 | ```
60 | # Attach to a running application
61 | frida -U owasp.mstg.uncrackable1
62 | # Load a script and start a package
63 | frida -U --no-pause -l uncrackable1.js -f owasp.mstg.uncrackable1
64 | ```
65 |
66 | * From [Bypassing certificate pinning with Frida][pinning-bypass]:
67 | ```
68 | # Download script from https://codeshare.frida.re/@akabe1/frida-multiple-unpinning/
69 | frida -U -f com.whatsapp -l frida_multiple_unpinning.js --no-pause
70 | ```
71 |
72 | * To upgrade Frida Follow the instructions on to install the latest version of frida-server on the phone. Then to upgrade Frida on the computer run:
73 | ```
74 | pip install --upgrade frida-tools
75 | ```
76 |
77 |
78 | # Other tools
79 | * From [Solving OWASP MSTG UnCrackable App for Android Level 1][uncrackable1], using dex2jar:
80 | ```
81 | d2j-dex2jar.bat -f UnCrackable-Level1.apk
82 | ```
83 |
84 | [installing-frida]: {{ site.baseurl }}{% post_url 2020-05-15-installing-frida %}
85 | [uncrackable1]: {{ site.baseurl }}{% post_url 2020-05-16-uncrackable1 %}
86 | [tls]: {{ site.baseurl }}{% post_url 2022-05-21-sniffing-tls-traffic %}
87 | [pinning-bypass]: {{ site.baseurl }}{% post_url 2022-11-18-bypassing-pinning %}
--------------------------------------------------------------------------------
/docs/index.markdown:
--------------------------------------------------------------------------------
1 | ---
2 | # Feel free to add content and custom Front Matter to this file.
3 | # To modify the layout, see https://jekyllrb.com/docs/themes/#overriding-theme-defaults
4 |
5 | layout: home
6 | ---
7 |
8 | Hi there!
9 |
10 | Security in general, and Android security in particular is a topic of interest to me so [Frida](https://frida.re) is a tool that's caught my attention. To learn more about it's capabilities and how to use it, I'm solving various reverse engineering challenges using Frida and writing about it on this blog.
11 |
12 | If you don't have any experience with Frida I suggest that you start with the first post ([Installing Frida]({{ site.baseurl }}{% post_url 2020-05-15-installing-frida %})), and continue from there.
13 |
--------------------------------------------------------------------------------
/docs/links.markdown:
--------------------------------------------------------------------------------
1 | ---
2 | layout: page
3 | title: Links
4 | permalink: /links/
5 | ---
6 |
7 | # Android CTFs
8 |
9 | * [CyberTruckChallenge19][cybertruck] - A CTF with three different challenges. The two first are pretty easy with Java code only and the third gets more difficult as it introduces native code. The third part is a great complement to the UnCrackable App's native challenges.
10 | * [DEFCON Quals 2019 VeryAndroidoso][veryandroidoso] - A CTF that focus more on reversing an algorithm rather than finding a hard coded secret. It has some native code, but can be solved without working with the native code. I found this CTF fairly challenging.
11 | * [EVABS][evabs] - Beginner friendly CTF with several different challenges of different types. A few of them can be solved with Frida. Great to start with.
12 | * [FridaLab][fridalab] - A small beginner friendly app with 8 Frida challenges. Great for those who are just starting out with Frida and need to get some practice.
13 | * [h1-702 2018 CTF][h1-702-apks] - A CTF with one flag where you have to brute force a 6 digit pin code. Relies on native code, but it can be solved using Frida by just working with the Java code.
14 | * [Hacker101][h101] - Several different CTFs of various kinds with a couple of fairly easy Android CTFs. Some of the challenges, like the Oauthbreaker challenge can be solved with Frida. A free HackerOne account is needed to do these.
15 | * [hpAndro Vulnerable Application][hpandro] - An Android CTF written in Kotlin which is still under development. It currently has 101 different flags in a wide range of challenges, some of which are suitable for solving with Frida.
16 | * [UnCrackable App][uncrackable] - A couple of different CTF apps, the first has only Java code and is also fairly beginner friendly. The second introduces native code and the third hides the secret better and adds native anti tampering code that needs to be bypassed. It's quite difficult, but entirely possible without prior experience in reversing native code.
17 |
18 | [cybertruck]: https://github.com/nowsecure/cybertruckchallenge19
19 | [evabs]: https://github.com/abhi-r3v0/EVABS
20 | [fridalab]: https://rossmarks.uk/blog/fridalab/
21 | [h1-702-apks]: https://github.com/aadityapurani/h1-702-ctf-2018-solutions/tree/master/challenges
22 | [h101]: https://www.hacker101.com
23 | [hpandro]: http://ctf.hpandro.raviramesh.info
24 | [uncrackable]: https://github.com/OWASP/owasp-mstg/tree/master/Crackmes
25 | [veryandroidoso]: https://archive.ooo/c/VeryAndroidoso/272/
26 |
27 |
28 | # Tools
29 | * [dex2jar][dex2jar] - command line tool for converting apks to jar files (among other things).
30 | * [Burp Suite][burp] - Popular tool among pen-testers, contains among others an http(s) proxy.
31 | * [Bytecode Viewer][bytecode-viewer] - Java/Android decompiler. Haven't used it much, but it looks like a promising alternative to Jadx.
32 | * [Fiddler][fiddler] - A great, powerful and free http(s) proxy for Windows, my proxy of choice for analyzing, intercepting and modifying http(s) traffic.
33 | * [Frida][frida] - dynamic instrumentation toolkit, and also the main purpose of this blog.
34 | * [Ghidra][ghidra] - a free and open source reverse engineering tool, good as a free alternative to IDA.
35 | * [Jadx][jadx] - Java decompiler. Quick and easy to use as you can open apk files directly and see the source code.
36 | * [JD-GUI][jd-gui] - Java decompiler. Can't open apk files directly, so tools like dex2jar has to be used on the apk first. But once that's done it sometimes produces better results than Jadx.
37 |
38 | [frida]: https://frida.re
39 | [dex2jar]: https://github.com/pxb1988/dex2jar
40 | [bytecode-viewer]: https://github.com/Konloch/bytecode-viewer
41 | [jd-gui]: https://java-decompiler.github.io
42 | [jadx]: https://github.com/skylot/jadx
43 | [ghidra]: https://github.com/NationalSecurityAgency/ghidra
44 | [burp]: https://portswigger.net/burp/communitydownload
45 | [fiddler]: https://www.telerik.com/download/fiddler
46 |
--------------------------------------------------------------------------------
/src/2022-nahamcon/clickme.js:
--------------------------------------------------------------------------------
1 | Java.perform(function(){
2 | var ma = Java.use("com.example.clickme.MainActivity");
3 | ma.getFlagButtonClick.implementation = function(view){
4 | this.CLICKS.value = 99999999;
5 | this.getFlagButtonClick(view);
6 | }
7 | });
--------------------------------------------------------------------------------
/src/2022-nahamcon/notes.js:
--------------------------------------------------------------------------------
1 | Java.perform(function(){
2 | let success = false;
3 | let toTry;
4 | // Hook the method that decrypts the data for convenient access to the input
5 | // and output files. It throws an exception if decryption fails.
6 | Java.use("o.d").k.implementation = function(pin, input, output) {
7 | let start = 5000;
8 | let offset = 0;
9 | success = false;
10 | console.log("Starting pin brute force attempt");
11 | // Assume that the correct pin is near the middle and iterate outwards
12 | while (Math.abs(offset) <= 5000) {
13 | toTry = String(start + offset).padStart(4, '0');
14 | let nextPin = "" + toTry + toTry + toTry + toTry;
15 | try {
16 | this.k(nextPin, input, output);
17 | if (success) {
18 | return;
19 | }
20 | } catch (ex) {}
21 | offset = nextOffset(offset);
22 | }
23 | }
24 |
25 | // Successful decryption isn't enough. Many pins can decrypt the data
26 | // without having exceptions being thrown, but for all pins except the
27 | // correct one the data is just garbage. So need to check if the data
28 | // is json data when the data is written to disk just after decryption.
29 | let fos = Java.use("java.io.FileOutputStream");
30 | fos.write.overload('[B').implementation = function(barr) {
31 | if (barr[0] == 0x7b) {
32 | // 0x7b is ascii code for '{' which json files start with
33 | console.log("Pin: " + toTry + " - Correct pin found");
34 | this.write(barr);
35 | success = true;
36 | } else {
37 | console.log("Pin: " + toTry + " - Decryption succeeded but resulted in garbage");
38 | }
39 | }
40 | });
41 |
42 | // Calculate next offset to iterate from the middle and out.
43 | // Returns a number of opposite sign and increases the absolute value when input is positive
44 | function nextOffset(offset) {
45 | if (offset >= 0) {
46 | offset++;
47 | }
48 | return offset * -1;
49 | }
50 |
--------------------------------------------------------------------------------
/src/cybergym/cybergym.js:
--------------------------------------------------------------------------------
1 | Java.perform(function(){
2 | Java.use("java.lang.String").equalsIgnoreCase.implementation = function(a) {
3 | if (this == "5555") {
4 | console.log("Real passcode was: " + a);
5 | return true;
6 | }
7 | return this.equalsIgnoreCase(a);
8 | };
9 | });
--------------------------------------------------------------------------------
/src/cybergym/cybergym2.js:
--------------------------------------------------------------------------------
1 | Java.perform(function(){
2 | Java.use("ja").a.overload('java.lang.String', 'java.lang.String').implementation = function(a, b) {
3 | var ret = this.a(a, b);
4 | console.log("flag: " + ret);
5 | return ret;
6 | };
7 | });
--------------------------------------------------------------------------------
/src/cybergym/cybergym3.js:
--------------------------------------------------------------------------------
1 | Java.perform(function(){
2 | Java.use("java.lang.String").getBytes.overload('java.lang.String').implementation= function(a) {
3 | console.log("getBytes called on: " + this);
4 | return this.getBytes(a);
5 | };
6 | });
--------------------------------------------------------------------------------
/src/cybertruck19/cyber.js:
--------------------------------------------------------------------------------
1 | Java.perform(function(){
2 | console.log("");
3 | bypassHookDetection();
4 | challenge1();
5 | challenge2();
6 | challenge3();
7 | });
8 |
9 | function bypassHookDetection() {
10 | var HookDetector = Java.use("org.nowsecure.cybertruck.detections.HookDetector");
11 | HookDetector.isFridaServerInDevice.implementation = function() {
12 | console.log("Frida detection bypassed");
13 | return false;
14 | }
15 | }
16 |
17 | function challenge1() {
18 | var Challenge1 = Java.use('org.nowsecure.cybertruck.keygenerators.Challenge1');
19 | Challenge1.generateDynamicKey.implementation = function(bArr) {
20 | var ret = this.generateDynamicKey(bArr);
21 | console.log("Challenge 1 key: " + toHexString(ret));
22 | return ret;
23 | }
24 | }
25 |
26 | function challenge2() {
27 | var Challenge2 = Java.use('org.nowsecure.cybertruck.keygenerators.a');
28 | Challenge2.a.overload('[B', '[B').implementation = function(bArr1, bArr2) {
29 | var ret = this.a(bArr1, bArr2);
30 | console.log("Challenge 2 key: " + toHexString(ret));
31 | return ret;
32 | }
33 | }
34 |
35 | function challenge3() {
36 | var MainActivity = Java.use('org.nowsecure.cybertruck.MainActivity');
37 | MainActivity.k.implementation = function() {
38 | // Setup the native interceptors
39 | nativeChallenge3();
40 | this.k();
41 | }
42 | }
43 |
44 | function nativeChallenge3() {
45 | // Detach all to prevent duplicate attachments when the script is reloaded.
46 | Interceptor.detachAll();
47 |
48 | Interceptor.attach(Module.findExportByName("libnative-lib.so", "Java_org_nowsecure_cybertruck_MainActivity_init"), {
49 | onEnter: function(args) {
50 | // The init functions calls strlen to check the length of the key, attach to it
51 | this.strlen = Interceptor.attach(Module.findExportByName("libc.so", "strlen"), {
52 | onEnter: function(args) {
53 | // strln is only of interest when actually called from libnative-lib.so
54 | if (isAddressInModule('libnative-lib.so', this.returnAddress)) {
55 | console.log("Challenge 3 key: "+ args[0].readUtf8String(32));
56 | }
57 | }
58 | });
59 |
60 |
61 | },
62 | onLeave: function(retval) {
63 | // Detach from strlen since we no longer need to listen to it.
64 | this.strlen.detach();
65 |
66 | // the init function de-obfuscates the secret and stores it in a local variable
67 | // (on the stack). The stack grows towards lower addresses, so look at the content
68 | // of the stack before the stack pointer when returning from the init function.
69 | console.log('Content of the stack when returning from the init function:');
70 | console.log(hexdump(this.context.sp.sub(208), {offset: 0, length: 208, header: true, ansi:true}));
71 | }
72 | });
73 | }
74 |
75 | // Checks if the given address is located inside the given module
76 | function isAddressInModule(moduleName, address) {
77 | var module = Process.findModuleByName(moduleName)
78 | return address >= module.base && address < module.base.add(module.size);
79 | }
80 |
81 | // Converts a Frida Java byte array to a hex string.
82 | function toHexString(byteArray) {
83 | var ret = ""
84 | // byteArray is a Java array, not a javascript array so things like foreach
85 | // and Array.from doesn't work
86 | for (var i = 0; i < byteArray.length; i++) {
87 | // Frida's Java byte array uses signed bytes, do bitwise & to convert to a signed byte
88 | // Then convert to a string with a leading zero, and take the last two characters
89 | // to always get exactly two characters for each byte.
90 | ret += ('0' + (byteArray[i] & 0xFF).toString(16)).slice(-2)
91 | }
92 | return ret;
93 | }
94 |
--------------------------------------------------------------------------------
/src/cybertruck19/cyber2.js:
--------------------------------------------------------------------------------
1 | Java.perform(function(){
2 | console.log("");
3 | bypassHookDetection();
4 | challenge3();
5 | });
6 |
7 | function bypassHookDetection() {
8 | var HookDetector = Java.use("org.nowsecure.cybertruck.detections.HookDetector");
9 | HookDetector.isFridaServerInDevice.implementation = function() {
10 | console.log("Frida detection bypassed");
11 | return false;
12 | }
13 | }
14 |
15 | function challenge3() {
16 | var MainActivity = Java.use('org.nowsecure.cybertruck.MainActivity');
17 | MainActivity.k.implementation = function() {
18 | // Setup the native interceptors
19 | nativeChallenge3();
20 | this.k();
21 | }
22 | }
23 |
24 | function nativeChallenge3() {
25 | const SECRET_LENGTH = 32;
26 |
27 | // The LEA instruction at 75c loads the address where the key is stored into RDI
28 | // before the call to strlen. Read the key from this location on the next instruction
29 | var keyHook = Interceptor.attach(Module.findBaseAddress('libnative-lib.so').add(0x760), {
30 | onEnter: function(args) {
31 | console.log("Challenge3 key: " + this.context.rdi.readUtf8String(SECRET_LENGTH));
32 | keyHook.detach();
33 | }
34 | })
35 |
36 | var secret = "";
37 | var len = 0;
38 | // Assembly code from where the xor operation takes place
39 | // address operation operands comment
40 | // 7bd XOR ECX, EDX ECX = ECX ^ EDX
41 | // 7bf MOV SIL, CL
42 | var secretHook = Interceptor.attach(Module.findBaseAddress('libnative-lib.so').add(0x7bf), {
43 | onEnter: function(args) {
44 | // Only r*x and not e*x is available trough Frida, but e*x is just the 4 lower bytes of
45 | // r*x, so we can look at r*x instead.
46 | if (len++ < SECRET_LENGTH) {
47 | secret += String.fromCharCode(this.context.rcx);
48 | } else {
49 | console.log("Challenge3 secret: " + secret);
50 | secretHook.detach();
51 | }
52 | }
53 | });
54 | }
55 |
56 |
--------------------------------------------------------------------------------
/src/evabs/evabs.js:
--------------------------------------------------------------------------------
1 | Java.perform(function(){
2 | var Launch = Java.use("com.revo.evabs.Launch");
3 | Launch.onCreate.overload("android.os.Bundle").implementation = function(bundle) {
4 | this.onCreate(bundle);
5 | var f = Java.use("com.revo.evabs.Frida1").$new();
6 | console.log("Frida: '" + f.stringFromJNI() + "'");
7 | var debugMe = Java.use("com.revo.evabs.DebugMe").$new();
8 | console.log("debugMe: 'EVABS{" + debugMe.stringFromJNI() + "}'");
9 | }
10 | });
11 |
12 |
13 |
--------------------------------------------------------------------------------
/src/fridalab/lab.js:
--------------------------------------------------------------------------------
1 | Java.perform(function(){
2 | let lab = Java.use("uk.rossmarks.fridalab.MainActivity");
3 | // Pretend to solve everything:
4 | /*lab.changeColors.implementation = function() {
5 | this.completeArr.value = Java.array('int', [1, 1, 1, 1, 1, 1, 1, 1]);
6 | this.changeColors();
7 | }*/
8 |
9 | // Challenge 1. Change challenge_01's variable chall01 to 1
10 | Java.use("uk.rossmarks.fridalab.challenge_01").chall01.value = 1;
11 |
12 | // Challenge 2. Call chall02()
13 | Java.choose("uk.rossmarks.fridalab.MainActivity", {
14 | onMatch : function(instance) {
15 | instance.chall02();
16 | return "stop";
17 | },
18 | onComplete:function() {}
19 | });
20 |
21 | // Challenge 3. Change return value of chall03
22 | Java.use("uk.rossmarks.fridalab.MainActivity").chall03.implementation = function(){
23 | return true;
24 | };
25 |
26 | // Challenge 4. Call chall04 with the argument "frida"
27 | Java.choose("uk.rossmarks.fridalab.MainActivity", {
28 | onMatch : function(instance) {
29 | instance.chall04("frida");
30 | return "stop";
31 | },
32 | onComplete:function() {}
33 | });
34 |
35 | // Challenge 5. Change argument of chall05
36 | Java.use("uk.rossmarks.fridalab.MainActivity").chall05.implementation = function(a){
37 | this.chall05("frida");
38 | };
39 |
40 | // Challenge 6. Call chall06 with the correct value (changed every 50ms)
41 | Java.use("uk.rossmarks.fridalab.MainActivity").changeColors.implementation = function() {
42 | this.chall06(Java.use("uk.rossmarks.fridalab.challenge_06").chall06.value);
43 | this.changeColors();
44 | }
45 |
46 | // Challenge 7. Brute force a pin code
47 | // Cheat and just check what the pin is instead of brute forcing it
48 | //console.log("the pin code is: " + Java.use("uk.rossmarks.fridalab.challenge_07").chall07.value);
49 | let pin = ""
50 | for (let i = 1000; i < 10000; i++) {
51 | pin = "" + i;
52 | if (Java.use("uk.rossmarks.fridalab.challenge_07").check07Pin(pin)) {
53 | break;
54 | }
55 | }
56 | Java.choose("uk.rossmarks.fridalab.MainActivity", {
57 | onMatch : function(instance) {
58 | instance.chall07(pin);
59 | return "stop";
60 | },
61 | onComplete:function() {}
62 | });
63 |
64 | // Challenge 8. Change the text of the button
65 | Java.choose("uk.rossmarks.fridalab.MainActivity", {
66 | onMatch : function(instance) {
67 | let button = instance.findViewById(2131165231);
68 | let casted = Java.cast(button, Java.use('android.support.v7.widget.AppCompatButton'));
69 | let text = Java.use('java.lang.String').$new('Confirm');
70 | casted.setText(text);
71 |
72 | return "stop";
73 | },
74 | onComplete:function() {}
75 | });
76 | });
77 |
--------------------------------------------------------------------------------
/src/h1-702/h1.js:
--------------------------------------------------------------------------------
1 | Java.perform(function(){
2 | let success = false;
3 | let sb = Java.use('org.libsodium.jni.crypto.SecretBox');
4 | sb.decrypt.implementation = function (bArr, bArr2) {
5 | let ret ="";
6 | try {
7 | ret = this.decrypt(bArr, bArr2);
8 | success = true;
9 | console.log("found the flag: " + Java.use('java.lang.String').$new(ret));
10 | } catch (ex) {
11 | success = false;
12 | ret = Java.array('byte', []);
13 | }
14 | return ret;
15 | }
16 |
17 | let listener = Java.use('com.hackerone.mobile.challenge2.MainActivity$1');
18 | listener.onComplete.implementation = function(key) {
19 | console.log("on complete called with key: " + key);
20 | for (let i = 999999; i > 0; i--) {
21 | if (i % 50 == 0) {
22 | // reset cool down regularly to not have to wait.
23 | this.this$0.value.resetCoolDown();
24 | }
25 | let toTry = String(i).padStart(6, '0');
26 | let ret = this.onComplete(toTry);
27 | if (success) {
28 | console.log("the correct pin is: " + toTry);
29 | return ret;
30 | }
31 | }
32 | console.log("Done");
33 | };
34 | });
35 |
--------------------------------------------------------------------------------
/src/h101/oauth.js:
--------------------------------------------------------------------------------
1 | Java.perform(function(){
2 | var WebAppInterface = Java.use("com.hacker101.oauth.WebAppInterface");
3 | var flag = WebAppInterface.$new(null).getFlagPath();
4 | console.log("Flag path: " + flag);
5 | });
6 |
--------------------------------------------------------------------------------
/src/hpandro/auth.js:
--------------------------------------------------------------------------------
1 | // for version 1.1.12
2 | Java.perform(function(){
3 | // Response manipulation part. Just check what the hash is of my input and make sure to respond with the same
4 | var otp = Java.use('com.hpandro.androidsecurity.ui.activity.task.authentication.responseMani.ResponseManipOTPActivity');
5 | otp.otpToSHA1.implementation = function(a) {
6 | var ret = this.otpToSHA1(a);
7 | console.log("input: " + a + " return: " + ret);
8 | return ret;
9 | }
10 | })
11 |
--------------------------------------------------------------------------------
/src/hpandro/binary.js:
--------------------------------------------------------------------------------
1 | Java.perform(function(){
2 | var bin = Java.use("com.hpandro.androidsecurity.ui.activity.task.binary.NativeFunTaskActivity");
3 | bin.hello.implementation = function() {
4 | var ret = this.flag();
5 | console.log("flag: " + ret);
6 | return ret;
7 | }
8 | });
--------------------------------------------------------------------------------
/src/hpandro/device_ids.js:
--------------------------------------------------------------------------------
1 | Java.perform(function(){
2 |
3 | // IMSI level:
4 | var intrinsics = Java.use('kotlin.jvm.internal.Intrinsics');
5 | intrinsics.areEqual.overload('java.lang.Object', 'java.lang.Object').implementation = function(a, b){
6 | if (b.toString() == "431337133713373") {
7 | return true;
8 | }
9 | return this.areEqual(a, b);
10 | }
11 |
12 |
13 | var ctx = Java.use("android.content.Context");
14 | ctx.getSystemService.overload('java.lang.String').implementation = function(str) {
15 | console.log("calling getSystemService");
16 | return this.getSystemService(str);
17 | }
18 |
19 | var tm = Java.use('android.telephony.TelephonyManager');
20 | tm.getSubscriberId.overload().implementation = function() {
21 | console.log("calling getSubscriberId");
22 | return "431337133713373";
23 | }
24 | tm.getSubscriberId.overload('int').implementation = function(i) {
25 | console.log("calling getSubscriberId: " + i);
26 | return "431337133713373";
27 | }
28 |
29 | var secure = Java.use('android.provider.Settings$Secure');
30 | secure.getString.implementation = function(context, id) {
31 | console.log("called with: " + id);
32 | if (id == "android_id") {
33 | return "131337133713373";
34 | }
35 | return this.getString(context, id);
36 | }
37 |
38 | var wifi = Java.use('android.net.wifi.WifiInfo');
39 | wifi.getMacAddress.implementation = function() {
40 | return "01:02:03:04:05:06";
41 | };
42 |
43 |
44 | var macLevel = Java.use('com.hpandro.androidsecurity.ui.activity.task.deviceID.DeviceMacTaskActivity');
45 | macLevel.showFlag.implementation = function(flag) {
46 | console.log("Flag: " + flag);
47 | this.showFlag(flag);
48 | }
49 |
50 | var loc = Java.use('android.location.Location');
51 | Java.use('android.location.Location').getLatitude.implementation = function() {
52 | return 29.9791;
53 | }; /* strange, can't do both at the same time, will cause the app to crash
54 | Java.use('android.location.Location').getLongitude.implementation = function() {
55 | return 31.1341;
56 | };*/
57 |
58 | Java.use('com.hpandro.androidsecurity.ui.activity.task.deviceID.GPSLocationTaskActivity').showFlag.implementation = function(flag) {
59 | console.log("Flag: " + flag);
60 | this.showFlag(flag);
61 | }
62 |
63 | });
--------------------------------------------------------------------------------
/src/hpandro/emulator.js:
--------------------------------------------------------------------------------
1 | // version 1.1.15
2 | Java.perform(function(){
3 |
4 | var emu = Java.use('com.hpandro.androidsecurity.utils.emulatorDetection.EmulatorDetector');
5 | emu.log.implementation = function(s) { console.log("Logging: " + s); }
6 | emu.checkFiles.implementation = function(a, b){ return false }
7 | emu.checkIp.implementation = function(){ return false }
8 | emu.isDebug.implementation = function(){ return false }
9 | emu.checkOperatorNameAndroid.implementation = function(){ return false }
10 | emu.checkPhoneNumber.implementation = function(){ return false }
11 | emu.checkBasic.implementation = function(){ return false }
12 |
13 | // Can't open the correct level for Emulator package check and QEMU, pressing the task button does nothing at all.
14 | var lastMenuName = "";
15 | var act = Java.use('com.hpandro.androidsecurity.utils.fragment.HomeWebViewFragment');
16 | act.redirectToTask.implementation = function() {
17 | this.redirectToTask();
18 | console.log("redirect to task called");
19 | // The redirect to task button doesn't link to the Package name task activity, it just doesn't do anything
20 | // We have to open the activity ourselves instead.
21 | // Find the HomeWebViewFragment instance and start call startActivity on it with the appropriate class
22 | // when the task button is pressed.
23 | Java.choose('com.hpandro.androidsecurity.utils.fragment.HomeWebViewFragment', {
24 | onMatch: function(instance) {
25 | var activity = instance.getActivity();
26 | var targetClass = null;
27 | if (lastMenuName == "Check Package Name") {
28 | targetClass = Java.use('com.hpandro.androidsecurity.ui.activity.task.emulatorDetection.PackageNamesActivity');
29 | }
30 | else if (lastMenuName == "QEMU") {
31 | targetClass = Java.use('com.hpandro.androidsecurity.ui.activity.task.emulatorDetection.QEMUDetectionActivity');
32 | }
33 |
34 | if (activity != null && targetClass != null) {
35 | var intent = Java.use('android.content.Intent').$new(activity, targetClass.class);
36 | console.log("intent: " + JSON.stringify(activity));
37 | instance.startActivity(intent);
38 | }
39 | },
40 | onComplete: function() {}
41 | });
42 | }
43 |
44 | // Note having the code below active on startup can cause the app to crash,
45 | // comment it out during startup, or just load the script after startup instead.
46 |
47 | // Keep track of which menu entry that has been last pressed and use that as an indication
48 | // of which task is currently open.
49 | Java.use('com.hpandro.androidsecurity.ui.menu.MenuModel').getMenuName.implementation = function() {
50 | var ret = this.getMenuName();
51 | lastMenuName = ret;
52 | return ret;
53 | }
54 | });
--------------------------------------------------------------------------------
/src/hpandro/encryption.js:
--------------------------------------------------------------------------------
1 | // version 1.1.12
2 | Java.perform(function(){
3 |
4 | var rsa = Java.use('com.hpandro.androidsecurity.ui.activity.task.encryption.RSAActivity');
5 | rsa.decrypt.implementation = function(a, b) {
6 | var decrypted = this.decrypt(a, b);
7 | console.log("Decrypted: " + Java.use('java.lang.String').$new(decrypted));
8 | return decrypted;
9 | }
10 |
11 | var des = Java.use('com.hpandro.androidsecurity.ui.activity.task.encryption.DESTaskActivity');
12 | des.decrypt.implementation = function(a) {
13 | var decrypted = this.decrypt(a);
14 | console.log("Decrypted: " + decrypted);
15 | return decrypted;
16 | }
17 |
18 | var des3 = Java.use('com.hpandro.androidsecurity.ui.activity.task.encryption.ThreeDESActivity');
19 | des3.decrypt.implementation = function(a, b) {
20 | var decrypted = this.decrypt(a, b);
21 | console.log("Decrypted: " + decrypted);
22 | return decrypted;
23 | }
24 |
25 | var rc4 = Java.use('com.hpandro.androidsecurity.ui.activity.task.encryption.RC4Activity');
26 | rc4.decrypt.implementation = function(a) {
27 | var decrypted = this.decrypt(a);
28 | console.log("Decrypted: " + Java.use('java.lang.String').$new(decrypted));
29 | return decrypted;
30 | }
31 |
32 | var blowfish = Java.use('com.hpandro.androidsecurity.ui.activity.task.encryption.BlowfishActivity');
33 | blowfish.decrypt.implementation = function(a) {
34 | var decrypted = this.decrypt(a);
35 | console.log("Decrypted: " + decrypted);
36 | return decrypted;
37 | }
38 |
39 | var aes = Java.use('com.hpandro.androidsecurity.ui.activity.task.encryption.AESActivity');
40 | aes.decrypt.overload('java.lang.String', 'java.lang.String', '[B').implementation = function(a, b, c) {
41 | var decrypted = this.decrypt(a, b, c);
42 | console.log("Decrypted: " + Java.use('java.lang.String').$new(decrypted));
43 | return decrypted;
44 | }
45 |
46 | var iv = Java.use('com.hpandro.androidsecurity.ui.activity.task.encryption.PredictableInitializationVectorActivity');
47 | iv.decrypt.overload('java.lang.String', 'java.lang.String', '[B').implementation = function(a, b, c) {
48 | var decrypted = this.decrypt(a, b, c);
49 | console.log("Decrypted: " + Java.use('java.lang.String').$new(decrypted));
50 | return decrypted;
51 | }
52 |
53 | });
--------------------------------------------------------------------------------
/src/hpandro/misc.js:
--------------------------------------------------------------------------------
1 | // version 1.1.14
2 | Java.perform(function(){
3 |
4 | // Just brute force the pin
5 | var bd6 = Java.use("com.hpandro.androidsecurity.ui.activity.task.misc.Backdoor6Activity");
6 | bd6.hello.implementation = function(a) {
7 | console.log("hello called with " + a );
8 | var ret = "NO";
9 | for (let i = 0; i <= 9999; i++) {
10 | ret = this.hello(("000" + i).substr(-4,4));
11 | if (ret != "NO") {
12 | console.log("correct pin found at: " + i); //4321
13 | break;
14 | }
15 | }
16 | return ret;
17 | }
18 |
19 | });
--------------------------------------------------------------------------------
/src/hpandro/root.js:
--------------------------------------------------------------------------------
1 | // version 1.1.12
2 |
3 | Java.perform(function(){
4 |
5 | var root = Java.use('com.hpandro.androidsecurity.utils.rootDetection.RootDetectionUtils$Companion');
6 | root.checkFlagBusyBoxBinaries.implementation = function() {
7 | console.log("checking for busybox");
8 | return false; //make it return true on startup, then update to return false when in the level.
9 | }
10 |
11 | root.checkFlagSUExists.implementation = function() {
12 | console.log("checking for su exists");
13 | return false; //make it return true on startup, then update to return false when in the level.
14 | }
15 |
16 | root.checkFlagRWSystems.implementation = function() {
17 | console.log("checking for checkFlagRWSystems");
18 | return false; //make it return true on startup, then update to return false when in the level.
19 | }
20 |
21 | root.checkFlagRootManagementApps.implementation = function() {
22 | console.log("checking for checkFlagRootManagementApps");
23 | return false; //make it return true on startup, then update to return false when in the level.
24 | }
25 |
26 | /*
27 | // make it return true on startup, then run the original implementation in the task (app crashes otherwise).
28 | root.checkFlagRootClockingApps.implementation = function() {
29 | console.log("checking for checkFlagRootClockingApps");
30 | return true;
31 | }*/
32 |
33 | root.checkFlagPotentialDangerousApps.implementation = function() {
34 | console.log("checking for checkFlagPotentialDangerousApps");
35 | return false; //make it return true on startup, then update to return false when in the level.
36 | }
37 |
38 | root.checkFlagTestKeys.implementation = function() {
39 | console.log("checking for checkFlagTestKeys");
40 | return false; //make it return true on startup, then update to return false when in the level.
41 | }
42 |
43 | //got a crash when running the task first. But not running this function at all on startup, then running it later made it work.
44 | root.checkFlagDangerousProps.implementation = function() {
45 | console.log("checking for checkFlagDangerousProps");
46 | console.log("retval: " + ret);
47 | return false;
48 | }
49 |
50 | });
--------------------------------------------------------------------------------
/src/uncrackable1/uncrackable1.js:
--------------------------------------------------------------------------------
1 | Java.perform(function(){
2 |
3 | // Convert a byte array to a string
4 | // https://reverseengineering.stackexchange.com/a/22255
5 | function bufferToString(buf) {
6 | var buffer = Java.array('byte', buf);
7 | var result = "";
8 | for(var i = 0; i < buffer.length; ++i){
9 | result += (String.fromCharCode(buffer[i] & 0xff));
10 | }
11 | return result;
12 | }
13 |
14 | // If root / debuggable app is detected this method is called which shows an error
15 | // message to the user and then exit. Replace the method with one that does nothing.
16 | Java.use("sg.vantagepoint.uncrackable1.MainActivity").a.implementation=function(s){
17 | console.log("Tamper detection suppressed, message was: " + s);
18 | }
19 |
20 | // Automatically find the secret when onResume is called
21 | Java.use("sg.vantagepoint.uncrackable1.MainActivity").onResume.implementation = function() {
22 | this.onResume();
23 | Java.use("sg.vantagepoint.uncrackable1.a").a("dummy");
24 | }
25 |
26 | // The secret is encrypted. When the user inputs a string the correct one
27 | // is decrypted and compared with the provided one. This method returns
28 | // the decrypted version so a comparison can be made. Print the decrypted
29 | // secret that is returned.
30 | Java.use("sg.vantagepoint.a.a").a.implementation=function(ba1, ba2){
31 | const retval = this.a(ba1, ba2);
32 | console.log("secret code is: " + bufferToString(retval));
33 | return retval;
34 | }
35 |
36 | });
37 |
--------------------------------------------------------------------------------
/src/uncrackable2/uncrackable2.js:
--------------------------------------------------------------------------------
1 | Java.perform(function(){
2 |
3 | var MainActivity = Java.use("sg.vantagepoint.uncrackable2.MainActivity");
4 | MainActivity.a.overload("java.lang.String").implementation = function(s) {
5 | console.log("Tamper detection suppressed, message was: " + s);
6 | }
7 |
8 | Java.use("sg.vantagepoint.uncrackable2.MainActivity").onResume.implementation = function() {
9 | this.onResume();
10 |
11 | // Hook to strncmp and look for any calls to it with the magic word as argument
12 | var theMagicWord = "12345678901234567890123";
13 | var strncmp = listNames('libfoo.so', 'strncmp')
14 | attachToStrncmp(strncmp, theMagicWord);
15 |
16 | // Find the CodeCheck instance that was created by the app in onCreate()
17 | // Then call it with the magic word as argument.
18 | Java.choose("sg.vantagepoint.uncrackable2.CodeCheck", {
19 | onMatch : function(instance) {
20 | instance.a(Java.use("java.lang.String").$new(theMagicWord));
21 | return "stop";
22 | },
23 | onComplete:function() {}
24 | });
25 |
26 | // We've got what we wanted, detach again to prevent multiple attachments
27 | // to strncmp if onResume is called multiple times.
28 | Interceptor.detachAll();
29 | }
30 | });
31 |
32 | // Attach to strncmp on the given address and log any calls
33 | // made where one of the strings to compare is 'compareTo'
34 | function attachToStrncmp(strncmpAddress, compareTo) {
35 | Interceptor.attach(strncmpAddress, {
36 | // strncmp takes 3 arguments, str1, str2, max number of characters to compare
37 | onEnter: function(args) {
38 | var len = compareTo.length;
39 | if (args[2].toInt32() != len) {
40 | // Definitely comparing something uninteresting, bail.
41 | return;
42 | }
43 | var str1 = args[0].readUtf8String(len)
44 | var str2 = args[1].readUtf8String(len)
45 | if (str1 == compareTo || str2 == compareTo) {
46 | console.log("strncmp(" + str1 + ", " + str2 + ", " + len + ") called");
47 | }
48 | },
49 | // Return value of strncmp is of no interest.
50 | //onLeave: function(retval) {}
51 | });
52 | }
53 |
54 | // Lists all names (exports, imports and symbols) in the given module
55 | // if a 'needle' is given this function returns the address of the name
56 | // that matches the 'needle'
57 | function listNames(module, needle) {
58 | var address = undefined;
59 | console.log("Names found in " + module + ":");
60 | Process.enumerateModules()
61 | .filter(function(m){ return m["path"].toLowerCase().indexOf(module) != -1; })
62 | .forEach(function(mod) {
63 | mod.enumerateExports().forEach(function (entry) {
64 | console.log("Export: " + entry.name );
65 | if (entry.name.indexOf(needle) != -1) address = entry.address;
66 | });
67 | mod.enumerateImports().forEach(function (entry) {
68 | console.log("Import: " + entry.name );
69 | if (entry.name.indexOf(needle) != -1) address = entry.address;
70 | });
71 | mod.enumerateSymbols().forEach(function (entry) {
72 | console.log("symbol name: " + entry.name );
73 | if (entry.name.indexOf(needle) != -1) address = entry.address;
74 | });
75 | });
76 | console.log("");
77 | return address;
78 | }
79 |
--------------------------------------------------------------------------------
/src/uncrackable3/uncrackable3.js:
--------------------------------------------------------------------------------
1 | Java.perform(function(){
2 |
3 | const secretLength = 24;
4 | var xorkey = undefined;
5 |
6 | // libc.strstr == libfoo.strstr since it's imported by libfoo. But
7 | // libfoo has not been loaded when the script is loaded so we can't hook it
8 | // trying to hook it after it has been loaded is too late since it will
9 | // detect frida and exit right away when loaded. So hook libc instead
10 | Interceptor.attach(Module.findExportByName("libc.so", "strstr"), {
11 | onEnter: function(args) {
12 | this.fridaDetected = 0;
13 | if (args[0].readUtf8String().indexOf("frida") != -1) {
14 | this.fridaDetected = 1;
15 | }
16 | else {
17 | this.fridaDetected = 0;
18 | }
19 |
20 | },
21 | onLeave: function(retval) {
22 | if (this.fridaDetected == 1) {
23 | retval.replace(0);
24 | }
25 | }
26 | });
27 |
28 | Interceptor.attach(Module.findExportByName("libc.so", "strncpy"), {
29 | onEnter: function(args) {
30 | if (args[2].toInt32() == secretLength) {
31 | xorkey = new Uint8Array(args[1].readByteArray(secretLength));
32 | }
33 | },
34 | });
35 |
36 | var MainActivity = Java.use("sg.vantagepoint.uncrackable3.MainActivity");
37 | MainActivity.$init.implementation = function() {
38 | this.$init();
39 | attachToSecretGenerator();
40 | };
41 |
42 | MainActivity.onResume.implementation = function() {
43 | this.onResume();
44 | // Find the CodeCheck instance that was created by the app in onCreate()
45 | // Then call it with a dummy string.
46 | Java.choose("sg.vantagepoint.uncrackable3.CodeCheck", {
47 | onMatch : function(instance) {
48 | instance.check_code(Java.use("java.lang.String").$new("dummy"));
49 | return "stop";
50 | },
51 | onComplete:function() {}
52 | });
53 | };
54 |
55 | MainActivity.showDialog.implementation = function(s) {
56 | console.log("Tamper detection suppressed, message was: " + s);
57 | }
58 |
59 | function attachToSecretGenerator() {
60 | // function that generates the secret code is located at 0x12c0
61 | Interceptor.attach(Module.findBaseAddress('libfoo.so').add(0x12c0), {
62 | onEnter: function(args) {
63 | // First (only) argument is the address of where the secret should be stored
64 | this.answerLocation = args[0];
65 | },
66 | onLeave: function(retval) {
67 | var encodedAnswer = new Uint8Array(this.answerLocation.readByteArray(secretLength));
68 | var decodedAnswer = xorByteArrays(encodedAnswer, xorkey);
69 | console.log("Secret key: " + String.fromCharCode.apply(null, decodedAnswer));
70 | }
71 | });
72 | }
73 |
74 | function xorByteArrays(a1, a2) {
75 | var i;
76 | const ret = new Uint8Array(new ArrayBuffer(a2.byteLength));
77 | for (i = 0; i < a2.byteLength; i++) {
78 | ret[i] = a1[i] ^ a2[i];
79 | }
80 | return ret;
81 | }
82 | });
83 |
84 |
85 |
--------------------------------------------------------------------------------
/src/veryandroidoso/veryandroidoso.js:
--------------------------------------------------------------------------------
1 | Java.perform(function() {
2 | Java.use("ooo.defcon2019.quals.veryandroidoso.MainActivity").onResume.implementation = function() {
3 | this.onResume();
4 | var Solver = Java.use("ooo.defcon2019.quals.veryandroidoso.Solver");
5 | bypassSleep(Solver);
6 | var flagBytes = solve(Solver);
7 | console.log("flag: " + getFlag(flagBytes));
8 | }
9 | });
10 |
11 | // Count number of sleep calls to know how many bytes are correct
12 | // see comment for bypassSleep() for more details.
13 | var sleepCount = 0;
14 |
15 | // index is 0-8, specifying which parameter to solve
16 | // v is an array of values to use as input to the solver
17 | // returns a list of all acceptable values
18 | function solveByteAt(Solver, index, v) {
19 | var curr = v[index];
20 | var acceptable = [];
21 |
22 | // Bytes before the sixth byte sleeps once per byte
23 | // Byte 6 does not sleep at all (no prior call to scramble)
24 | // so all bytes after that requires one less sleep.
25 | var targetSleepCount = index < 5 ? index + 1 : index;
26 | do {
27 | v[index] = curr;
28 | sleepCount = 0;
29 | Solver.solve(v[0], v[1], v[2], v[3], v[4], v[5], v[6], v[7], v[8]);
30 | if (sleepCount > targetSleepCount) {
31 | acceptable.push(curr);
32 | }
33 | } while (curr++ < 256)
34 | v[index] = acceptable[0];
35 | return acceptable;
36 | }
37 |
38 | // Solve the last byte by trying all possible combinations of acceptable values
39 | function solveLast(Solver, acceptable) {
40 | console.log("solving last, this will take a while...");
41 | for (var a = 0; a < acceptable[0].length; a++) {
42 | for (var b = 0; b < acceptable[1].length; b++) {
43 | for (var c = 0; c < acceptable[2].length; c++) {
44 | for (var d = 0; d < acceptable[3].length; d++) {
45 | for (var e = 0; e < acceptable[4].length; e++) {
46 | for (var f = 0; f < acceptable[5].length; f++) {
47 | for (var g = 0; g < acceptable[6].length; g++) {
48 | for (var h = 0; h < acceptable[7].length; h++) {
49 | for (var i = 0; i < 256; i++) {
50 | var ret = Solver.solve(acceptable[0][a], acceptable[1][b], acceptable[2][c],
51 | acceptable[3][d], acceptable[4][e], acceptable[5][f], acceptable[6][g],
52 | acceptable[7][h], i);
53 | if (ret) {
54 | var secret = [acceptable[0][a], acceptable[1][b], acceptable[2][c],
55 | acceptable[3][d], acceptable[4][e], acceptable[5][f], acceptable[6][g],
56 | acceptable[7][h], i];
57 | console.log("Secret found: " + secret);
58 | return secret;
59 | }
60 | }
61 | }
62 | }
63 | }
64 | }
65 | }
66 | }
67 | }
68 | }
69 | console.log("couldn't find the secret :(");
70 | }
71 |
72 | function solve(Solver) {
73 | var params = [0, 0, 0, 0, 0 ,0, 0, 0, 0];
74 | var acceptable = [];
75 | acceptable[0] = solveByteAt(Solver, 0, params);
76 | console.log("Byte 1 solved: " + acceptable[0]);
77 |
78 | acceptable[1] = solveByteAt(Solver, 1, params);
79 | console.log("Byte 2 solved: " + acceptable[1]);
80 |
81 | acceptable[2] = solveByteAt(Solver, 2, params);
82 | console.log("Byte 3 solved: " + acceptable[2]);
83 |
84 | acceptable[3] = solveByteAt(Solver, 3, params);
85 | console.log("Byte 4 solved: " + acceptable[3]);
86 |
87 | // We detect that byte x is has been solved by counting calls to scramble
88 | // but byte 6 is solved without any scramble call. This means that both
89 | // byte 5 and 6 needs to be solved before the next call to scramble is
90 | // made. So solve byte 6 before 5 to be able to detect when byte 5 is
91 | // correct.
92 | var tmp = [];
93 | for(var i = 0; i < 256; i++) {
94 | if ((i & 0x41) == 65) {
95 | tmp.push(i);
96 | }
97 | }
98 | acceptable[5] = tmp;
99 | params[5] = tmp[0];
100 | console.log("Byte 6 solved: " + acceptable[5]);
101 |
102 | acceptable[4] = solveByteAt(Solver, 4, params);
103 | console.log("Byte 5 solved: " + acceptable[4]);
104 |
105 | acceptable[6] = solveByteAt(Solver, 6, params);
106 | console.log("Byte 7 solved: " + acceptable[6]);
107 |
108 | acceptable[7] = solveByteAt(Solver, 7, params);
109 | console.log("Byte 8 solved: " + acceptable[7]);
110 |
111 | return solveLast(Solver, acceptable);
112 | }
113 |
114 | // Get the flag by converting the array to a hex string
115 | function getFlag(bytes) {
116 | var ret = "OOO{";
117 | for(var i = 0; i < bytes.length; i++) {
118 | ret += ("0" + bytes[i].toString(16)).slice(-2);
119 | }
120 | return ret + "}";
121 | }
122 |
123 | // Whenever scramble is called it will sleep for 500ms, to be able to
124 | // use brute force, don't sleep at all. Instead just count how many
125 | // times it's called. Usually sleep is called once after each digit
126 | // has been verified to be correct. So by counting the number of
127 | // calls to sleep we know how many bytes are correct.
128 | function bypassSleep(Solver) {
129 | Solver.sleep.implementation = function(input) {
130 | sleepCount++;
131 | return input;
132 | };
133 | }
134 |
--------------------------------------------------------------------------------