Android/FridaLab

Level 1 ~ 8

앱 정보

[그림 1] 앱 실행 후 모습

앱을 실행하면 1 ~ 8번까지 조건들이 나열되어 있으며, CHECK 버튼을 눌렀을 때 해당 조건이 만족되면 초록색 그렇지 않으면 빨간색으로 바뀌는 애플리케이션이다.

 

Level 1

  • Change class challenge_01's variable 'chall01' to:1

Level 1의 조건은 challenge_01 클래스의 chall01 변수의 값을 1로 설정하는 것이다.

 

challenge_01

public class challenge_01 {
    static int chall01;

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

challenge_01 클래스를 확인해보면 다음과 같다.

  • chall01 변수와 해당 변수의 값을 넣어줄 수 있는 getChall01Int() 함수를 확인할 수 있다.

 

변수의 값을 1로 설정하기 위해서는 다음 두 가지 중 하나를 이용하여 진행한다.

  1. 변수 chall01의 값을 직접 1로 설정한다.
  2. getChall01Int 함수를 후킹 하여 반환 값을 1로 설정한다.

 

Exploit Code

console.log("Script loaded successfully");
Java.perform(function x() {
    console.log("java perform function");
    var ch1 = Java.use('uk.rossmarks.fridalab.challenge_01');
    ch1.chall01.value = 1; // 첫 번째 방법

    ch1.getChall01Int.implementation = function () { // 두 번째 방법
        return 1;
    }
});

코드의 구성은 다음과 같다.

  • Java.use(className)을 통해 challenge_01 클래스를 가져온다.
    • class.variableName.value = 1을 사용하여 chall01 변수의 값을 1로 설정한다.
    • class.functionName.implementation을 통해 함수를 후킹 한 후 반환 값으로 1을 돌려준다.

스크립트 실행 후 CHECK 버튼을 눌러주면 다음과 같이 1번이 해결된 것을 확인할 수 있다.

[그림 2] Level 1 성공

 

Level 2

  • Run chall02()

Level 2의 조건은 chall02() 함수를 실행시키는 것이다.

 

chall02()

private void chall02() {
        this.completeArr[1] = 1;
    }

chall02() 함수를 살펴보면 다음과 같다.

  • completeArr[] 배열 두 번째 값에 1을 넣어주는 것을 확인할 수 있지만 해당 함수가 어디에도 호출되지 않는다는 것을 알 수 있다.

그러므로 해당 함수를 호출하는 코드를 작성하여 이를 해결해주면 문제를 풀 수 있다.

 

Exploit Code

console.log("Script loaded successfully");
Java.perform(function x() {
    console.log("java perform function");
    var main;
    Java.choose('uk.rossmarks.fridalab.MainActivity', {
        onMatch: function(instance) {
            main = instance;
        },
        onComplete: function() {}
    });
    main.chall02();
});

코드의 구성은 다음과 같다.

  • Java.choose(className, callbacks)를 사용하여 MainActivity의 인스턴스를 변수 main에 저장한다.
  • instance.functionName()을 통해 chall02() 함수를 호출해준다.

스크립트를 실행해보면 Level 2를 해결한 모습을 확인할 수 있다.

[그림 3] Level 2 성공

 

Level 3

  • Make chall03() return true

Level 3의 조건은 chall03() 함수의 반환 값을 true로 변경하는 것이다.

 

chall03()

public boolean chall03() {
        return false;
    }

chall03() 함수를 살펴보면 다음과 같다.

  • false값을 반환한다.

이를 해결하기 위해서는 hall03() 함수를 후킹 한 후 반환 값을 true로 변환하면 해결할 수 있다.

 

Exploit Code

console.log("Script loaded successfully");
Java.perform(function x() {
    console.log("java perform function");
    var ch3 = Java.use('uk.rossmarks.fridalab.MainActivity');
    ch3.chall03.implementation = function () {
        return true;
    }
});

코드의 구성은 다음과 같다.

  • Java.use(className)을 통해 chall03() 함수가 포함되어있는 MainActivity 클래스를 가져온다.
  • class.functionName.implementaion을 사용하여 chall03() 함수를 후킹 한 후 반환 값으로 true를 반환해준다.

스크립트를 실행하면 다음과 같이 Level 3을 해결할 수 있다.

[그림 4] Level 3 성공

 

Level 4

  • Send "frida" to chall04()

Level 4의 조건은 chall04() 함수에게 "frida" 문자열을 보내는 것이다.

 

chall04()

public void chall04(String str) {
        if (str.equals("frida")) {
            this.completeArr[3] = 1;
        }
    }

chall04() 함수를 살펴보면 다음과 같다.

  • 인자로 String 형태의 값을 받고 해당 값이 "frida"일 경우 completArr 배열 4번째 값에 1을 설정하여 문제를 해결할 수 있다는 것을 알 수 있다.

문제 해결은 Level 2에서 사용한 방법을 거의 그대로 사용하면 해결할 수 있다.

 

Exploit Code

console.log("Script loaded successfully");
Java.perform(function x() {
    console.log("java perform function");
    var main;
    Java.choose('uk.rossmarks.fridalab.MainActivity', {
        onMatch: function(instance) {
            main = instance;
        },
        onComplete: function() {}
    });
    main.chall04('frida');
});

코드의 구성은 다음과 같다.

  • Java.choose(className, callbacks)를 사용하여 MainActivity의 인스턴스를 변수 main에 저장한다.
  • instance.functionName('frida')을 통해 'frida' 문자열을 인자로 chall04() 함수를 호출한다.

스크립트를 실행하면 다음과 같이 Level 4를 해결할 수 있다.

[그림 5] Level 4 성공

 

Level 5

  • Always send "frida" to chall05()

Level 5의 조건은 언제나 chall05() 함수에게 "frida" 문자열을 보내는 것이다.

 

MainActivity

public void onClick(View view) {
    MainActivity.this.chall05("notfrida!");
            if (MainActivity.this.chall08()) {
                MainActivity.this.completeArr[7] = 1;
            }
public void chall05(String str) {
        if (str.equals("frida")) {
            this.completeArr[4] = 1;
        } else {
            this.completeArr[4] = 0;
        }
    }

MainActivty 클래스를 살펴보면 다음과 같은 역할을 한다.

  • 인자로 String 형태의 값을 받고 해당 값이 "frida"일 경우 completArr 배열의 5번째 값을 1로 설정하면서 문제를 해결할 수 있다는 것을 알 수 있다.
  • 문제는 onClick() 함수에서 "notfrida!"를 인자로 chall05() 함수를 호출하여 버튼을 누를 때마다 "notfrida!"를 인자로 넘어가는 것을 확인할 수 있다.

이를 해결하기 위해서는 해당 함수를 후킹 하여 넘어오는 인자 값을 "frida"로 변경해주면 된다.

 

Exploit Code

console.log("Script loaded successfully");
Java.perform(function x() {
    console.log("java perform function");
    var ch5 = Java.use('uk.rossmarks.fridalab.MainActivity');
    ch5.chall05.overload('java.lang.String').implementation = function (p0) {
        this.chall05('frida');
        return;
    }
});

코드의 구성은 다음과 같다.

  • Java.use(className)을 통해 chall05() 함수가 포함되어있는 MainActivity 클래스를 가져온다.
  • class.functionName.implementaion을 사용하여 chall05() 함수를 후킹 한 후 chall05() 함수를 "frida"를 인자로 호출해준다.

스크립트를 실행하면 다음과 같이 Level 5를 해결할 수 있다.

[그림 6] Level 5 성공

 

Level 6

  • Always send "frida" to chall05()

Level 6의 조건은 10초가 지난 이후 정확한 값을 chall06() 함수에게 보내는 것이다.

 

challenge_06

public class challenge_06 {
    static int chall06;
    static long timeStart;

    public static void startTime() {
        timeStart = System.currentTimeMillis();
    }

    public static boolean confirmChall06(int i) {
        return i == chall06 && System.currentTimeMillis() > timeStart + 10000;
    }

    public static void addChall06(int i) {
        chall06 += i;
        if (chall06 > 9000) {
            chall06 = i;
        }
    }
}

challenge_06 클래스에서는 다음과 같은 역할을 한다.

  • startTime() 함수는 timeStart 변수에 현재 시간을 저장한다.
  • confirmChall06() 함수는 인자로 넘어온 i 값과 chall06이 같으며, timeStart에 저장된 값에서 10000을 더한 값보다 현재 시간 값이 더 크면(timeStart에 저장된 시간에서 10초가 지났을 때) true를 반환한다.
  • addChall06() 함수는 인자로 넘어온 값을 chall06 값에 더하고 만약 더한 값이 9000보다 크다면 chall06 값을 i 값으로 대체한다.

 

MainActivity

public void onCreate(Bundle bundle) {
      challenge_06.startTime();
      challenge_06.addChall06(new Random().nextInt(50) + 1);
      new Timer().scheduleAtFixedRate(new TimerTask() {
          public void run() {
              int nextInt = new Random().nextInt(50) + 1;
              challenge_06.addChall06(nextInt);
              Integer.toString(nextInt);
          }
      }, 0, 1000);
  }
public void chall06(int i) {
      if (challenge_06.confirmChall06(i)) {
          this.completeArr[5] = 1;
      }
  }

MainActivity 클래스에서는 다음과 같은 역할을 한다.

  • startTime() 함수를 호출하여 현재 시간을 설정한다.
  • addChall06() 함수의 인자로 1 ~ 50까지의 무작위 수를 인자로 넘겨준다.
  • 10초 동안 1 ~ 50까지의 무작위 수를 addChall06() 함수의 인자로 넘겨준다.
  • chall06() 함수는 인자로 넘어온 값을 confirmChall06() 함수로 넘겨 True를 반환한다면 completeArr 배열 6번째에 값을 설정하여 문제를 해결한다.

 

Exploit Code

console.log("Script loaded successfully");
Java.perform(function x() {
    console.log("java perform function");
    var main;
    Java.choose('uk.rossmarks.fridalab.MainActivity', {
        onMatch: function(instance) {
            main = instance;
        },
        onComplete: function() {}
    });
    var ch6 = Java.use('uk.rossmarks.fridalab.challenge_06');
    setTimeout(function () {
        console.log("chall06 values is : " + ch6.chall06.value);
        main.chall06(ch6.chall06.value);
    }, 10000); 

});

코드의 구성은 다음과 같다.

  • Java.choose(className, callbacks)를 사용하여 MainActivity의 인스턴스를 변수 main에 저장한다.
  • Java.use(className)을 통해 challenge_06 클래스를 가져온다.
  • setTimeout() 함수를 사용하여 10초 이후 chall06 변수에 저장된 값을 출력 후 chall06() 함수의 인자로 넘기도록 한다.

스크립트를 실행하면 다음과 같이 Level 6을 해결할 수 있다.

[그림 7] 10초 후 chall06 값을 출력하는 모습
[그림 8] Level 6 성공

 

Level 7

  • Brouteforce check07Pin() then confirm with chall07()

Level 7의 조건은 브루트포스를 통해 check07Pin() 함수를 인증하여 해결하는 것이다.

 

MainActivity

public void onCreate(Bundle bundle) {
    //....
    challenge_07.setChall07();
}
public void chall07(String str) {
        if (challenge_07.check07Pin(str)) {
            this.completeArr[6] = 1;
        } else {
            this.completeArr[6] = 0;
        }
    }

MainActivity 클래스에서는 다음과 같은 역할을 한다.

  • onCreate() 함수에서 challenge_07 클래스의 setChall07() 함수를 호출한다.
  • chall07() 함수의 경우 문자열형 인자를 받고 내부에서 이를 check07Pin() 함수로 넘겨준 후 만약 true를 반환한다면 completeArr 배열의 7번째 값에 1을 넣어주어 문제를 해결할 수 있다는 것을 알 수 있다.

 

challenge_07

public class challenge_07 {
    static String chall07;

    public static void setChall07() {
        chall07 = BuildConfig.FLAVOR + (((int) (Math.random() * 9000.0d)) + 1000);
    }

    public static boolean check07Pin(String str) {
        return str.equals(chall07);
    }
}

challenge_07 클래스에서는 다음과 같은 역할을 한다.

  • setChall07() 함수를 통해 4 자릿수 난수 값을 문자열 형 chall07 변수에 저장한다.
  • check07Pin() 함수에서는 넘어온 인자와 chall07 변수를 비교하여 같다면 true 그렇지 않다면 false를 반환한다.

해당 레벨은 onCreate() 함수에서 호출되는 setChall07() 함수에서 설정된 4자리 난수를 맞추면 해결할 수 있는 문제이다.

 

Exploit Code

console.log("Script loaded successfully");
Java.perform(function x() {
    console.log("java perform function");
    var main;
    Java.choose('uk.rossmarks.fridalab.MainActivity', {
        onMatch: function(instance) {
            main = instance;
        },
        onComplete: function() {}
    });
    var ch7 = Java.use('uk.rossmarks.fridalab.challenge_07');
    for (var i = 1 < 9999 ; i <= 9999 ; i++) {
        var key = i.toString(10);
        if(ch7.check07Pin(key.padStart(4,'0'))) {
            main.chall07(key);
            console.log("key is " + key);
            break;
        }
    }
});

코드의 구성은 다음과 같다.

  • Java.choose(className, callbacks)를 사용하여 MainActivity의 인스턴스를 변수 main에 저장한다.
  • Java.use(className)을 사용하여 challenge_07 클래스를 가져온다.
  • 이후 1부터 9999까지의 숫자를 4자리 문자열형으로 변환하여 check07Pin()함수에 인자로 넘겨준다. 만약 true를 반환했다면 해당 값이 key 값이기 때문에 이를 저장한다.
  • 획득한 key 값을 MainActivity의 chall07() 함수에 인자로 넘겨준다.

스크립트를 실행하면 다음과 같이 Level 7을 해결할 수 있다.

[그림 9] chall07 값 출력 모습
[그림 10] Level 7 성공

 

Level 8

  • Change 'check' button's text value to 'Confirm'

Level 8의 조건은 버튼의 텍스트를 'check'에서 'Confirm'으로 변경하는 것이다.

 

MainActivity

public void onClick(View view) {
	if (MainActivity.this.chall08()) {
	      MainActivity.this.completeArr[7] = 1;
	}
}
public boolean chall08() {
        return ((String) ((Button) findViewById(R.id.check)).getText()).equals("Confirm");
    }

MainActivity 클래스에서는 다음과 같은 역할을 한다.

  • onClick() 함수에서는 chall08() 함수 호출 후 true를 반환한다면 completeArr 배열 8번째 값에 1을 설정한다.
  • chall08() 함수에서는 findViewById() 함수와 check 변수에 저장된 값을 통해 뷰를 찾고, 찾은 뷰에 저장된 텍스트와 "Confirm"과 비교하여 같다면 true를 반환한다.

 

Exploit Code

console.log("Script loaded successfully");
Java.perform(function x() {
    console.log("java perform function");

    var id = Java.use('uk.rossmarks.fridalab.R$id');
    var check_id = id.check.value;
    console.log("check id : " + check_id);

    var button = Java.use('android.widget.Button');
    var main;
    Java.choose('uk.rossmarks.fridalab.MainActivity', {
        onMatch: function(instance) {
            main = instance;
        },
        onComplete: function() {}
    });
    var check = Java.cast(main.findViewById(check_id), button);
    console.log("before : " + check.getText());
    var str = Java.use('java.lang.String');
    check.setText(str.$new('Confirm'));
    console.log("after : " + check.getText());
});

코드의 구성은 다음과 같다.

  • 가장 처음으로 호출되는 Java.use()로는 리소스 클래스 내부 id 클래스를 가져온 후 check 변수의 값을 가져오기 위해 사용된다(해당 부분은 리소스 파일을 통해 직접 값을 가져온다면 무시해도 무관하다).
  • 변수 button에 Java.use()를 사용해서 Button 클래스를 가져온다.
  • MainActivty의 경우 이전 레벨과 동일하게 진행하면 된다.
  • Java.cast(handle, klass)를 사용하여 자바스크립트 래퍼를 생성한다.
  • 생성된 래퍼를 사용하여 getText() or setText()를 사용하면 되는데 문자열을 설정할 경우 타입을 맞춰야 하기 때문에 java.lang.String을 사용하여 생성한 래퍼를 사용한다.

스크립트를 실행하면 다음과 같이 Level 8을 해결할 수 있다.

[그림 11] Level 8 성공

 

마지막으로 모든 레벨의 스크립트를 모아 실행하면 다음과 같이 모든 문제를 해결할 수 있다.

[그림 12] 모든 Level 성공

 

Reference