FridaLab – Writeup

Today I solved FridaLab, a playground Android application for playing with Frida and testing your skills.

The app is made of various challenges, with increasing difficulty, that will guide you through Frida’s potential.

This is a writeup with solutions to the challenges in FridaLab. We suggest the reader to take a look at it and try to solve it by itself before reading further.

In this writeup we will assume that the reader has a working environment with frida-server already installed on the Android device and frida-tools installed on the PC as well, since we will not cover those topics.

Challenge 1

First of all, install and decompile the APK file and open the Frida API documentation.

The first challenge is:

Change class challenge_01’s variable ‘chall01’ to 1

From the decompiled code we can se that the challenge_01 class has a variable chall01 declared as int.

1
2
3
4
5
6
7
8
9
package uk.rossmarks.fridalab;

public class challenge_01 {
    static int chall01;

    public static int getChall01Int() {
        return chall01;
    }
}

With Frida we can get a wrapper for a Java class and change its static variables like this:

1
2
3
4
5
6
7
if (Java.available) {
  Java.perform(function() {
    // Chall01
    var challenge_01 = Java.use('uk.rossmarks.fridalab.challenge_01');
    challenge_01.chall01.value = 1;
  }
}

All the Frida code we will list below needs to be placed inside a Java.perform in order to run, like the code above.


Challenge 2

Run chall02()

chall02 is an instance method of the MainActivity class (it’s not declared as static), so if we try to run it with the above approach Frida will fail telling us:

“Error: chall02: cannot call instance method without an instance”.

We need to get a running instance (or making a new one) to call that method.
The following code will do it through the Java.choose Frida API.

1
2
3
4
5
6
7
8
9
var main;
Java.choose('uk.rossmarks.fridalab.MainActivity', {
  onMatch: function(instance) {
    main = instance;
  },
  onComplete: function() {}
});
// Chall02
main.chall02();

Note that we are declaring a main variable so we can use that instance for the following challenges.


Challenge 3

Make chall03() return true

We need to overload MainActivity.chall03() with an implementation that always returns true.
With Frida we can overload it like this:

1
2
3
4
// Chall03
main.chall03.overload().implementation = function () {
    return true;
}

Challenge 4

Send “frida” to chall04()

Can be solved just by calling the function with “frida” as argument:

1
2
// Chall04
main.chall04('frida');

Challenge 5

Always send “frida” to chall05()

We can solve this challenge by overloading the function and inside the implementation call the original one with “frida” as argument.
This translate to the following code:

1
2
3
4
5
// Chall05
main.chall05.overload('java.lang.String').implementation = function (arg0) {
    this.chall05.overload('java.lang.String').call(this,'frida');
    return;
}

Challenge 6

Run chall06() after 10 seconds with correct value

In the challenge_06 class there are 3 static methods: startTime, addChall06 and confirmChall06.

The startTime method simply stores the time when the application started in the variable timeStart.
The addChall06 is called every 1 second inside a TimerTask (see MainActivity code) with a random value that is summed to the variable chall06.
The confirmChall06 checks if 10 seconds are elapsed since the application’s start and if the argument value match the variable chall06.

Here we can follow 2 approach:

  1. Wait 10 seconds and submit to confirmChall06 the value inside chall06.
  2. Subtract 10 seconds from timeStart and submit as above.

The following code will wait 10 seconds and perform the submission:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
setTimeout(function () {
  Java.perform(function () {
    // Chall06
    var challenge_06 = Java.use('uk.rossmarks.fridalab.challenge_06');
    challenge_06.addChall06.overload('int').implementation = function (arg0) {
      console.warn("Solved Challenge 06");
      Java.choose('uk.rossmarks.fridalab.MainActivity', {
        onMatch: function(instance) {
          instance.chall06(challenge_06.chall06.value);
        },
        onComplete: function() {}
      });
    }
  })
}, 10000);

Challenge 7

Bruteforce check07Pin() then confirm with chall07()

This is pretty interesting since we can completely bypass the bruteforce check by watching the value of the variable chall07.

Anyway, we know that the pin is 4 digits long so we can cycle through all the possible values from 9999 to 0000.

We defined a pad function in JavaScript that convert the pin from integer to string and adds 0es to the left, if necessary.

The Frida code that performs the bruteforce is the following:

1
2
3
4
5
6
7
8
9
// Chall07
var challenge_07 = Java.use('uk.rossmarks.fridalab.challenge_07');
console.log("Target PIN: " + challenge_07.chall07.value);
for (var i = 9999; i >= 0; i--) {
  if (challenge_07.check07Pin(pad(i, 4))) {
    main.chall07(pad(i,4));
    break;
  }
}

Challenge 8

 Change ‘check’ button’s text value to ‘Confirm’

This challenge consists of UI manipolation.
On Android, UI functions must run on the Main UI thread, in Frida we can use the Java.scheduleOnMainThread API.

It’s just a matter of calling the Android Activity.findViewById method, casting to the correct class and replacing it’s text with Button.setText.
** Code below
:
**

1
2
3
4
5
6
// Chall08
var button = Java.use('android.widget.Button');
var checkid = main.findViewById(2131165231);
var check = Java.cast(checkid.$handle, button);
var string = Java.use('java.lang.String');
check.setText(string.$new("Confirm"));

Conclusion

The complete solver script is available at: @TheZ3ro/fridalab-solver

Thanks to Ross Marks for making FridaLab.
I really enjoyed breaking it and I hope they will made a version 2 (with some JNI code maybe) or an iOS version.

3 min

Date

4 February 2019

Author

thezero

Security Researcher and Senior Penetration Tester at Shielder.
In the office I’m the one with the soldering iron.