[Frida Tutorial] FridaLab Writeup

ladins9·2024년 9월 2일

Android

목록 보기
1/4

FridaLab이란?

Frida로 APK 분석을 시작할 때 해당 툴에 익숙해지도록 거쳐가면 좋은 튜토리얼 앱.

1번부터 8번까지의 문제가 있으며 클리어 후 check를 하면 빨간 글자에서 초록 글자로 변환됨.

1) Change class challenge_01's variable 'chall01' to 1

먼저 jadx로 fridalab.apk를 decompile 해보면 아래와 같다.

static int chall01; 로 선언된 변수를 후킹을 통해 1로 변경.

  1. frida-ps-Uai를 사용해서 프로세스 확인 후
  2. frida.get_usb_device().attach("FridaLab")로 attach
  3. jscode를 작성하여 후킹
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("FridaLab")
script = process.create_script(jscode)
script.load()
  • Java.perform()
    후킹 코드를 파라미터로 넘겨 사용
  • Java.use()
    uk.rossmarks.fridalab.challenge_01 클래스를 var challenge_01에 저장합니다.
    ex) Java.use(앱의 패키지명.클래스 이름)

2) Run chall02()


chall02()를 실행하라고 해서 보니 MainActivity에 private void chall02() 함수가 존재함. 내부적으로는 어디에서도 호출하지 않는 함수이며 static으로 선언되어 있지 않아 Java.use()의 사용은 불가능.

import frida

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

});
"""
process = frida.get_usb_device().attach('FridaLab')
script = process.create_script(jscode)
script.load()
  • Java.choose()
    arg1: 찾고자 하는 인스턴스 'uk.rossmarks.fridalab.MainActivity'
    arg2: 찾았을 때(onMatch), 찾기를 끝냈을 때(onComplete) 실행할 함수

Java.use()의 경우 프록시 객체(proxy object)를 반환
Java.choose()의 경우 객체 인스턴스를 반환

3) Make chall03() return true


리턴 값을 True로 변환해야함.

import frida

jscode = """
Java.perform(function(){
    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('FridaLab')
script = process.create_script(jscode)
script.load()
input()

choose()와 .overload().implementation를 사용했지만,
아래와 같이 use()와 .implementation을 사용해도 된다.

let challenge_03 = Java.use('uk.rossmarks.fridalab.MainActivity');
    challenge_03.chall03.implementation = function() {
      return true;
    };

4) Send "frida" to chall04()

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("FridaLab")
script = process.create_script(jscode)
script.load()
input()

5) Always send "frida" to chall05()



onClick에서 "frida"문자열인지 계속 감시하는 중
"notfrida!"를 "frida"로 변경해야함

import frida

jscode = """
Java.perform(function(){
    let challenge_05 = Java.use('uk.rossmarks.fridalab.MainActivity');
    challenge_05.chall05.implementation = function(str) {
      str = "frida";
      this.chall05(str);
    };
});
"""

process = frida.get_usb_device().attach("FridaLab")
script = process.create_script(jscode)
script.load()
input()

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



MainActivity.onCreate에서 chall6 호출

confirmChall06(i)의 return이 True가 되려면
시작 시간으로부터 10초 뒤 i == chall06

chall06은 1초마다 1~50 사이의 값이 더해지다가 9000을 초과하면 i 값으로 초기화

1초마다 값이 바뀌므로 addChall06을 오버라이드해서 해당 값을 더할 때 main에서 chall06함수를 해당 인자로 실행하도록 함.

jscode = """
Java.perform(function(){
    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) {
 challenge_06.addChall06.overload('int').call(this,arg0);
        main.chall06(this.chall06.value)	
        return ;
    };
});
"""

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



MainActivity onCreate시 setChall07을 실행하여 chall07에 랜덤값을 넣는다.
check07Pin에 적합한 인자를 Bruteforce를 통해 찾아낸다.

  • main의 객체 인스턴스를 가져온다
  • challenge_07의 프록시 객체를 가져와 check07Pin과 1000부터 9999까지 비교하여 일치하는 값을 찾는다
  • main.chall07을 해당 인자로 수행 후 중단
jscode = """
Java.perform(function(){
    let main;
    Java.choose('uk.rossmarks.fridalab.MainActivity', {
    onMatch: function(instance) {
        main = instance;
    },
    onComplete: function() {}
    });
    
    let challenge_07 = Java.use('uk.rossmarks.fridalab.challenge_07')
    for(let i = 1000; i<10000; i++){
        if(challenge_07.check07Pin(i.toString())){
            main.chall07(i.toString())
            break;
        }
    }
});
"""

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

  • 디컴파일러를 통해 R.id.check는 0x7f07002f 라는 것을 확인
  • findViewById로 view를 찾고
  • view를 Button으로 캐스팅
  • java.lang.String.$new를 통해 Confirm이라는 문자열을 생성
  • setText(confirm)
jscode = """
Java.perform(function(){
    let main;
    Java.choose('uk.rossmarks.fridalab.MainActivity', {
    onMatch: function(instance) {
        main = instance;
    },
    onComplete: function() {}
    });

    let id = main.findViewById(0x7f07002f);
    let button = Java.use('android.widget.Button');
    let check = Java.cast(id, button);
    let confirm = Java.use('java.lang.String').$new('Confirm');
    check.setText(confirm);
});
"""

후기

어떤 메소드가 존재하는지 어떻게 사용해야하는지에 대한 부분은 외우지 않고 있어 그때그때 검색하거나 gpt의 도움을 많이 받았다.
큰 개념만 잡고 있으면 gpt로 금방 서치 가능하니 문제 없는 세상인듯 하다.

profile
잡학다식에서 박학다식으로

0개의 댓글