앱 정보
앱을 실행하면 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로 설정하기 위해서는 다음 두 가지 중 하나를 이용하여 진행한다.
- 변수 chall01의 값을 직접 1로 설정한다.
- 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번이 해결된 것을 확인할 수 있다.
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를 해결한 모습을 확인할 수 있다.
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을 해결할 수 있다.
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를 해결할 수 있다.
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를 해결할 수 있다.
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을 해결할 수 있다.
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을 해결할 수 있다.
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을 해결할 수 있다.
마지막으로 모든 레벨의 스크립트를 모아 실행하면 다음과 같이 모든 문제를 해결할 수 있다.
Reference
- setTimeout : https://developer.mozilla.org/ko/docs/Web/API/setTimeout
- Fridalab : https://rossmarks.uk/blog/fridalab/
- Frida : https://frida.re/docs/javascript-api/
- findViewById : https://developer.android.com/reference/android/view/View#findViewById(int)
- padStart : https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/String/padStart