Proguard/R8으로 안드로이드 앱 난독화 및 최적화하기

Proguard/R8으로 안드로이드 앱 난독화 및 최적화하기

작성자
태인태인
카테고리
⚗️ 프로젝트
작성일
2024년 11월 05일
태그
Android
Google Play Console에 앱 번들을 올릴 때마다 마주했던 경고 메시지가 있다. 바로 소스코드를 난독화하라는 것이다. 도대체 난독화가 뭐길래 구글에서 권장하고 있는 것일까? 그리고 어떻게 할 수 있을까?

난독화(Obfuscation)

코드 난독화는 프로그래밍 언어로 작성된 코드에 대해 읽기 어렵게 만드는 작업이다. apk 파일을 디컴파일(decompile)하면 앱의 소스코드를 확인할 수 있다. 이를 이용해 코드를 임의로 수정한 후 빌드까지 하게 된다면 보안에 위협을 줄 수 있는 변조 앱을 만들 수 있는 것이다.
실제로 jadx와 같은 디컴파일 툴을 사용하면 아래와 같이 소스코드를 열어 볼 수 있다.
notion image
 

DEX

DEX(Dalvik Executable)는 Android 운영 체제에서 실행되는 바이트 코드 형식이다.
안드로이드에서 Kotlin 코드는 다음과 같은 순서로 실행된다.
  1. Kotlin 소스 코드는 Kotlin 컴파일러인 kotlinc에 의해 JVM 바이트 코드로 컴파일된다.
  1. Proguard나 R8등의 도구는 이 JVM 바이트 코드를 최적화한다.
  1. 최적화된 JVM 바이트 코드는 DEX 형식으로 변환된다.
  1. DEX 파일이 Android에서 Runtime Execution (런타임 실행)된다.

65K 메서드 제한 문제

DEX(Dalvik Executable) 파일은 16비트 참조 구조로 인해 메서드를 최대 65,536개까지만 담을 수 있다. 이는 DEX 파일 자체의 기술적 한계에서 비롯된 제약사항이다. 라이브러리 의존성이 늘어나고 앱 규모가 커질수록, 개발자들은 이른바 "65K 문제"와 자주 부딪히게 된다. 특히 서드파티 라이브러리를 적극적으로 활용하는 프로젝트에서는 이 한계치에 금방 도달하곤 한다.
이러한 제약을 극복하기 위한 해결책으로 멀티 DEX가 등장했다. 여러 개의 DEX 파일을 하나의 앱에 포함시키는 방식이다. 다만 이 방식에도 단점이 있다. 앱 실행 시 다수의 DEX 파일을 불러와야 하므로 런칭 타임이 길어질 수 있기 때문이다. Android 5.0부터는 ART(Android Runtime)가 도입되면서 상황이 나아졌다. ART는 AOT(Ahead-of-Time) 컴파일링을 지원하여, 앱 설치 단계에서 바이트코드를 네이티브 코드로 미리 변환한다. 이로 인해 멀티 DEX로 인한 성능 저하가 상당 부분 개선되었다. 하지만 여전히 멀티 DEX 사용은 앱의 용량과 실행 속도에 일정 부분 영향을 미치므로, 개발 시 이를 고려해야 한다.
 
이러한 문제점을 개선하고, 앱을 최적화하기 위해 Proguard나 R8과 같은 도구를 사용할 수 있다.

Proguard/R8

Android Gradle 플러그인 3.4.0 이상부터는 Proguard 대신 R8 컴파일러를 이용한다.
안드로이드에서는 코드 난독화 및 축소 등을 지원하는 라이브러리가 존재한다. Proguard와 R8 컴파일러가 대표적이다. app 단위 build.gradle 파일에 isMinifyEnabled = true 옵션을 추가하여 이 기능을 활성화할 수 있다.
buildTypes { release { isMinifyEnabled = true proguardFiles( getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro" ) } }
 
proguard-rules.pro 파일에는 Proguard(R8) 컴파일 시 구성을 지정할 수 있다. 몇 가지 속성을 소개하고 넘어가겠다.
  • keepattributes <SourceFile,LineNumberTable>: 소스 파일의 라인을 섞지 않음(Stack trace에서 정보를 얻기 어려운 문제 해결)
  • renamesourcefileattribute <SourceFile> :  파일 이름을 모두 "SourceFile" 문자열로 바꿈
  • keep class <PackageName> : 난독화를 진행하지 않는다. 보통 소스코드가 공개되어 있는 라이브러리에 적용한다.
 
Retrofit과 같은 일부 라이브러리는 난독화 및 축소 과정을 거쳤을 때 예기치 못한 오류가 발생할 수 있기 때문에 충분한 테스트를 거쳐 적절한 구성으로 진행해야 한다.
 
Proguard(R8) 옵션을 활성화하면 앱 빌드시 코드 난독화 뿐만 아니라 다음과 같은 작업들을 수행한다.
  • 코드 축소(또는 Tree Shaking): 앱 및 라이브러리 종속 항목에서 미사용 클래스, 필드, 메서드, 속성을 감지하여 삭제한다.
  • 리소스 축소: 앱 라이브러리 종속 항목의 미사용 리소스를 포함하여 패키징된 앱에서 사용하지 않는 리소스를 삭제한다.
  • 최적화: 코드를 검사하고 다시 작성하여 런타임을 개선함으로써 앱 DEX 파일*의 크기를 더 줄일 수 있다. 예를 들어 주어진 if/else 구문의 else {} 분기가 전혀 사용되지 않음을 R8에서 감지한 경우 R8이 else {} 분기 코드를 삭제하는 식이다.
  • 난독화: 클래스 및 멤버 이름을 단축해 코드를 DEX 파일 크기를 줄이고, 코드를 읽기 어렵게 만든다.
 
이렇게 난독화 및 코드 최적화를 진행하고 나면, 빌드한 apk파일을 디컴파일 툴을 이용해 열어보더라도 아래와 같이 알아볼 수 없는 형식으로 표시되는 것을 확인할 수 있다.
notion image
 
리소스 파일과 소스코드에 대한 축소 및 최적화도 진행되었기에 빌드된 apk 파일의 크기를 비교해보더라도 큰 폭으로 줄어드는 것을 확인할 수 있다.
 
Proguard나 R8을 통해 난독화 및 최적화를 진행한 앱 번들을 Play Console을 통해 배포할 때 네이티브 디버그 기호라는 것을 업로드해주는 것이 비정상 종료 분석 등에 용이하다.
notion image
위와 같이 App Bundle을 업로드하고 나면 에셋에 네이티브 디버그 기호를 업로드할 수 있는 버튼이 표시된다.
app/build/intermediates/merged_native_libs/프로젝트폴더/out/lib
하위에 있는 x86_64, armeabi-v7a 등과 같은 여러 폴더들을 하나의 ZIP 파일로 압축하여 업로드해주면 된다.

댓글

guest