Frida-tutorial_1
모바일 보안 검색을 하던 중 Frida를 알게 되었다. 간단히 살펴보려고 했으나, Frida의 강력함을 느끼고 시간을 두고 깊이 있게 다뤄봐야겠다는 생각이 들었다. 실제 프로젝트 진행에 앞서 크랙미나 CTF 문제들을 통해서 활용법을 알아보자.
OWASP Crackme Level 1
adb install sg.vantagepoint.uncrackable1.apk
어플리케이션 설치 후 실행해보면 루팅 디바이스 탐지로 인해 곧바로 종료되는 것을 확인할 수 있다.
디컴파일러를 이용하여 MainActivity
의 onCreate
함수를 확인해보자.
protected void onCreate(Bundle bundle) {
if (sg.vantagepoint.a.c.a() || sg.vantagepoint.a.c.b() || sg.vantagepoint.a.c.c()) {
this.a("Root detected!");
}
if (sg.vantagepoint.a.b.a((Context)this.getApplicationContext())) {
this.a("App is debuggable!");
}
super.onCreate(bundle);
this.setContentView(2130903040);
}
if (sg.vantagepoint.a.c.a() || sg.vantagepoint.a.c.b() || sg.vantagepoint.a.c.c())
조건문을 이용하여 루팅 디바이스 탐지를, if (sg.vantagepoint.a.b.a((Context)this.getApplicationContext()))
조건문을 통해서 디버킹 탐지를 수행하고 있는 것을 확인할 수 있다.
이제 조건이 참인 경우 이동하는 a
함수를 살펴보자.
private void a(String string) {
AlertDialog alertDialog = new AlertDialog.Builder((Context)this).create();
alertDialog.setTitle((CharSequence)string);
alertDialog.setMessage((CharSequence)"This in unacceptable. The app is now going to exit.");
alertDialog.setButton(-3, (CharSequence)"OK", (DialogInterface.OnClickListener)new b(this));
alertDialog.setCancelable(false);
alertDialog.show();
}
탐지구문을 출력한 다이얼로그를 사용자에게 표시한 후 “OK” 버튼 선택 시 b
클래스에 있는
public void onClick(DialogInterface dialogInterface, int n) {
System.exit(0);
}
코드를 수행하여 어플리케이션이 종료된다. 먼저 어플리케이션 종료를 막아보자.
Frida를 이용해서 onClick
함수를 덮어쓰는 스크립트를 작성한다.
1 setImmediate(function() {
2 console.log("[*] Starting script");
3 Java.perform(function() {
4 bClass = Java.use("sg.vantagepoint.uncrackable1.b");
5 bClass.onClick.implementation = function(v) {
6 console.log("[*] onClick called");
7 }
8 console.log("[*] onClick handler modified");
9 })
10 })
setImmediate()
바로 다음에 실행 - 현재 이벤트 루프 주기 끝에 코드를 실행한다. 현재 이벤트 루프의 모든 I/O 작업 후 다음 이벤트 루프에 스케줄링 된 모든 타이머 이전에 실행된다.Java.perform()
자바를 다루기 위한 Frida 함수OnClickListener 인터페이스를 상속받아 구현한 onClick 메서드를 Frida가 삽입한 함수로 대체하였으므로
System.exit(0);
구문은 호출되지 않는다.
frida -U -l uncrackable1.js sg.vantagepoint.uncrackable1
어플리케이션을 구동해보면 루팅 탐지 다이얼로그 창의 “OK” 버튼을 선택하여도 종료되지 않는 것을 확인할 수 있다.
이제 다음 단계로 넘어가자.
public void verify(View view) {
String string = ((EditText)this.findViewById(2131230720)).getText().toString();
AlertDialog alertDialog = new AlertDialog.Builder((Context)this).create();
if (a.a((String)string)) {
alertDialog.setTitle((CharSequence)"Success!");
alertDialog.setMessage((CharSequence)"This is the correct secret.");
} else {
alertDialog.setTitle((CharSequence)"Nope...");
alertDialog.setMessage((CharSequence)"That's not it. Try again.");
}
alertDialog.setButton(-3, (CharSequence)"OK", (DialogInterface.OnClickListener)new c(this));
alertDialog.show();
}
a.a((String)string)
와 일치하는 문자열을 삽입하여야한다는 것을 verify
함수에서 확인할 수 있다.
public static boolean a(String string) {
byte[] arrby = Base64.decode((String)"5UJiFctbmgbDoLXmpL12mkno8HT4Lv8dlat8FxR2GOc=", (int)0);
byte[] arrby2 = new byte[]{};
try {
byte[] arrby3;
arrby2 = arrby3 = sg.vantagepoint.a.a.a((byte[])a.b((String)"8d127684cbc37c17616d806cf50473cc"), (byte[])arrby);
}
catch (Exception var3_4) {
Log.d((String)"CodeCheck", (String)("AES error:" + var3_4.getMessage()));
}
if (string.equals(new String(arrby2))) {
return true;
}
return false;
}
arrby2
를 만들기 위해 호출하는 sg.vantagepoint.a.a.a
를 살펴보자.
public static byte[] a(byte[] arrby, byte[] arrby2) {
SecretKeySpec secretKeySpec = new SecretKeySpec(arrby, "AES/ECB/PKCS7Padding");
Cipher cipher = Cipher.getInstance("AES");
cipher.init(2, secretKeySpec);
return cipher.doFinal(arrby2);
}
AES 암호화를 수행하는 함수라는 것을 알 수 있다. 이 함수도 위에서 진행한 것 처럼 덮어써서 문자열 검증 로직을 우회하여 보자. 암호화를 진행하기 전 전달 받은 인자 값을 콘솔에 출력하는 방법으로 원본 문자열을 확인한 후, 해당 문자열을 입력하는 것으로 진행한다.
1 setImmediate(function() {
2 console.log("[*] Starting script");
3 Java.perform(function() {
4 bClass = Java.use("sg.vantagepoint.uncrackable1.b");
5 bClass.onClick.implementation = function(v) {
6 console.log("[*] onClick called");
7 }
8 console.log("[*] onClick handler modified");
9
10 aaClass = Java.use("sg.vantagepoint.a.a");
11 aaClass.a.implementation = function(arg1, arg2) {
12 retval = this.a(arg1, arg2);
13 password = ''
14 for(i=0; i<retval.length; i++) {
15 password += String.fromCharCode(retval[i]);
16 }
17 console.log("[*] Decrypted: " + password);
18 return retval;
19 }
20 console.log("[*] sg.vantagepoint.a.a.a modified");
21 })
22 });
frida -U -l uncrackable1.js sg.vantagepoint.uncrackable1
어플리케이션을 실행한 후 임의의 문자열을 입력하여 Verify 버튼을 선택하면 콘솔 창에 암호화하기 전 원본 문자열이 출력되는 것을 확인할 수 있다. 해당 문자열을 입력 창에 입력하면 성공적으로 “Success” 다이얼로그를 확인할 수 있다.