
0. 용어 설명 및 선행 지식
@ build.prop 파일
디바이스의 시스템 속성이 정의된 파일로, Android OS가 부팅 시 참조하는 파일
기본적으로 해당 파일이 위치한 디렉토리는 읽기 전용으로 마운트되어 있기 때문에 변경이 불가능합니다
AVD( Android Virtual Device )의 경우, 변경이 불가하나 Nox의 경우 변경이 가능하였습니다
@ smali 코드
Android 앱은 위와 그림과 같이 최종적으로 dex 파일 형태로 컴파일 됩니다
CPU는 해당 CPU에 맞는 고유한 기계어가 있으며 이에 1대1로 대응하는것이 어셈블리어입니다
그렇기에 CPU 아키텍처마다 기계어, 어셈블리어는 달라집니다
또한 CPU는 Machine으로 불리기도하며 ART, JVM같은 Virtual Machine은 가상 CPU라고 볼 수 있습니다
이러한 가상 CPU 또한 기계어와 어셈블리어의 개념을 가집니다
즉, JVM이라는 가상 CPU에서 인식되는 기계어가 Java Byte Code이고 이에 대응하는 어셈블리어가 바로 Jasmin이며
ART라는 가상 CPU의 기계어는 dex이고 이에 대응하는 어셈블리어가 smali라는 것입니다
@ JPDA ( Java Platform Debugger Architecture )
Java 프로그램의 디버깅을 지원하기 위해 설계된 아키텍쳐
@ JDWP ( Java Debug Wire Protocol )
JVM에서 디버깅을 위해 사용되는 프로토콜이며, debugger와 JVM간의 통신을 관리한다
ART는 이러한 JDWP를 지원합니다
@ Android 앱은 두 가지 유형의 디버깅을 지원합니다
1. JDWP를 활용한 Java Runtime 수준의 디버깅
2. ptrace를 활용한 Native 수준( Linux/Unix )의 디버깅
참고 - https://mas.owasp.org/MASTG/techniques/android/MASTG-TECH-0031/
1. 취약점 설명
@ 취약점이 되기 위한 조건 : 아래 조건 중 하나라도 충족할 때
1. android:debuggable 속성이 true일 때
2. 디바이스의 build.prop 파일의 ro.debuggable 속성이 1일 때 ( 필자가 테스한 결과, 실패 )
Application Debuggable 취약점은 AndroidManifest.xml 파일에 정의된 android:debuggable 속성이
true일 때 디버깅이 가능한 취약점입니다 위의 그림과 같이 APKToolGUI 도구를 통해 앱을 디컴파일한 후,
AndroidManifest.xml 파일을 살펴보면 debuggable 속성 여부를 파악할 수 있습니다
android:debuggable속성이 false더라도 build.prop 파일의 ro.debuggable 속성이 1일 때도 디버깅이 가능하다고 합니다
하지만 필자가 Magisk의 MagiskHidePropsConf 모듈을 통해 ro.debuggable 속성을 1로 변경하여 테스트한 결과,
디버깅이 불가능하였습니다
( 어디까지나 에뮬레이터에서만 테스트를 진행하였으며 실제 디바이스에서는 테스트를 진행하지 못하였습니다 )
2. 공격
1. 공격 - 디컴파일 후, 속성 변경
해당 공격은 매우 간단합니다
APKToolGUI 도구를 통해 앱을 디컴파일 한 후, AndroidManifest.xml 파일의 debuggable 속성을 true로 변경합니다
그리고 다시 컴파일을 하면 끝입니다 하지만 해당 앱이 무결성 검증이 구현되어 있을 경우, 해당 공격은 성공할 수 없습니다
2. 공격 - jdb로 디버깅 수행
@ 필자의 경험을 바탕으로 jdb를 사용하기 전에 읽어볼 내용을 정리해보았습니다
1. jdb를 사용할 때는 원본 코드를 띄워놓고 같이 사용해야 활용도가 높습니다 ( JADX, JEB의 디컴파일 코드, 원본 코드 등 )
- print <클래스>.<변수>와 같은 jdb 명령어를 사용하기 위해서는 변수 이름을 알고 있어야 출력이 가능하기 때문입니다
- 또한 jdb의 명령어만으로는 전역 변수 파악이 불가능한 점도 있습니다
2. 직관적인 구조 파악이 어렵습니다 ( ? )
위와 같은 구조일 때는 클래스의 메소드를 나열하는 명령어, 한 줄씩 실행 등으로는 실질적인 흐름 파악이 어렵습니다
3. jdb를 통한 디버깅의 단점
- 사용자 정의 클래스 / 메소드만 출력하는 방법이 없다
- 첫 시작 Activity 디버깅이 불가능하다
jdb가 연결된 상태에서 classes, methods 명령어를 각각 입력해보면 정말 많은 클래스와 메소드가 나열됩니다
이러한 이유는 Android 라이브러리의 클래스 / 메소드들도 함께 나열되기 때문입니다
또한 많은 앱들이 첫 시작 Activity에서 보안 위협을 감지합니다 이를 우회하기위해서는 첫 Activity 실행전에 디버거가 붙어야하는데
jdb는 이게 불가합니다 즉, jdb로는 첫 Activity의 보안 감지 우회가 불가합니다 Frida는 이게 가능하지만요
jdb 연결을 위한 준비 과정은 위의 첫 번째 그림과 같습니다
가장 먼저 디버깅 할 앱을 실행한 후, adb jdwp를 입력하면 debuggable 속성이 true인 앱의 PID를 출력합니다
adb forward를 통해 해당 앱과 로컬 포트끼리 포트포워딩을 진행합니다 ( 추가로, forward 관련 명령어들은 위와 같습니다 )
다음으로 jdb를 연결합니다
연결 방법은 connect를 사용하는 방법과 attach를 사용하는 방법이 있는데 둘 중에 작동하는걸로 사용하면 됩니다
주의사항으로는 앱의 Wait for debugger를 활성화 한 상태에서는 jdb 붙는게 실패합니다 꼭 비활성화 해야 합니다
jdb로 디버깅하는 방법은 위의 그림과 같습니다 기본적인 명령어 사용법만 나열하였습니다
- methods <클래스이름> : 클래스내의 메소드를 나열하면 명령어
- stop in <패키지이름>.<메소드이름> : 메소드에 Break Point 지정하는 명령어
- locals : 현재 메소드의 지역 변수 나열하는 명령어
- next : Break Point Hit 상태에서 1줄씩 코드를 실행하는 명령어
jdb로 지역 변수값을 변경하는 방법은 위의 그림과 같습니다
- set <변수명> = <값> 을 통해 변수값을 변경할 수 있습니다
3. 공격 - jadx로 디버깅 수행
@ jadx는 디컴파일러이지만 디버깅 기능 또한 지원합니다
필자가 직접 테스한 결과, 하드웨어 디바이스를 통해서만 정상적인 디버깅이 가능하였습니다
AVD 에뮬레이터의 경우, jadx 연결은 성공하나 한 줄씩 실행, 레지스터값 확인 등의 기능이 정상 작동하지 않았습니다
Nox는 테스트해보지 못하였습니다
@ 필자의 경우, Mac 환경에서 Parallels Desktop Pro를 구동한 후 Galaxy Note 9을 연결하여 수행하였습니다
Parallels에서 Windows를 구동시킨 후, 디바이스를 Mac에 연결합니다
이 때 Mac과 Parallels 둘 중 어디에 인식시킬지 선택창이 뜨는데 Parallels를 선택합니다
해당 섹션에서는 jadx를 통한 디버깅 수행과 smali 코드 변조까지 수행하겠습니다 ( 그림 참고 )
디버깅을 시작하면 break point를 따로 설정하지 않아도 자동으로 엔트리 포인트부터 시작합니다
break point 설정 단축키는 F2이며 Java 소스 코드가 아닌 smali 코드 수준에서만 설정 가능합니다
0. 디버깅 할 앱을 디바이스에 다운로드 합니다
1. jadx로 디버깅을 수행하기 위해 디바이스의 USB 디버깅과 Wait For debugger를 활성화 합니다
2. jadx를 실행하고 1번의 동일한 앱을 jadx로 불러옵니다 ( 반드시 동일해야 합니다 )
3. 디바이스에서 앱을 실행한 후, jadx의 벌레 아이콘 클릭 - 앱을 더블클릭하여 디버깅을 실행합니다
4. 시작 버튼을 클릭하면 앱 실행과 동시에 디버깅이 시작됩니다
다음으로 smali 코드 변조를 수행하겠습니다
1. 디버깅을 수행하면 앱은 SplashActivity부터 시작 하며, 위와 같이 isRooted() 메소드들을 실행합니다
- 메소드 실행 결과가 True일 경우, 0057로 점프하는 구조입니다
2. 그 다음 코드들을 살펴보면 nativeDetectAll() 메소드 또한 실행하는 것을 알 수 있습니다
- 해당 메소드 또한 실행 결과가 True일 경우, 0057로 점프하며 False일 경우에는 0047로 점프합니다
- 0057 구간을 살펴보면 alertDialog를 띄운 후, return하며 실행이 종료되는 것을 알 수 있습니다
- 이를 통해 감지 메소드에 감지될 경우, 0057로 점프하며 종료되는 것을 확인할 수 있습니다
3. APKToolGUI를 통해 디컴파일 후, SplashActivity의 스마일 코드를 체크합니다
- smali 코드 수준에서는 nativeDetectAll()의 리턴값이 False면 cond_0으로, True면 cond_1로 점프합니다
- 공격자는 cond_0으로만 점프하게끔 만들면 됩니다 때문에 리턴값이 True더라도 cond_0로 점프하게끔 변조하겠습니다
4. 위의 그림과 같이 True일 때 cond_0으로 점프하게끔 변조합니다
- 변조한 후, 다시 컴파일하여 앱을 실행해보면 정상적으로 우회가 되는것을 확인할 수 있습니다
3. 대응 방안
1. debuggable 속성을 false로 설정
위의 그림과 같이 debuggable 속성을 false로 설정하면 adb jdwp 명령어를 수행하여도 PID가 노출되지 않습니다 ( 그림 1 )
또한 디버그 모드로 수행하는 명령어인 am start -D를 입력하더라도 디버그 모드로 수행되지 않습니다 ( 그림 2 )
2. TracerPID 탐지
jadx나 jdb같은 디버거가 연결된 상태일 경우, 해당 앱의 TracerPID를 살펴보면 Debugger의 PID가 노출됩니다 ( 그림 참고 )
이러한 점을 이용하여 TracerPID가 0이 아닐 경우, True를 반환하는 메소드를 구현하면 이에 대응할 수 있습니다
public static boolean isDebuggerAttached() {
String temp1 = null;
String temp2 = null;
try {
BufferedReader bufferedReader = new BufferedReader(new FileReader("/proc/self/status"));
Iterator<String> iterator = bufferedReader.lines().iterator();
while ( iterator.hasNext() ) {
temp1 = iterator.next();
temp2 = temp1.startsWith("TracerPid") ? temp1.split(":")[1].trim() : null;
if ( temp2 != null && !temp2.equals("0") ) {
Log.i(TAG, "isDebuggerAttached() : " + temp1);
return true;
}
}
} catch (Exception e) {
e.printStackTrace();
return false;
}
Log.i(TAG, "isDebuggerAttached() : false");
return false;
}
위와 같은 코드를 구현한 후, 디버거를 연결했을 때와 연결하지 않았을 때를 비교해보면 위의 그림과 같습니다
3. 무결성 검증 구현 ( 생략 )
글 상단에서 앱을 디컴파일하여 속성을 변경하는 공격을 언급하였습니다
해당 공격에 대응하기 위해서는 앱의 무결성을 검증하는 로직을 구현하여야 합니다
'Project > RedAlienShop' 카테고리의 다른 글
6. RedAlienshop - Frida Detection and Bypass 취약점 (2) | 2025.04.17 |
---|---|
5. RedAlienshop - Developer Option Detection and Bypass 취약점 (1) | 2025.04.15 |
4. RedAlienshop - Emulator Detection and Bypass 취약점 (0) | 2025.04.15 |
3. RedAlienshop - Root Detection and Bypass 취약점 (0) | 2025.04.10 |
2. RedAlienshop - Allow Data Backup 취약점 (0) | 2025.04.03 |
IT / Android
포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!