앱 정보
앱을 실행시키면 이전 Uncrackable 1과 똑같이 루팅 감지를 하고 OK 버튼을 누르면 종료되는 것을 확인할 수 있다.
루팅 감지는 이전 단계와 똑같이 우회할 수 있으니 우회를 한 뒤 확인해보면 사용자 입력을 받을 수 있는 공간 하나와 VERFIY 버튼 하나로 구성되어 있음을 알 수 있다. 또한 이전과 똑같이 무작위 데이터 입력 후 버튼을 눌러보면 "Nope..."이라는 창을 확인할 수 있다.
앱 분석
verify()
public void verify(View view) {
String str;
String obj = ((EditText) findViewById(R.id.edit_text)).getText().toString();
AlertDialog create = new AlertDialog.Builder(this).create();
if (this.m.a(obj)) {
create.setTitle("Success!");
str = "This is the correct secret.";
} else {
create.setTitle("Nope...");
str = "That's not it. Try again.";
}
create.setMessage(str);
create.setButton(-3, "OK", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialogInterface, int i) {
dialogInterface.dismiss();
}
});
create.show();
}
verify() 함수를 분석하면 다음과 같은 정보를 알 수 있다.
- 사용자가 입력한 값을 obj 변수에 저장한 후 CodeCheck 클래스 a() 함수에 인자로 넘긴다.
- 만약 a() 함수의 반환 값이 true라면 "Success!"라는 타이틀을 설정하고 str 변수에 "This is the correct secret."를 저장한다. 그렇지 않다면 "Nope..."라는 타이틀로 설정한 후 str 변수에 "That's not it. Try again."를 저장한다.
- 이전에 설정한 타이틀과 문자열로 경고창을 띄운다.
CodeCheck
public class CodeCheck {
private native boolean bar(byte[] bArr);
public boolean a(String str) {
return bar(str.getBytes());
}
}
CodeCheck 클래스를 분석하면 다음과 같은 정보를 알 수 있다.
- a() 함수에서 인자로 넘어온 값을 바이트 형식으로 변환하여 native 함수인 bar() 함수에 인자로 넘겨준 후 해당 함수의 반환 값을 그대로 반환한다.
최종적으로 인증을 통과하기 위해서 native 함수를 분석해야 한다는 것을 알 수 있으므로 ida를 통해 라이브러리 파일을 분석한다.
실제 \lib\arm64-v8a\libfoo.so 파일을 확인해보면 다음과 같은 함수를 확인할 수 있다.
bool __fastcall Java_sg_vantagepoint_uncrackable2_CodeCheck_bar(__int64 a1, __int64 a2, __int64 a3)
{
_BOOL8 result; // x0
const char *v6; // x21
char v7[24]; // [xsp+0h] [xbp-40h] BYREF
__int64 v8; // [xsp+18h] [xbp-28h]
result = 0LL;
v8 = *(_ReadStatusReg(ARM64_SYSREG(3, 3, 13, 0, 2)) + 40);
if ( byte_1300C == 1 )
{
strcpy(v7, "Thanks for all the fish");
v6 = (*(*a1 + 1472LL))(a1, a3, 0LL);
result = (*(*a1 + 1368LL))(a1, a3) == 23 && !strncmp(v6, v7, 0x17u);
}
return result;
}
bar() 함수를 확인해보면 다음과 같은 정보를 알 수 있다.
- v7 변수에 "Thanks for all the fish" 문자열을 strcpy() 함수를 통해 저장한다.
- 변수 a1, a3를 사용하여 무언가 동작 후 v6 변수에 이를 저장한다.
- 마지막으로 v6과 v7이 같으며, 변수 a1, a3와 어떤 동작을 한 반환 값이 23(아마 사용자 입력 값 길이가 23인지 확인하는 부분으로 예측)이면 result 변수에 1을 저장하여 이를 반환한다.
Exploit
1. Hard Coding
네이티브 함수인 bar() 내부 strcpy() 함수 인자 값에 하드 코딩되어 저장된 문자열을 사용하여 인증한다.
2. Hooking
후킹을 통한 우회는 bar() 함수를 후킹 하여 리턴 값을 true로 고정하여 인증하는 방법이다.
Exploit Code
console.log("Script loaded successfully");
Java.perform(function x() {
console.log("java perform function");
var sys = Java.use('java.lang.System');
sys.exit.implementation = function () {
console.log("success");
}
});
Interceptor.attach(
Module.findExportByName("libfoo.so", "Java_sg_vantagepoint_uncrackable2_CodeCheck_bar"), {
onLeave: function(retl) {
retl.replace(1);
}
}
);
코드를 확인해보면 다음과 같다.
- 위 코드는 이전 Uncrackable1에서 사용한 루팅 탐지 코드와 같다.
- 이후 Interceptor.attach()를 통해 네이티브 함수를 후킹 하는데 이때 라이브러리와 함수명을 사용하여 해당 함수를 찾을 수 있도록 한다.
- 마지막으로 onLeave로 종료될 때 리턴 값으로 1을 반환하도록 하면 우회를 할 수 있다.
실제 스크립트를 실행한 뒤 무작위 값을 입력 후 VERIFY 버튼을 누르면 다음과 같이 Success!라는 창을 확인할 수 있다.
Reference
'Android > Uncrackable' 카테고리의 다른 글
Uncrackable 1 - Secret String (0) | 2021.11.01 |
---|---|
Uncrackable 1 - Root Detection (0) | 2021.11.01 |