Luv{Flag}
article thumbnail

 

Android와 모바일 해킹, 앱 버그바운티까지 공부해야겠다는 결심이 들었다.

그래서 frida 기본적 명령어와 옵션 정리하며 frida 입문으로 남들 다 풀어본 fridalab 풀어보고 정리하며 공부하려한다.

 

 

Frida 명령어

/data/local/tmp 에 frida server 설치 후 진행한다.

 

기본적인 명령어

frida-ls-devices 연결된 device 들의 리스트 출력
frida-ps -D emulator-5554 (-U : usb로 연결된 디바이스 / -D emulator-5554 : 가상 디바이스) 기기 연결
frida -D emulator-5554 -F [packagename] 앱에 attach, 작동 안되면 프리다 서버 확인. ATTACH 관련 표 참고
-l [filename] 후킹 할때 js파일 함께 (-D -F 와 같이씀)

 

 

frida 연결관련 옵션

-F 현재 foreground에서 실행되고 있는 앱을 연결 frida -D emulator-5554 -F FridaLab
-n <PACKAGE NAME> 앱의 패키지 이름으로 앱을 연결  
-p <PID> 앱의 프로세스 ID로 앱을 연결  
-f <PACKAGE NAME> *앱을 실행후, 연결*  

 

 

 

Frida Hooking 관련 함수

fridalab 을 풀면서 사용할 후킹 관련 함수 모음이다.

 

Java.perform(function() {}); 현재 스레드가 디바이스에 연결 되어있는지 확인 후 함수 호출
Java.use(className) 변수와 메서드에 엑세스 할 수 있는 클래스 객체를 반환
Java.choose(className, callbacks) Java.use와 혼동x , 인스턴스화 된 객체를 반환 (인스턴스 메서드 ↔ 스태틱 메서드 차이)
Java.enumerateLoadedClasses(className, callbacks) 로드된 모든 클래스를 열거하고 모든 일치 항목을 출력
.implementation 정의된 메소드의 구현 내용을 재작성
overload() implement 와 함께쓰기, 자바 오버로딩 
Java.setImmediate(fn) 단말기가 느려질 때 자동으로 프로세스를 종료하는 특성이 있다.이러한 경우를 방지하기 위해서 setImmediate()를 사용한다. 백그라운드로 자동으로 스크립트가 재실행되어 종료되지 않는다.

https://frida.re/docs/javascript-api/

 

JavaScript API

Observe and reprogram running programs on Windows, macOS, GNU/Linux, iOS, watchOS, tvOS, Android, FreeBSD, and QNX

frida.re

 

 

 

FridaLab writeup

Challenge 1

package uk.rossmarks.fridalab;

public class challenge_01 {
    static int chall01;

    public static int getChall01Int() {
        return chall01;
    }
}
Java.perform(()=>{
    var ch1 = Java.use("uk.rossmarks.fridalab.challenge_01");
    ch1.chall01.value= 1
});

 

 

MainActivity 를 확인하면 chall01 의 값을 1로 만들어야 한다고 분석할 수 있다.

java는 변수를 자동 0으로 초기화해서 Java.use로 chall01 static 변수에 접근해서 값을 강제로 수정한다.

 

 

Challenge 2

private void chall02() {
    this.completeArr[1] = 1;
}
Java.perform(()=>{
    var ch2 = Java.choose("uk.rossmarks.fridalab.MainActivity",{
        "onMatch": function(instance){
            instance.chall02();
        },
        "onComplete": function(){}
    });
});

 

chall02 를 실행시키기만 하면된다.

instance method 이기 떄문에, Java.choose 사용한다. onMatch. onComplete 반환한다.

 

 

Challenge 3

public boolean chall03() {
        return false;
    }
Java.perform(()=>{
    var ch3 = Java.use("uk.rossmarks.fridalab.MainActivity");
    ch3.chall03.implementation = function() {
        return true;
    }
});

메서드를 직접 호출할 필요 없이 반환 값만 변경해도 되기 때문에 static 메서드가 아니여도 Java.use()를 사용하여 후킹이 가능하다. 만약 메서드를 직접 호출하려면 choose 사용해야 한다.

 

Challenge 4

public void chall04(String str) {
        if (str.equals("frida")) {
            this.completeArr[3] = 1;
        }
    }
Java.perform(()=>{
    var ch4 = Java.choose("uk.rossmarks.fridalab.MainActivity",{
        "onMatch": function(instance){
            instance.chall04("frida");
        },
        "onComplete": function(){}
    });
});

challenge2 와 비슷하지만 frida 문자열을 전달해야 한다.

 

 

Challenge 5

public void chall05(String str) {
        if (str.equals("frida")) {
            this.completeArr[4] = 1;
        } else {
            this.completeArr[4] = 0;
        }
    }
Java.perform(()=>{
    var ch5 = Java.use("uk.rossmarks.fridalab.MainActivity");
    ch5.chall05.implementation = function(a) {
        this.chall05("frida");
    }
});

challenge4 와 같이 frida 문자열을 넘겨주는 것이 목표이나, MainActivity 에서 notfrida! 를 인자로 호출해버린다.
따라서 Java.use 사용하여 chall05를 오버로딩하여 frida 인자를 넘겨주도록 한다.

 

 

Challenge 6

public void chall06(int i) {
        if (challenge_06.confirmChall06(i)) {
            this.completeArr[5] = 1;
        }
    }
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;
        }
    }
}
Java.perform(()=>{
    var ch6 = Java.use("uk.rossmarks.fridalab.challenge_06");
    ch6.chall06.value= 1;
    Java.choose("uk.rossmarks.fridalab.MainActivity",{
        "onMatch": function(instance){
            instance.chall06(1);
        },
        "onComplete": function(){}   
    });
});

 

문제의 의도는 10초 후에  i와 chall06의 값이 같아야 하지만, chall06 변수의 값을 1로 설정하고, i값으로 1을 넣어주어 바로 풀리도록 했다.

의도대로라면 js의 setTimeout 을 활용하여 10초 후에 실행 하도록 하면 될것 같다.

 

 

Challenge 7

public void chall07(String str) {
        if (challenge_07.check07Pin(str)) {
            this.completeArr[6] = 1;
        } else {
            this.completeArr[6] = 0;
        }
    }
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);
    }
}

 

Java.perform(()=>{
    var ch7 = Java.use("uk.rossmarks.fridalab.challenge_07");
    ch7.chall07.value= "frida";
    Java.choose("uk.rossmarks.fridalab.MainActivity",{
        "onMatch": function(instance){
            instance.chall07("frida");
        },
        "onComplete": function(){}   
    });
});

 

랜덤하게 설정되는 chall07 값을 맞춰 주어야 하는 문제이다.

brute force attack 으로 풀 수도 있지만, 나는 challenge6 과 같은방법으로 풀었다.

 

 

Challenge 8

public boolean chall08() {
        return ((String) ((Button) findViewById(R.id.check)).getText()).equals("Confirm");
    }

 

Java.perform(()=>{
    var ch8 = Java.use("uk.rossmarks.fridalab.MainActivity");
    ch8.chall08.implementation = function() {
        return true;
    }
});

문제의 의도는 아니나, 풀리기는 한다.

Java.perform(()=>{
    var button = Java.use("android.widget.Button");
    Java.choose("uk.rossmarks.fridalab.MainActivity",{
        "onMatch": function(instance){
            var check = instance.findViewById(0x7f07002f)
            var confirm = Java.cast(check, button);
            confirm.setText(Java.use("java.lang.String").$new("Confirm"));
        },
        "onComplete": function(){}
    });
});

버튼의 check 문자열을 confirm 으로 바꾸는 것의 원래의 의도이고, 그에 맞는 풀이이다.

 

우선 똑같이 mainactivity 의 인스턴스를 매칭하여 findViewById 로 버튼을 지정, Confirm 이라는 string 객체를 생성해서 버튼의 문자열을 변경한다.

 

 

 

스프링개발과 앱개발 경험이 있기때문에 이해하는데에 큰 어려움은 없었으나, 실제 환경에서 버그바운티에 어떻게 활용하고 우회할지는 아직 감이 오지 않는다.

계속 공부해 나가야 할 것 같다.

반응형

검색 태그

loading