모바일 앱 보안을 강화하려면 어떻게 해야 할까요
공격자의 입장에서 안드로이드 앱이 얼마나 취약한지 확인해보기 위해 CrackMe를 시도해보았습니다
시작하기에 앞서, 글쓴이의 배경 지식은 다음과 같습니다:
- 안드로이드 앱 제작 및 배포 경험
- 정적 분석과 동적 분석이 무엇인가 하는 간략한 개념
- 리버싱 핵심 원리를 읽다가 포기함
- dreamhack 리버스 엔지니어링 코스 학습
- 기초적인 수준의 어셈블리어
- 기초적인 수준의 jadx, frida, ghidra 사용법
배경 지식이 기초적인 수준이기 때문에 내용에 오류가 있을 수 있습니다
CrackMe 프로그램은 리버스 엔지니어링 실력을 시험해 볼 수 있는 프로그램으로, 보통 다양한 탐지 및 디버깅 방지 기법을 우회하여 키 값을 찾아내는 것을 목적으로 합니다
OWASP(The Open Web Application Security Project)의 MASTG(Mobile Application Security Testing Guide)에서 제공하는 CrackMe를 풀어보려 합니다
https://github.com/OWASP/owasp-mastg
총 다섯 개의 안드로이드 CrackMe 앱을 제공하고 있습니다
https://mas.owasp.org/crackmes/
UnCrackable-Level1을 에뮬레이터에 설치하고 실행해보니, 루팅된 디바이스로 분류되어 앱이 종료됩니다
먼저 루팅 탐지부터 우회해야 하겠네요 jadx를 사용하여 정적 분석을 시도합니다
jadx 기본 설정의 경우 너무 긴 함수의 경우 디컴파일하지 않지만, --show-bad-code
플래그를 붙이는 경우 부정확하게나마 보여줍니다
함수를 길게 작성하는 것도 도움이 되려나요
jadx-gui UnCrackable-Level1.apk --show-bad-code
네비게이션 > 클래스 검색(Cmd-N)
메뉴에서 Activity를 검색합니다
간단한 앱의 경우 소스코드 트리를 뒤져봐도 좋지만 복잡도가 높은 앱의 경우 검색을 하는 편이 원하는 결과를 빠르게 얻을 수 있습니다
현재 앱에는 MainActivity 밖에 없네요
코드를 확인해봅니다
onCreate()
를 보면 다양한 함수로 루팅 여부를 탐지하고 이후 디버깅 가능 여부를 탐지하고 있네요
루팅 체크 부분을 자세히 확인해보겠습니다
a()
메소드는 환경변수에 su 파일이 있는지 확인하는 메소드입니다b()
메소드는 os의 빌드 태그에 "test-keys"가 포함되어 있는지 확인하는 메소드이며,c()
메소드는 시스템에 루팅과 관련된 파일이 존재하는지 확인하는 메소드네요
파일 체크의 경우 해당하는 파일의 이름만 변경해도 무력화되니, 의미있는 방식인지는 잘 모르겠습니다
루팅 탐지를 우회하는 frida 스크립트를 작성합니다
// unCrackable1.js
Java.perform(() => {
const c = Java.use("sg.vantagepoint.a.c");
c["a"].implementation = function () {
console.log(`c.a(): ${this.a()}`);
return false;
};
c["b"].implementation = function () {
console.log(`c.b(): ${this.b()}`);
return false;
};
c["c"].implementation = function () {
console.log(`c.c(): ${this.c()}`);
return false;
};
});
스크립트를 실행합니다
frida-ps -Uai # 설치된 앱 Identifier 확인
frida -U -f owasp.mstg.uncrackable1 -l unCrackable1.js # USB 연결된 디바이스에서 UnCrackable 1 앱 실행 및 스크립트 로딩
앱이 종료되지 않습니다
이상하게도 디버깅 가능 여부는 탐지되지 않네요
코드를 확인해보니 ApplicationInfo의 플래그에 FLAG_DEBUGGABLE이 포함되어 있는지 확인하는 코드였습니다
EditText에 텍스트를 입력하고 VERIFY를 눌러보면 verify()
메소드로 입력 값이 키 값과 동일한지 체크하는 모습을 볼 수 있습니다
우리가 관심있는 부분은 정확한 키 값이니 검증 과정을 우회하는 건 의미가 없겠네요
검증 함수(a.a(obj)
)를 따라가봅니다
b("8d127684cbc37c17616d806cf50473cc")
를 시크릿 키로 Base64.decode("5UJiFctbmgbDoLXmpL12mkno8HT4Lv8dlat8FxR2GOc=", 0)
AES 복호화를 적용하네요
복호화 후 결과 값을 String으로 출력하면 키 값을 알 수 있겠죠?
이전에 작성한 루팅 탐지 우회 스크립트를 수정해봅니다
Java.perform(function () {
passRootDetection();
logVerify();
});
function passRootDetection() {
const c = Java.use("sg.vantagepoint.a.c");
c["a"].implementation = function () {
return false;
};
c["b"].implementation = function () {
return false;
};
c["c"].implementation = function () {
return false;
};
}
function logVerify() {
const stringClass = Java.use("java.lang.String");
const a = Java.use("sg.vantagepoint.a.a");
a["a"].implementation = function (bArr, bArr2) {
const result = this["a"](bArr, bArr2);
console.log(`a.a result: ${stringClass.$new(result)}`);
return result;
};
}
[Android Emulator 5554::owasp.mstg.uncrackable1 ]-> a.a result: I want to believe
키 값은 I want to believe
였네요
동일한 값을 입력 후 VERIFY 버튼을 누르니 성공 팝업이 표시됩니다
공격자의 입장을 체험해보고 나니 jvm 환경에서 작성된 그 어떤 것도 믿을 수 없을 것 같습니다..
너무 쉽게 분석되고 우회가 가능하네요..
난독화, 패킹 등의 방식을 더 알아보아야 하겠습니다
'Android' 카테고리의 다른 글
AOSP 빌드 오류 #1 (0) | 2024.02.15 |
---|---|
[OWASP-MSTG] UnCrackable-Level2.apk (3) | 2023.07.02 |
신입 안드로이드 개발자 회고 & 내년도 목표 설정하기 (0) | 2022.12.04 |
안드로이드 에뮬레이터 저장(Snapshot)이 안 될 때 (0) | 2022.08.25 |
기다려 for Kakao 개인정보 처리 방침 (0) | 2022.08.20 |