Droid Flag - 100
이번 KnightCTF 문제 풀면서 안드로이드 관련 문제가 나왔는데 워겜이나 변조 앱 분석은 했어도 CTF 문제는 처음이라 풀이를 남긴다.
Analysis
Application
앱 실행 후 확인해보면 다음과 같은 부분을 확인할 수 있다.
- “Enter Flag”라는 안내 문구가 적혀있으며, 사용자가 입력할 수 있는 칸
- “VALIDATE”라는 문자열이 적힌 Flag 인증 버튼
Source Code
MainActivity
디컴파일러를 이용하여 APK를 열어준 후 MainActivity를 확인하기 위해 AndroidManifest 파일을 통해 이를 확인해준다.
MainActivity를 확인해보면 이런저런 코드를 확인할 수 있는데 여기서 중요한 부분은 이전에 확인해본 것과 같이 값을 입력 후 버튼을 누르면 어떻게 동작하는지를 중점적으로 확인한다.
확인해보면 버튼 이후 어떻게 동작하는지 onClick() 함수 내부를 발견할 수 있다.
public void onClick(View view) {
String obj = MainActivity.this.inputFlagET.getText().toString();
if (obj.isEmpty()) {
Toast.makeText(MainActivity.this, "Please enter a flag", 0).show();
} else if (obj.length() <= 10) {
Toast.makeText(MainActivity.this, KnightCTF.flag, 0).show();
} else {
StringHandler stringHandler = new StringHandler();
if ((stringHandler.getS1(MainActivity.this) + "{" + stringHandler.getS3(MainActivity.this) + "_" + stringHandler.getS2(MainActivity.this) + "_" + stringHandler.getS4(MainActivity.this) + "}").equals(obj)) {
AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this, R.style.AlertDialog);
builder.setTitle((CharSequence) MainActivity.this.getResources().getString(R.string.s9));
builder.setMessage((CharSequence) MainActivity.this.getResources().getString(R.string.s10));
builder.show();
return;
}
AlertDialog.Builder builder2 = new AlertDialog.Builder(MainActivity.this, R.style.AlertDialog);
builder2.setTitle((CharSequence) MainActivity.this.getResources().getString(R.string.s11));
builder2.setMessage((CharSequence) MainActivity.this.getResources().getString(R.string.s12));
builder2.show();
}
}
여기서 확인할 수 있는 동작은 다음과 같다.
- 입력값이 없을 경우 “Please enter a flag”라는 문자열을 출력한다.
- 입력값의 길이가 10 이하일 경우 KnightCTF 클래스의 flag 값을 출력한다.
- 해당 값을 따라가 보면 "KCTF {its_a_fake_flag}”라는 값을 확인할 수 있으며, 이는 더미인 것을 알 수 있다.
- 입력값의 길이가 10보다 클 경우 StringHandler 클래스에 getS1(), getS3(), getS2(), getS4() 함수의 반한 값과 flag에 포함된 특수 기호(”{”, “_”, “}”)를 추가한 문자열과 입력값을 비교한다. 여기서 입력값과 비교하는 값이 flag임을 알 수 있다.
- 이후 값이 같거나 혹은 같지 않은 경우에 따라 다른 타이틀과, 메시지를 설정하여 출력해준다.
앱을 통해 확인해보면 분석한 결과 그대로 동작하는 것을 확인할 수 있다.
- 입력값이 없을 때 “Please enter a flag” 출력
- 입력값이 10 이하일 경우 가짜 flag 출력
- 입력값이 10 보다 크지만 비교하는 값과 다를 경우
StringHandler
MainActivity에서 호출하는 getS1(), getS2(), getS3(), getS4() 함수는 리소스에서 값을 가져온 후 이를 반전하여 문자열로 반환하는 함수임을 확인할 수 있다.
Exploit
문제는 getS1(), getS2(), getS3(), getS4() 함수에서 flag의 일부분을 그대로 반환하는 점을 이용하여 해결하였다.
Code
console.log("Script loaded successfully");
Java.perform(function x() {
console.log("java perform function");
var flag = "";
var str_hdr = Java.use('org.knightsquad.droidflag.StringHandler');
str_hdr.getS1.overload('android.content.Context').implementation = function (arg1) {
var x = this.getS1(arg1)
flag = x + "{";
return x;
}
str_hdr.getS2.overload('android.content.Context').implementation = function (arg1) {
var x = this.getS2(arg1)
flag += x;
flag += "_";
return x;
}
str_hdr.getS3.overload('android.content.Context').implementation = function (arg1) {
var x = this.getS3(arg1)
flag += x;
flag += "_";
return x;
}
str_hdr.getS4.overload('android.content.Context').implementation = function (arg1) {
var x = this.getS4(arg1)
flag += x;
flag += "}";
console.log("flag : " + flag);
return x;
}
});
코드는 간단하게 호출되는 getS1(), getS2(), getS3(), getS4() 함수를 후킹 하여 각각의 반환 값을 flag 변수에 저장한다. 이후 마지막으로 호출되는 getS4() 함수의 반환 값을 저장 후 출력하여 flag를 획득할 수 있다.
이후 스크립트를 실행한 후 아무 값을 10개 이상 입력하면(이는 getS1(), getS2(), ... 함수가 호출돼야 작성한 후킹 스크립트가 작동하기 때문이다.) 정상적으로 flag를 획득할 수 있다.
실제로 획득한 flag를 입력 후 인증해보면 축하한다는 메시지를 확인할 수 있다.
Reference
- KnightCTF 2022
https://ctftime.org/event/1545 - frida
https://frida.re/docs/javascript-api/