1. 코드 난독화 필요성
안드로이드 앱을 개발 후 APK 실행 파일을 추출할 수 있다. 또한 추출한 APK를 바탕으로 앱의 소스코드가 분석 가능하도록 디컴파일 할 수 있다.
이는 테스트 및 리버스 엔지니어링 목적에는 도움이 되지만, 공격자가 앱의 코드와 동작을 분석하여 취약성을 발견하거나 중요한 데이터를 훔치는 등의 보안의 측면에서는 위험하다.
따라서 소스 코드의 분석을 어렵게 만들기 위해 코드 난독화를 적용한다.
2. Proguard 란?
Proguard는 코드를 난독화 및 최적화를 해주는 무료 오픈소스 툴이다.
즉, Proguard는 컴파일된 앱 패키지의 코드를 난독화하여 다른 사람이 해당 패키지를 디컴파일 했을 때 해독하기 어렵게 만드는 일종의 보안 장치이다.
이전에 안드로이드 AGP (Android Gradle Plugin) 3.4.0 미만인 경우에 프로가드를 활용해 소스코드 난독화를 진행했다. 하지만 AGP 3.4.0 이상부터는 Proguard 대신 R8을 기본으로 코드 최적화 및 난독화를 진행한다.
3. R8 이란?
R8은 Google에서 개발한 Android 앱의 난독화, 축소, 및 최적화 도구이다
R8 이전에는 ProGuard를 사용하였고, R8은 Proguard의 후속 버전인 Android의 도구이다.
R8가 제공하는 주요 기능 및 특성은 아래와 같다.
- 난독화
- 코드 축소
- 최적화
- DEX 바이트 코드 생성
- Proguard 규칙 지원
4. R8 vs Proguard 비교
[그림 1] proguard
[그림 2] R8
- Proguard는 java 바이트 코드를 DEX로 변환할 때 java 바이트 코드를 최적화 한 뒤 dx, d8을 활용하여 jvm 바이트 코드를 변환한다.
- 하지만 사진과 같이 R8은 java 바이트 코드를 최적화 하면서 동시에 바로 DEX로 바꾸기 때문에 Proguard보다 더 빠르고, 경량화 성능도 더 좋다.
- 하위 호환성을 갖추고 있어 proguard-rules 파일을 R8이 그대로 이용할 수 있다.
5. 안드로이드 코드 난독화 적용
[그림 3] build.gradle 파일 난독화 적용
- build.gradle 파일에서 buildTypes 중 원하는 타입의 isMinifyEnabled 속성의 값을 true로 설정하면 된다.
- true = 난독화 적용, false = 난독화 미적용을 뜻한다.
- [그림 3] 에서는 debug, release 두 버전 모두에 난독화를 true로 설정한 경우이다.
5-1. 샘플 앱 구성
- 샘플 앱은 MainActivity에 버튼, 이미지, 텍스트가 존재한다.
- 내부메일, 전자결재, 오늘근무 버튼을 클릭할 시 각각 새로운 페이지로 전환된다.
- 서버 API 연결 버튼을 클릭할 시 API를 바탕으로 받아온 데이터가 뷰에 띄워진다.
- API 는 https://reqres.in/ 를 활용하였다.
5-2. 이렇게 샘플 앱을 구성한 이유는?
- 소스코드 난독화라는 것은 결국 어떤 서비스의 디컴파일 과정에서 생기는 보안 취약점을 보완하기 위해서 필요하다고 생각했다.
- 따라서 보통의 서비스들이 필수적으로 사용하는 retrofit 서버 통신, 받아온 데이터를 가공해서 뷰에 띄우는 등의 기능을 축약하여 샘플 앱에 적용해보았다.
6. R8 적용 비교
6-1. 난독화 측면
java decompiler 를 통해 apk를 추출하고 디컴파일을 진행했다.
[그림 4] 난독화 적용 전
[그림 5] 난독화 적용 후
난독화 적용 시 기존 코드의 클래스 이름과 변수 이름이 모두 숨겨진다.
6-2. 앱 경량화 측면
R8은 난독화 뿐만 아니라 앱 경량화에도 좋다.
[그림 7]과 [그림 8]을 비교했을 때 경량화한 경우 2개의 dex 파일이 1개로 축소된다.
또한 [그림 6]처럼 용량 측면에서도 약 2배 이상 용량이 축소됨을 볼 수 있다.
이처럼 R8은 난독화뿐만 아니라 코드 최적화, 축소 과정을 통해 앱 경량화를 제공한다.
7. Proguard-rules .pro 옵션
Proguard-rules 를 활용하여 난독화에 대한 예외처리를 할 수 있다.
해당 파일에 작성할 수 있는 옵션은 아래와 같다.
- keepattributes SourceFile,LineNumberTable : 소스파일의 라인을 섞지 않는 옵션
- renamesourcefileattribute SourceFile: 소스 파일 변수명 바꾸는 옵션
- keep class 라이브러리패키지명.**{*}; : 난독화 할 필요가 없는 라이브러리 처리 옵션
- ignorewarnings : warning 무시 옵션
- dontwarn 패키지명.** : 지정한 패키지의 warning 무시 옵션
- #-dontoptimize: #없애면 난독화 하지 않는다. #-dontobfuscate: #없애면 최적화 하지 않는다.
- #-keepresourcexmlattributenames manifest/**: #없애면 manifest 난독화 하지 않는다.
샘플앱에서는 서버통신을 위한 retrofit, Okhttp 를 사용하므로,
해당 라이브러리가 난독화로 인해 오류가 발생하지 않도록 예외처리를 하였다.
- 샘플앱의 proguard-rules .pro 내용
##---------------Begin: proguard configuration for Gson ----------
# Gson uses generic type information stored in a class file when working with fields. Proguard
# removes such information by default, so configure it to keep all of it.
-keepattributes Signature
# For using GSON @Expose annotation
-keepattributes *Annotation*
# Gson specific classes
-dontwarn sun.misc.**
#-keep class com.google.gson.stream.** { *; }
# Application classes that will be serialized/deserialized over Gson
-keep class com.google.gson.examples.android.model.** { <fields>; }
# Prevent proguard from stripping interface information from TypeAdapter, TypeAdapterFactory,
# JsonSerializer, JsonDeserializer instances (so they can be used in @JsonAdapter)
-keep class * extends com.google.gson.TypeAdapter
-keep class * implements com.google.gson.TypeAdapterFactory
-keep class * implements com.google.gson.JsonSerializer
-keep class * implements com.google.gson.JsonDeserializer
# Prevent R8 from leaving Data object members always null
-keepclassmembers,allowobfuscation class * {
@com.google.gson.annotations.SerializedName <fields>;
}
# Retain generic signatures of TypeToken and its subclasses with R8 version 3.0 and higher.
-keep,allowobfuscation,allowshrinking class com.google.gson.reflect.TypeToken
-keep,allowobfuscation,allowshrinking class * extends com.google.gson.reflect.TypeToken
##---------------End: proguard configuration for Gson ----------
# Retrofit does reflection on generic parameters. InnerClasses is required to use Signature and
# EnclosingMethod is required to use InnerClasses.
-keepattributes Signature, InnerClasses, EnclosingMethod
# Retrofit does reflection on method and parameter annotations.
-keepattributes RuntimeVisibleAnnotations, RuntimeVisibleParameterAnnotations
# Keep annotation default values (e.g., retrofit2.http.Field.encoded).
-keepattributes AnnotationDefault
# Retain service method parameters when optimizing.
-keepclassmembers,allowshrinking,allowobfuscation interface * {
@retrofit2.http.* <methods>;
}
# Ignore annotation used for build tooling.
-dontwarn org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement
# Ignore JSR 305 annotations for embedding nullability information.
-dontwarn javax.annotation.**
# Guarded by a NoClassDefFoundError try/catch and only used when on the classpath.
-dontwarn kotlin.Unit
# Top-level functions that can only be used by Kotlin.
-dontwarn retrofit2.KotlinExtensions
-dontwarn retrofit2.KotlinExtensions$*
# With R8 full mode, it sees no subtypes of Retrofit interfaces since they are created with a Proxy
# and replaces all potential values with null. Explicitly keeping the interfaces prevents this.
-if interface * { @retrofit2.http.* <methods>; }
-keep,allowobfuscation interface <1>
# Keep inherited services.
-if interface * { @retrofit2.http.* <methods>; }
-keep,allowobfuscation interface * extends <1>
# With R8 full mode generic signatures are stripped for classes that are not
# kept. Suspend functions are wrapped in continuations where the type argument
# is used.
-keep,allowobfuscation,allowshrinking class kotlin.coroutines.Continuation
# R8 full mode strips generic signatures from return types if not kept.
-if interface * { @retrofit2.http.* public *** *(...); }
-keep,allowoptimization,allowshrinking,allowobfuscation class <3>
# With R8 full mode generic signatures are stripped for classes that are not kept.
-keep,allowobfuscation,allowshrinking class retrofit2.Response
-dontwarn okhttp3.internal.platform.** -dontwarn org.conscrypt.** -dontwarn org.bouncycastle.** -dontwarn org.openjsse.**
-dontwarn com.google.protobuf.java_com_google_android_gmscore_sdk_target_granule__proguard_group_gtm_N1281923064GeneratedExtensionRegistryLite**
-keep,allowobfuscation,allowshrinking class kotlin.coroutines.Continuation
'Android > 공부' 카테고리의 다른 글
[Android/공부] 안드로이드 라이브러리 / AAR 만들기 (0) | 2024.07.17 |
---|---|
[Android/공부] JNI 활용 안드로이드 디버깅 / USB 탐지 (JAVA) (0) | 2024.07.11 |
[Android] Flow란? LiveData와 Flow 비교 (0) | 2023.11.13 |
[Android/코루틴] 2.코루틴의 기본요소들을 알아보자 (0) | 2023.09.21 |
[Android/코루틴] 1. 코루틴은 무엇이고 왜 쓰는가? (0) | 2023.09.21 |