R e d A l i e n Click

엄청난 기술을 접하면, 마치 외계인의 기술을 훔친 것과 같다고 말합니다

붉은외계인은 그러한 놀라운 기술을 탐구하고, 기술적인 도전에 맞서는 것을 의미하는 저의 또 다른 이름입니다

서로가 성장할 수 있는 건설적인 토론을 좋아합니다.

article_thumbnail

[붉은외계인] Mobile - FridaLab 풀이

2024. 2. 26.
클릭 시, 이동!

들어가며

Frida를 배우고 난 후, 연습을 위하여 FridaLab 을 풀어보았다 

1 ~ 5단계는 쉽기 때문에, 해당 포스트에서는 6 ~ 8 단계만 다루고 있다
나머지 Solution Scripts는 아래 Github에 올려놓았으니, 참고하길 바란다

https://github.com/RedAlien00/FridaLab

 

GitHub - RedAlien00/FridaLab

Contribute to RedAlien00/FridaLab development by creating an account on GitHub.

github.com

 


 

서론

 

FridaLab을 풀어볼 때,
외우는것이 아닌 왜 이러한 메소드를 썼고, 이러한 코드를 짰는가에 포인트를 두고 진행하였다


그리고 많은 풀이들을 살펴보고, 이리저리 바꾸어가며 나의 걸로 만들려고 시도하였다
그 결과, 몇 가지 깨달은점이 있다 

 

◆ JavaScript의 화살표 함수를 사용할 경우, 오류가 발생한다는 것
◆ 조건문의 경우, True로 바꿔버리면 된다는 것 ( 이것은 아래에서 다룰 것이다 )
◆ use와 choose의 차이점을 확실히 이해해야 한다는 것

위의 그림을 살펴보자

Java.perform에서 사용하는 화살표 함수는 대부분의 경우에서 문제 없이 작동하였지만,
그 외에서 사용할 경우, 위와 같이 에러가 발생하는 경우가 많았다 
에러가 발생하는 원인을 모르겠다면, 화살표 함수를 변경해보자

 

 

let Activity = Java.use(""uk.rossmarks.fridalab.MainActivity"")

let Activity = Java.choose("uk.rossmarks.fridalab.MainActivity", {
    onMatch : function(){}
    onComplete : function(){}
  })

개인적으로, 가장 많은 시간을 할애했던 것은 use와 choose의 사용 방식이다

시간이 많이 들었던 이유는, 메뉴얼이 생각보다 자세하지 않았고,
검색을 통해 찾아보아도 각자 말하는 정의들이 달랐기 때문이다
Frida의 메뉴얼과 구글링, 그리고 FridaLab을 여러 번 풀어보면서, 나름대로 정의한 것은 아래와 같다

 

Java.use
- Static 맴버(정적 맴버)에 접근하는 메소드

Java.choose
- Heap을 스캔하여, 타겟 클래스의 인스턴스를 열거하는 메소드
- 메모리에 적재된 인스턴스 맴버에 접근하는 메소드

 

이제부터 본론으로 들어가보자

 


 

Challenge 6

해당 단계의 목표는 올바른 Value를 통해, 10초 뒤에 chall06을 실행하는 것이다
코드를 살펴보자

// Solution 1
Java.perform(function(){
  console.log("[+] Frida Start ...");
  let Main_Act = Java.choose("uk.rossmarks.fridalab.MainActivity",{
    onMatch : function(arg){
      let chall06_Act = Java.use("uk.rossmarks.fridalab.challenge_06")
      chall06_Act.confirmChall06.implementation = function(){return true}
      arg.chall06(1);
    },
    onComplete : function(){console.warn("Done !");}
  })
})

위에 코드는 올바른 Value를 삽입하는 것이 아닌,
chall06_Act_confirmChall06 메소드가, true를 반환하도록 재구현하여, 해결하는 방법이다

내가 위에서 언급한 " 조건문의 경우, True로 바꿔버리면 된다는 것 " 은 위와 같은 케이스를 의미한다
앱의 코드를 살펴보자

 // MainActivity
 public class MainActivity extends AppCompatActivity {
     public void chall06(int i) {
            if (challenge_06.confirmChall06(i)) {
                this.completeArr[5] = 1;
            }
        }
    }
// challenge_06
public static boolean confirmChall06(int i) {
        return i == chall06 && System.currentTimeMillis() > timeStart + 10000;
    }

결론적으로, chall06() 메소드를 실행하였을 때 True가 된다면 해결된다
그렇기 때문에, chall06_Act_confirmChall06 메소드가, True를 반환하도록 재구현한 것이다

제작자가 원하는 방식과는 다르지만, 어쨌든 하나의 풀이 방법이 될 수 있다
해당 코드는 challenge 3단계의 풀이 방법과 비슷하며, 조금 더 응용했다고 보면 된다

 

 

// Solution 2
Java.perform( console.warn("[+] Frida Start ..."))

setTimeout(function(){
  Java.perform(function(){
    Java.choose("uk.rossmarks.fridalab.MainActivity",{
      onMatch : function(arg){
        let Activity = Java.use("uk.rossmarks.fridalab.challenge_06")
        const value = Activity.chall06.value
        arg.chall06(value)
      },
      onComplete : function(){}
    })
    console.warn("[+] chall06 Success ! !");
  })
}
, 10000);

반대로, 위에 코드는 제작자가 원하는 방식의 풀이 방법을 기술한 것이며, 해결 포인트는 아래와 같다

 

1. MainActivity가 Oncreate 될 때, 위의 그림처럼 메소드가 호출되면서 challenge_06 클래스의
chall06, timeStart 필드값이 설정된다는 것
2. 10초가 지난 뒤에, chall06값과 일치하는 int 자료형을 넣으면 해결된다는 것

 

10초가 지난 뒤에 실행은 setTime() 함수로 구현하면 된다

문제 해결을 위해서는 MainActivity.chall06() 메소드를 호출하여야 하는데, 해당 메소드는
인스턴스 맴버이기 때문에, Java.use가 아닌, Java.choose를 사용해야 한다
( 위에서 언급하였지만, Java.choose는 메모리에 적재된 인스턴스 맴버에 접근하는 메소드라고 하였다 )

 

하지만 challenge_06의 chall06 필드는 static 맴버이기 때문에 Java.use를 사용하여 접근하여야 한다


 

Challenge 7

이번 단계의 목표는 check07pin()메소드를 Brute Force하여 chall07을 confirm 하는 것이다

// Solution 1 
Java.perform(function(){
  console.log("[+] Frida Start ...");
  Java.choose("uk.rossmarks.fridalab.MainActivity",{
    onMatch: function(arg){
      let Act_chall07 = Java.use("uk.rossmarks.fridalab.challenge_07")
      Act_chall07.check07Pin.implementation = function(){return true}
      arg.chall07('A')
    },
    onComplete : function(arg){console.log("[+] Done !");}
  })
})

위에 코드는 6단계 과정과 같은 원리로, true를 반환하도록 재구현 한 것이다 
같은 원리이기 때문에 설명은 생략하겠다

 

// Solution 2
Java.perform(function(){
  console.warn("[+] Frida Start ...")
  Java.choose("uk.rossmarks.fridalab.MainActivity",{
    onMatch: function(arg){
      const chall07 = Java.use("uk.rossmarks.fridalab.challenge_07")
      for(let i=1000; i<9999+1; i++){
        if(chall07.check07Pin(String(i))){
          console.warn(`Found ! ${i}`);
          arg.chall07(String(i))
          break
        }
        console.warn(`Brute Force : ${i}`);
      }
    },
    onComplete : function(arg){}
  })
  console.warn("[+] chall07 Success ! !");
})

위에 코드는 제작자가 원하는 방식의 해결 방법을 구현한것이다

 

이것 또한 위에서 설명한 것과 마찬가지로, 인스턴스 멤버는 Java.choose를, 
Static 맴버는 Java.use를 사용하여 접근하면 된다

0.0 ~ 0.9 의 난수를 생성하는 Math.random()함수 뒤에 추가로 연산이 있는데,
이는 결국 1000 ~ 9999이하 범위의 난수를 생성하는 것이다

 

그리고 랜덤 숫자는 "" + <랜덤숫자> 의 연산을 통해 chall07 필드에 저장된다
빈 문자열과 문자열 연결 연산이 되기 때문에, 만약 랜덤 숫자가 8이라면 "8" 문자열이 저장될 것이다
이러한 이유로, chall07() 메소드에 매개변수를 집어넣을 때는 문자열로 자료형 변환을 해주어야 한다

 


 

challenge 8

해당 단계에서 목표는 check 버튼의 text value를 Confirm으로 변경하는 것이다

// Solution 1
Java.perform(function(){
  const Activity = Java.use("uk.rossmarks.fridalab.MainActivity")
  Java.choose("uk.rossmarks.fridalab.MainActivity", {
    onMatch: function(arg){
      Activity.chall08.implementation = function(){return true}
      arg.chall08()
    },
    onComplete : function(){}

  })
})
// Solution 2
Java.perform(function(){
  console.warn("[-] Frida Start ...");

  Java.choose("uk.rossmarks.fridalab.MainActivity", {
    onMatch: function(arg){
      const checkId = arg.findViewById(0x7f07002f)      // View 객체 리턴
      const button = Java.use("android.widget.Button")
      const check = Java.cast(checkId, button)          // Button으로 타입 변환
      const string = Java.use("java.lang.String")
      check.setText(string.$new("Confirm"))            // String 객체 Confirm 생성
    },
    onComplete : function(){console.warn("[+] chall08 Success ! !");}
  })
})

Solution 1은 생략하고, 바로 2를 설명하겠다 

여기서, Java.cast는 형 변환 or 타입 변환을 해주는 메소드인데, Java 코드로 예를들면 아래 코드와 같다

Button button = (Button) findViewById(R.id.mybutton);

findViewById 메소드에 Resourece Id를 매개변수로 주면 해당 ID에 해당하는 View 객체가 리턴된다
그것을 Button으로 형 변환을 하는 코드이다

다음으로, $new 키워드는 객체의 생성자를 호출하여 인스턴스화 시키는 키워드이다
setText() 메소드에 바로 Confirm 을 삽입하면, 에러가 발생하기 때문에 $new 키워드를 통해
String 객체를 생성한 후, 삽입해주어야 한다


 

마무리

 

완전히 이해하는데까지 생각외로 시간이 꽤 걸린 것 같았다

위에서 언급하였듯, Java.use와 Java.choose가 시간을 많이 잡아먹었는데 
대충하고 넘어가면 나중에 발목을 잡을 것 같아서 끝까지 파고드느라 그렇다

 

 아! 그리고 아래 사이트에서도 많은 공부를 할 수 있었으니, 해당 게시물을 접하는 분들도
한번씩 꼭 들러보면 좋겠다

https://learnfrida.info/

 

Frida HandBook

 

learnfrida.info

 

끝 ~