OWASP - MASTG UnCrackable-LEVEL 3 (1)
작성자 - 5rocks_Ⅰ. UnCrackable
1. 개요
1. OWASTP MASTG(Mobile Application Security Testing Guide) 에서 역공학을 통해 모바일 진단을 실습이 가능한 앱
2. 아래의 페이지 처럼 AOS, IOS 단계별로 문제가 제공
Ⅱ. Uncracable AOS - LEVEL 3
1. 정적 분석
- Rooting 탐지
- 디버깅 탐지
- 무결성 검사
2. 동적 분석
- Frida 탐지
3. Frida 탐지 우회
- fgets 우회
4. Rooting 탐지 우회
- su 파일 탐지 우회
- /system/build.prop ⭢ test-key 탐지 우회
- 바이너리 파일 탐지 우회
Ⅲ. 정적 분석
1. MainActivity (1~53)
public class MainActivity extends AppCompatActivity {
private static final String TAG = "UnCrackable3";
static int tampered = 0;
private static final String xorkey = "pizzapizzapizzapizzapizz";
private CodeCheck check;
Map<String, Long> crc;
private native long baz();
private native void init(byte[] bArr);
/* JADX INFO: Access modifiers changed from: private */
public void showDialog(String str) {
AlertDialog create = new AlertDialog.Builder(this).create();
create.setTitle(str);
create.setMessage("This is unacceptable. The app is now going to exit.");
create.setButton(-3, "OK", new DialogInterface.OnClickListener() { // from class: sg.vantagepoint.uncrackable3.MainActivity.1
@Override // android.content.DialogInterface.OnClickListener
public void onClick(DialogInterface dialogInterface, int i) {
System.exit(0);
}
});
create.setCancelable(false);
create.show();
}
- tampered 변수명 ⭢ 무결성 탐지 추측
- xorkey 변수명 ⭢ xor 암/복호화에 사용하는 키 추측
- 사용자의 어떤 조작 있을 시 해당 OK 버튼 클릭 후 System.exit(0)을 호출 하여 앱 강제 종료
2. verifyLibs() Method (54 ~ 99)
private void verifyLibs() {
this.crc = new HashMap();
this.crc.put("armeabi-v7a", Long.valueOf(Long.parseLong(getResources().getString(owasp.mstg.uncrackable3.R.string.armeabi_v7a))));
this.crc.put("arm64-v8a", Long.valueOf(Long.parseLong(getResources().getString(owasp.mstg.uncrackable3.R.string.arm64_v8a))));
this.crc.put("x86", Long.valueOf(Long.parseLong(getResources().getString(owasp.mstg.uncrackable3.R.string.x86))));
this.crc.put("x86_64", Long.valueOf(Long.parseLong(getResources().getString(owasp.mstg.uncrackable3.R.string.x86_64))));
try {
ZipFile zipFile = new ZipFile(getPackageCodePath());
for (Map.Entry<String, Long> entry : this.crc.entrySet()) {
String str = "lib/" + entry.getKey() + "/libfoo.so";
ZipEntry entry2 = zipFile.getEntry(str);
Log.v(TAG, "CRC[" + str + "] = " + entry2.getCrc());
if (entry2.getCrc() != entry.getValue().longValue()) {
tampered = 31337;
Log.v(TAG, str + ": Invalid checksum = " + entry2.getCrc() + ", supposed to be " + entry.getValue());
}
}
ZipEntry entry3 = zipFile.getEntry("classes.dex");
Log.v(TAG, "CRC[classes.dex] = " + entry3.getCrc());
if (entry3.getCrc() != baz()) {
tampered = 31337;
Log.v(TAG, "classes.dex: crc = " + entry3.getCrc() + ", supposed to be " + baz());
}
} catch (IOException unused) {
Log.v(TAG, "Exception");
System.exit(0);
}
}
- 실행하는 모바일 기기의 아키텍처(ABI) 확인
getprop ro.product.cpu.abi 명령어를 통해 확인
armeabi-v7a: 32비트 ARM 프로세서용 (대부분의 구형 안드로이드 기기)
arm64-v8a: 64비트 ARM 프로세서용 (최신 안드로이드 기기)
x86: 32비트 인텔/AMD 프로세서용 (일부 태블릿, 에뮬레이터)
x86_64: 64비트 인텔/AMD 프로세서용 (일부 태블릿, 에뮬레이터)
- tampered 변수가 31337 값을 할당하면 앱 변조를 탐지
3. onCreate() Method (103 ~ 134)
public void onCreate(Bundle bundle) {
verifyLibs();
init(xorkey.getBytes());
new AsyncTask<Void, String, String>() { // from class: sg.vantagepoint.uncrackable3.MainActivity.2
/* JADX INFO: Access modifiers changed from: protected */
@Override // android.os.AsyncTask
public String doInBackground(Void... voidArr) {
while (!Debug.isDebuggerConnected()) {
SystemClock.sleep(100L);
}
return null;
}
/* JADX INFO: Access modifiers changed from: protected */
@Override // android.os.AsyncTask
public void onPostExecute(String str) {
MainActivity.this.showDialog("Debugger detected!");
System.exit(0);
}
}.execute(null, null, null);
if (RootDetection.checkRoot1() || RootDetection.checkRoot2() || RootDetection.checkRoot3() || IntegrityCheck.isDebuggable(getApplicationContext()) || tampered != 0) {
showDialog("Rooting or tampering detected.");
}
this.check = new CodeCheck();
super.onCreate(bundle);
setContentView(owasp.mstg.uncrackable3.R.layout.activity_main);
}
- onCreate: 안드로이드 액티비가 처음 실행 될 때 호출 되는 함수
- checkRoot1, checkRoot2, checkRoot3, isDebuggable 메서드를 통해 루팅 및 디버깅 탐지
3-1. RootDetection Class
package sg.vantagepoint.util;
import android.os.Build;
import java.io.File;
/* loaded from: classes.dex */
public class RootDetection {
public static boolean checkRoot1() {
for (String str : System.getenv("PATH").split(":")) {
if (new File(str, "su").exists()) {
return true;
}
}
return false;
}
public static boolean checkRoot2() {
String str = Build.TAGS;
return str != null && str.contains("test-keys");
}
public static boolean checkRoot3() {
for (String str : new String[]{"/system/app/Superuser.apk", "/system/xbin/daemonsu", "/system/etc/init.d/99SuperSUDaemon", "/system/bin/.ext/.su", "/system/etc/.has_su_daemon", "/system/etc/.installed_su_daemon", "/dev/com.koushikdutta.superuser.daemon/"}) {
if (new File(str).exists()) {
return true;
}
}
return false;
}
}
- checkRoot1메서드 에서는 루팅된 안드로이드 폰에서만 su 파일이 생성되어
해당 파일의 존재 여부 확인
- checkRoot2메서드 에서는 /system/build.prop 파일에 test-keys 가 있는지 확인
- cehckRoot3 메서드 에서는 루팅 기기에서 사용 되는 바이너리 파일 경로 확인
"/system/app/Superuser.apk", "/system/xbin/daemonsu", "/system/etc/init.d/99SuperSUDaemon", "/system/bin/.ext/.su", "/system/etc/.has_su_daemon", "/system/etc/.installed_su_daemon", "/dev/com.koushikdutta.superuser.daemon/"
3-2. IntegerityCheck Class
public class IntegrityCheck {
public static boolean isDebuggable(Context context) {
return (context.getApplicationContext().getApplicationInfo().flags & 2) != 0;
}
}
- IntegrityCheck 메서드의 getApplicationInfo().flags 값을 통해 플래그가 2(디버거 실행중)인지 확인
4. verify() 메서드 분석 (137 ~ )
public void verify(View view) {
String obj = ((EditText) findViewById(owasp.mstg.uncrackable3.R.id.edit_text)).getText().toString();
AlertDialog create = new AlertDialog.Builder(this).create();
if (this.check.check_code(obj)) {
create.setTitle("Success!");
create.setMessage("This is the correct secret.");
} else {
create.setTitle("Nope...");
create.setMessage("That's not it. Try again.");
}
create.setButton(-3, "OK", new DialogInterface.OnClickListener() { // from class: sg.vantagepoint.uncrackable3.MainActivity.3
@Override // android.content.DialogInterface.OnClickListener
public void onClick(DialogInterface dialogInterface, int i) {
dialogInterface.dismiss();
}
});
create.show();
}
- check_code()를 통해 해당 문제 코드
Ⅳ. 동적 분석 및 보안 로직 우회
1. Rooting Detection
- Rooting 된 폰을 통해 앱 접근 시 루팅 및 무결성 검증 메시지 확인
2. Frida Detection
- /lib/arm64/libfoo.so 네이티브 라이브러리 파일의 goodbye()함수가 호출되는 부분에서 문제 발생 확인
2-1. radare2 역공학 도구를 활용한 분석
- r2 [so 파일명]
- aaa 상세분석
- afl: 전체 함수 목록 조회 ⭢ goodbye 함수 확인
- pd @0x00003080 ⭢ goodbye 함수 디스어셈블
- goodbye 함수에서 /proc/self/maps에서 frida, xposed 문자열 탐지 하고 있음을 확인
2-2. Frida를 통한 fgets 문자열 출력
- Frida를 통해서 fgets 첫번째 매개변수 출력해 문자열을 frida, xposed 탐지로 인해 Frida가 종료되는지 확인
Interceptor.attach(Module.findExportByName(null, 'fgets'), {
onEnter: function (args) {
this.buffer = args[0];
},
onLeave: function (retval) {
if (!retval.isNull()) {
const content = Memory.readUtf8String(this.buffer);
console.log("fgets 버퍼 문자열 - " + content);
}
}
});
- Frida 문자열 읽은 이후 Frida가 종료되는것을 확인
fgets 버퍼 문자열 - 75191c7000-751938c000 rw-p 00000000 103:0f 2228329 /data/app/~~DmwAGGEkYU6cOddmjpNPhQ==/owasp.mstg.uncrackable3-yhjaAh8_uuEIcIBavSIJTg==/oat/arm64/base.vdex
fgets 버퍼 문자열 - 751938c000-751938d000 r--p 0003c000 103:0f 2228326 /data/app/~~DmwAGGEkYU6cOddmjpNPhQ==/owasp.mstg.uncrackable3-yhjaAh8_uuEIcIBavSIJTg==/oat/arm64/base.odex
fgets 버퍼 문자열 - 751938d000-751938e000 rw-p 0003d000 103:0f 2228326 /data/app/~~DmwAGGEkYU6cOddmjpNPhQ==/owasp.mstg.uncrackable3-yhjaAh8_uuEIcIBavSIJTg==/oat/arm64/base.odex
fgets 버퍼 문자열 - 7529058000-7529a52000 r--p 00000000 00:06 278932 /memfd:frida-agent-64.so (deleted)
Process crashed: Trace/BPT trap
***
*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
Build fingerprint: 'samsung/beyond1lteks/beyond1:12/SP1A.210812.016/G973NKSU7HVJ5:user/release-keys'
Revision: '26'
ABI: 'arm64'
Processor: '6'
Timestamp: 2025-04-27 15:51:33.213545384+0900
Process uptime: 1s
Cmdline: owasp.mstg.uncrackable3
pid: 14710, tid: 14744, name: tg.uncrackable3 >>> owasp.mstg.uncrackable3 <<<
uid: 10294
lr 0000007585cd7090 sp 0000007585ccfa00 pc 00000078980fa068 pst 0000000000000000
backtrace:
#00 pc 00000000000a0068 /apex/com.android.runtime/lib64/bionic/libc.so!libc.so (tgkill+8) (BuildId: 88b933fc529619f5e58bc6d5510017ee)
3. Frida Detection Bypass
- sp에 frida, xposed 문자열을 읽으면 buffer 값을 빈값으로 출력하여 프리다 탐지 우회
Interceptor.attach(Module.findExportByName(null, 'fgets'), {
onEnter: function (args) {
this.buffer = args[0];
},
onLeave: function (retval) {
if (!retval.isNull()) {
const content = Memory.readUtf8String(this.buffer);
if (content.indexOf("frida") !== -1 || content.indexOf("xposed") !== -1) {
Memory.writeUtf8String(this.buffer, "");
}
}
}
});
- frida 탐지 우회에 성공 하여 강제종료되지 않는것을 확인
4. Rooting Detection Bypass
- frida 탐지와 정적 분석을 통한 Rooting 탐지 우회 frida 코드를 작성
- checkRoot1의 환경변수를 통해 su 파일 탐지 하므로 환경변수 변조
- checkRoot2의 test-keys를 통해 탐지 하므로 mss-key로 변조
- checkRoot3의 해당 바이너리 파일 탐지 못하도록 변조
Java.perform(function() {
// Frida 탐지 우회
Interceptor.attach(Module.findExportByName(null, 'fgets'), {
onEnter: function (args) {
this.buffer = args[0];
},
onLeave: function (retval) {
if (!retval.isNull()) {
const content = Memory.readUtf8String(this.buffer);
if (content.indexOf("frida") !== -1 || content.indexOf("xposed") !== -1) {
Memory.writeUtf8String(this.buffer, "");
}
}
}
});
// Rooting 탐지 우회
var RootDetection = Java.use('sg.vantagepoint.util.RootDetection');
RootDetection.checkRoot1.implementation = function() {
var System = Java.use('java.lang.System');
var getenv = System.getenv.overload('java.lang.String');
getenv.implementation = function(name) {
if (name === "PATH") {
return "/mokpo/security/story";
}
return getenv.call(this, name);
};
return false;
};
RootDetection.checkRoot2.implementation = function() {
var Build = Java.use('android.os.Build');
Object.defineProperty(Build, 'TAGS', {
get: function() {
return 'mss-keys';
}
});
return false;
};
RootDetection.checkRoot3.implementation = function() {
var File = Java.use('java.io.File');
File.exists.implementation = function() {
var path = this.getAbsolutePath();
var bynaryFile = [
"Superuser.apk",
"daemonsu",
".su",
"99SuperSUDaemon",
".has_su_daemon",
".installed_su_daemon",
"/dev/com.koushikdutta.superuser.daemon/"
];
for (var i = 0; i < bynaryFile.length; i++) {
if (path.indexOf(bynaryFile[i]) !== -1) {
return false;
}
}
return this.exists();
};
return false;
};
});
Ⅴ. 우회 완료
Ⅵ. 추후 진단
- Secret String 찾기
'Security Tech' 카테고리의 다른 글
스택 구조 그리기 (0) | 2025.04.30 |
---|---|
V2X (Vehicle-to-Everything) 통신 (0) | 2025.02.28 |
API 취약점 진단 (0) | 2025.02.07 |
Cursor AI를 활용한 Macie 대체 서비스 개발기 (0) | 2025.01.31 |
CAN 통신 프로토콜 (0) | 2025.01.31 |