FridaLab란?

https://rossmarks.uk/blog/fridalab/ 에서 제공되는 Frida를 익히기 위한 Challenge APK입니다.

 

FridaLab

I was struggling with a recent test using frida, knowing it could do what I

rossmarks.uk

apk파일은 위 링크에서 다운로드 받으실 수 있습니다.


 

실행해보기

apk파일을 설치해서 실행해보겠습니다.

여러 챌린지를 보여주고 Check버튼이 있습니다.

Check 버튼을 누르면 클리어한 챌린지는 초록색으로 변하게 됩니다. 지금은 아무것도 해결하지 못하였기 때문에 전부 빨간색으로 표시됩니다.


Challenge 1

 

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

즉 클래스 challenge_01에 있는 chall01이라는 변수를 1로 바꾸라는 챌린지입니다.

jdax 또는 bytecode viewer등을 통해 apk를 디컴파일합니다.

challenge_01

challenge_01 클래스를 보면, static int chall01이 선언되어있는 것을 볼 수 있습니다. 

저 chall01의 값을 1으로 바꾸는 것이 목표입니다. 

import frida


jscode = """
Java.perform(function(){
    var challenge_01 = Java.use('uk.rossmarks.fridalab.challenge_01');
    challenge_01.chall01.value = 1;
});
"""

process = frida.get_usb_device().attach('uk.rossmarks.fridalab')
script = process.create_script(jscode)
script.load()

jscode부분을 보면, Java.use로 challenge_01이라는 클래스를 변수로 가져온뒤, 그 안의 chall01.value를 1로 바꾸어 주었습니다.

여기서 Java.use(classname)은 특정 클래스를 로드하는 명령입니다.

해당 스크립트를 실행한 뒤 다시 check를 눌러보면 1번이 클리어된 것을 확인할 수 있습니다.


Challenge 2

 

2. Run chall02()

chall02()를 실행시켜야 하는 챌린지 입니다.

chall02()는 MainActivity에 선언되어있고, 내부적으로 어디에서도 호출되지 않습니다.

함수 호출 또한 1번을 풀때와 같이 호출하면 되지만, chall02()는 static이 아니기 때문에 바로 불러서 사용할 수 없습니다.

이럴 때는 Java.choose()를 이용해서 instance를 불러와야 합니다.

import frida


jscode = """
Java.perform(function(){
    //chall02
    var main;
    Java.choose('uk.rossmarks.fridalab.MainActivity', {
    onMatch: function(instance) {
        main = instance;
    },
    onComplete: function() {}
    });
    main.chall02();

});
"""

process = frida.get_usb_device().attach('uk.rossmarks.fridalab')
script = process.create_script(jscode)
script.load()

 

위 코드에서, Java.choose()로 mainactivity를 불러온 뒤, 1번에서 처럼 chall02()로 함수를 호출하였습니다.


Challenge 3

 

3. Make chall03() return true

chall03()의 return을 true로 만들어야 합니다.

chall03()은 mainactivity에 선언되어있고 false를 return 합니다.

true로 바꿔주기 위해선 chall03()을 overload 해주어야 합니다.

import frida

jscode = """
Java.perform(function(){
    //chall03
    
    var main;
    Java.choose('uk.rossmarks.fridalab.MainActivity', {
    onMatch: function(instance) {
        main = instance;
    },
    onComplete: function() {}
    });


    main.chall03.overload().implementation = function () {
		return true;
    };
    


});
"""

process = frida.get_usb_device().attach('uk.rossmarks.fridalab')
script = process.create_script(jscode)
script.load()
raw_input()

2번처럼 choose로 인스턴스를 불러와도 되고 1번처럼 use를 사용해도 상관없습니다.

overload().implementation을 이용해서 해당 메소드를 오버로드 할 수 있습니다.

마지막의 raw_input()이 있는 이유는 chall03()을 오버로드한 것이 check를 누를때 까지 유지되어야 하기 때문에 스크립트가 꺼지는 것을 방지하기 위함입니다.


Challenge 4

 

4. Send "frida" to chall04()

chall04()의 인자로 "frida"라는 문자열을 주어야 합니다.

2번문제와 마찬가지로 main.chall04()로 부르고 인자로 "frida"를 주면 됩니다.

import frida

jscode = """
Java.perform(function(){

    //chall04
    var main;
    Java.choose('uk.rossmarks.fridalab.MainActivity', {
    onMatch: function(instance) {
        main = instance;
    },
    onComplete: function() {}
    });

    main.chall04("frida");


});
"""

process = frida.get_usb_device().attach('uk.rossmarks.fridalab')
script = process.create_script(jscode)
script.load()
raw_input()

 

 


Challenge 5

 

5. Always send "frida" to chall05()

chall05()의 인자가 항상 frida가 되어야 합니다.

4번 처럼 chall04("frida")를 호출하는 것이 아닌 chall05가 호출될때 항상 chall("frida")가 호출되게 해야합니다.

import frida

jscode = """
Java.perform(function(){

    //chall05
    var main;
    Java.choose('uk.rossmarks.fridalab.MainActivity', {
    onMatch: function(instance) {
        main = instance;
    },
    onComplete: function() {}
    });


    main.chall05.overload('java.lang.String').implementation = function (arg0) {
        this.chall05.overload('java.lang.String').call(this,'frida');
        return ;
    };


});
"""

process = frida.get_usb_device().attach('uk.rossmarks.fridalab')
script = process.create_script(jscode)
script.load()
raw_input()

overload로 string을 인자로 받는 chall05를 오버로드하고, 메소드 내부에서 chall05("frida")를 호출하게 합니다.


Challenge 6

 

6. Run chall06() after 10 seconds with correct value

chall06()을 10초 뒤에 알맞는 값으로 호출해야합니다.

chall06은 int형 인자를 challenge_06.confirmChall06에 전달해서 confirmChall06(i)의 결과가 True여야 해결됩니다.

confirmChall06 메소드는 인자를 받아서 현재 시간이 timeStart +10000 보다 클 때 인자와 chall06의 값이 동일해야 합니다.

MainActivity의 onCreate에서 challenge_06의 startTime을 구하고, Timer를 설정해서 1초마다 랜덤한 수를 chall06에 더하게됩니다.

해결을 위해서 먼저 chall06의 값을 알아야 합니다.

import frida

jscode = """
Java.perform(function(){

    //chall06
    var challenge_06 = Java.use('uk.rossmarks.fridalab.challenge_06')
    
    challenge_06.addChall06.overload('int').implementation = function (arg0) {
        console.log(this.chall06.value);
        challenge_06.addChall06.overload('int').call(this,arg0);
        return ;
    };
    


});
"""

process = frida.get_usb_device().attach('uk.rossmarks.fridalab')
script = process.create_script(jscode)
script.load()
raw_input()

5번과 마찬가지의 방법으로 chall06의 값을 증가시켜주는 addChall06의 메소드를 오버라이드 시켜서, 원래의 함수를 호출하면서 chall06의 값을 출력하게 설정하였습니다.

222
263
294
298
330
337
341
351
356
406
443
480
510
537

(여기서 스크립트를 실행할 때, 앱을 한번 종료했다가 켜야합니다.) 

정상적으로 chall06의 값이 1초마다 출력되고있습니다. 이제 10초뒤의 chall06의 값을 chall06()의 인자로 전달하면 됩니다.

import frida

jscode = """
Java.perform(function(){

    //chall06
    var main;
    Java.choose('uk.rossmarks.fridalab.MainActivity', {
    onMatch: function(instance) {
        main = instance;
    },
    onComplete: function() {}
    });
    
    var challenge_06 = Java.use('uk.rossmarks.fridalab.challenge_06')
    
    challenge_06.addChall06.overload('int').implementation = function (arg0) {
        console.log(this.chall06.value);
        challenge_06.addChall06.overload('int').call(this,arg0);
        main.chall06(this.chall06.value)	//추가된 부분
        return ;
    };
    


});
"""

process = frida.get_usb_device().attach('uk.rossmarks.fridalab')
script = process.create_script(jscode)
script.load()
raw_input()

원래대로라면 10초 이후를 계산해야하지만, 간단하게 풀이하기 위해서 addChall06이 호출될 때마다 현재의 chall06값을 전달하였습니다.

스크립트를 실행하고 10초뒤 check를 눌러보면 해결됩니다.

 


Challenge 7

 

7. Bruteforce check07Pin() then confirm with chall07()

chall07()이 아닌 check07Pin()을 bruteforce해야합니다.

setChall07()을 통해서 랜덤으로 1000부터 9999의 수를 chall07에 저장합니다.

check07Pin()으로 chall07과 인자를 비교해볼 수 있습니다.

import frida

jscode = """
Java.perform(function(){

    //chall07
    var main;
    Java.choose('uk.rossmarks.fridalab.MainActivity', {
    onMatch: function(instance) {
        main = instance;
    },
    onComplete: function() {}
    });


    var challenge_07 = Java.use('uk.rossmarks.fridalab.challenge_07')
    for(var i = 1000; i<10000; i++){
        if(challenge_07.check07Pin(i.toString())){
            main.chall07(i.toString())
            break;
        }
    }


});
"""

process = frida.get_usb_device().attach('uk.rossmarks.fridalab')
script = process.create_script(jscode)
script.load()
raw_input()

for 문으로 1000부터 9999까지의 반복을 만들어 준뒤, check07Pin의 인자로 넘겨주어 return이 True가 되면, main의 chall07을 호출하는 형태로 해결할 수 있습니다.

 


Challenge 8

 

8. Change 'check' button's text value to 'Confirm'

'check'버튼의 텍스트를 'Confirm'으로 바꿔야합니다.

안드로이드의 UI 함수들은 Main UI Thread에서 동작합니다.

참고 : https://developer.android.com/reference/android/app/Activity#runOnUiThread%28java.lang.Runnable%29

 

Activity  |  Android 개발자  |  Android Developers

 

developer.android.com

그래서 main instance를 불러온 상태에선 findViewById를 사용할 수 있습니다.

디컴파일러에서 R.id를 확인해보면 check의 Id는 2131165231입니다.

 

 id를 통해 view를 불러온 뒤, button으로 캐스팅한 뒤, setText로 버튼의 텍스트를 수정하였습니다.

import frida

jscode = """
Java.perform(function(){
    //chall08
    var main;
    Java.choose('uk.rossmarks.fridalab.MainActivity', {
    onMatch: function(instance) {
        main = instance;
    },
    onComplete: function() {}
    });

    var id = main.findViewById(2131165231);
    var button = Java.use('android.widget.Button');
    var checkButton = Java.cast(id, button);
    var string = Java.use('java.lang.String');
    checkButton.setText(string.$new('Confirm'));
});
"""

process = frida.get_usb_device().attach('uk.rossmarks.fridalab')
script = process.create_script(jscode)
script.load()
raw_input()

 

여기서 $new는 java에서 new와 같이 생성자를 부르게됩니다.

 

 

전체 스크립트

import frida

jscode = """
Java.perform(function(){
    //chall01
    var challenge_01 = Java.use('uk.rossmarks.fridalab.challenge_01');
    challenge_01.chall01.value = 1;

    //chall02
    var main;
    Java.choose('uk.rossmarks.fridalab.MainActivity', {
    onMatch: function(instance) {
        main = instance;
    },
    onComplete: function() {}
    });

    main.chall02();

    //chall03
    main.chall03.overload().implementation = function () {
		return true;
    };

    //chall04
    main.chall04("frida");

    //chall05
    main.chall05.overload('java.lang.String').implementation = function (arg0) {
        this.chall05.overload('java.lang.String').call(this,'frida');
        return ;
    };

    //chall06
    var challenge_06 = Java.use('uk.rossmarks.fridalab.challenge_06')
    challenge_06.addChall06.overload('int').implementation = function (arg0) {
        console.log(this.chall06.value);
        challenge_06.addChall06.overload('int').call(this,arg0);
        main.chall06(this.chall06.value)
        return ;
    };

    //chall07
    var challenge_07 = Java.use('uk.rossmarks.fridalab.challenge_07')
    for(var i = 1000; i<10000; i++){
        if(challenge_07.check07Pin(i.toString())){
            main.chall07(i.toString());
            break;
        }
    }

    //chall08
    var id = main.findViewById(2131165231);
    var button = Java.use('android.widget.Button');
    var checkButton = Java.cast(id, button);
    var string = Java.use('java.lang.String');
    checkButton.setText(string.$new('Confirm'));
});
"""

process = frida.get_usb_device().attach('uk.rossmarks.fridalab')
script = process.create_script(jscode)
script.load()
raw_input()

 

 

복사했습니다!