Android정책

안드로이드 [Kotlin] - 프로가드(Proguard) 설정하기

ziziDev 2024. 10. 29. 09:19
반응형

 

프로가드(Proguard) 설정하기

 

회사에서 보안점검으로 인하여 수정사항이 나왔다 그래서 수정진행하기전 몇 개의 게시글을 읽어보았다

proguard 경우는 외부 라이브러리를 사용할 때 어떤걸 사용하느냐에 따라서 하나씩 적용하고 확인해야하는것 같아 우선 해보는걸루!!

 

 

[Android/공부] 안드로이드 소스 코드 난독화 R8 / Proguard

1. 코드 난독화 필요성안드로이드 앱을 개발 후 APK 실행 파일을 추출할 수 있다. 또한 추출한 APK를 바탕으로 앱의 소스코드가 분석 가능하도록 디컴파일 할 수 있다.이는 테스트 및 리버스 엔지

hyeonlog-developer.tistory.com

 

 

 

 

앱 축소, 난독화 및 최적화  |  Android Studio  |  Android Developers

사용하지 않는 코드와 리소스를 삭제하기 위해 출시 빌드에서 코드를 축소하는 방법을 알아보세요.

developer.android.com

 

//rooting 검사
  private fun isDeviceRooted(): Boolean {
    val buildTags = Build.TAGS
    if (buildTags != null && buildTags.contains("test-keys")) {
      return true
    }
    val paths = arrayOf(
      "/system/app/Superuser.apk", "/sbin/su", "/system/bin/su", "/system/xbin/su",
      "/data/local/xbin/su", "/data/local/bin/su", "/system/sd/xbin/su",
      "/system/bin/failsafe/su", "/data/local/su"
    )
    for (path in paths) {
      if (File(path).exists()) {
        return true
      }
    }
    return false
  }

  //frida detection
  private fun isFridaServerRunning(): Boolean {
    val activityManager = getSystemService(Activity.ACTIVITY_SERVICE) as ActivityManager
    val runningAppProcess = activityManager.runningAppProcesses ?: return false

    return runningAppProcess.any { processInfo ->
      processInfo.processName.contains("frida-server", ignoreCase = true)
    }
  }
  private fun checkFrida(): Boolean {
    val pid = android.os.Process.myPid()

    return try {
      val mapsFileContents: String? = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        File("/proc/$pid/maps").readText(Charset.defaultCharset())
      } else {
        null
      }

      mapsFileContents?.contains("frida") ?: false
    } catch (e: IOException) {
      e.printStackTrace()
      false
    }
  }

  //adb, shell
  private fun isAdbShellRunning(): Boolean {
    val activityManager = getSystemService(Activity.ACTIVITY_SERVICE) as ActivityManager
    val runningAppProcess = activityManager.runningAppProcesses ?: return false
    return runningAppProcess.any { processInfo ->
      processInfo.processName.contains("shell", ignoreCase = true) ||
          processInfo.processName.contains("adb", ignoreCase = true)
    }
  }

  fun detectSuspiciousLibraries(): Boolean {
    val libraries = System.getProperty("java.library.path")
    return libraries?.contains("frida") == true ||
        libraries.contains("xposed") ||
        libraries.contains("substrate")
  }

  private fun isTracerPidPresent(): Boolean {
    val statusFile = File("/proc/self/status")
    if (statusFile.exists()) {
      statusFile.useLines { lines ->
        lines.forEach { line ->
          if (line.startsWith("TracerPid")) {
            val tracerPid = line.split(":")[1].trim().toInt()
            return tracerPid != 0
          }
        }
      }
    }
    return false
  }

 

proguard

  release {
            minifyEnabled = true
            shrinkResources = true
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
            signingConfig signingConfigs.release
            if (project.hasProperty("RELEASE_STORE_FILE")) {
                signingConfig signingConfigs.release
            }
        }
        debug {
            minifyEnabled = true
            shrinkResources = true

            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }

 

proguard-rules.pro

# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface

-keep @interface com.google.android.gms.common.annotation.KeepName
-keepnames @com.google.android.gms.common.annotation.KeepName class *
-keepclassmembernames class * {
  @com.google.android.gms.common.annotation.KeepName *;
}

-keep @interface com.google.android.gms.common.util.DynamiteApi


-dontwarn android.security.NetworkSecurityPolicy
#realm
-keep class io.realm.annotations.RealmModule
-keep @io.realm.annotations.RealmModule class *
-keep @interface io.realm.annotations.RealmModule { *; }
-keep class io.realm.annotations.RealmModule { *; }

-keep class io.realm.internal.Keep
-keep @io.realm.internal.Keep class * { *; }

-keep class io.realm.internal.KeepMember
-keep @io.realm.internal.KeepMember class * { @io.realm.internal.KeepMember *; }

-dontwarn javax.**
-dontwarn io.realm.**
-dontwarn io.reactivex.android.**

-keep class io.realm.RealmCollection
-keep class io.realm.OrderedRealmCollection
-keepclasseswithmembernames class io.realm.** {
    native <methods>;
}

-dontnote rx.Observable

# Referenced from JNI
-keep class org.bson.types.Decimal128 {
    public static org.bson.types.Decimal128 fromIEEE754BIDEncoding(...);
}
-keep class org.bson.types.ObjectId {
    <init>(...);
}

##---------------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  ----------


### Glide, Glide Okttp Module, Glide Transformations
-keep public class * implements com.bumptech.glide.module.GlideModule
-keep public class * extends com.bumptech.glide.module.AppGlideModule
-keep public enum com.bumptech.glide.load.resource.bitmap.ImageHeaderParser$** {
  **[] $VALUES;
  public *;
}

#========================================

## AWS

# Class names are needed in reflection
-keepnames class com.amazonaws.**
-keepnames class com.amazon.**

# Enums are not obfuscated correctly in combination with Gson
-keepclassmembers enum * { *; }

# Request handlers defined in request.handlers
-keep class com.amazonaws.services.**.*Handler

# The following are referenced but aren't required to run
-dontwarn com.fasterxml.jackson.**

# Android 6.0 release removes support for the Apache HTTP client
-dontwarn org.apache.http.**

# The SDK has several references of Apache HTTP client
-dontwarn com.amazonaws.http.**
-dontwarn com.amazonaws.metrics.**

# AGP 8 enables R8 full-mode optimization, which will remove constructors of classes that are only
# instantiated via reflection. These classes are instantiated via reflection in the SignerFactory.
-keep class com.amazonaws.auth.AWS4Signer { *; }
-keep class com.amazonaws.auth.QueryStringSigner { *; }
-keep class com.amazonaws.auth.NoOpSigner { *; }

#=========================================================
# Retrofit
# 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
#===================================================

# Gson
-keep class com.google.gson.** { *; }
-dontwarn com.google.gson.**

# Gson에서 직렬화/역직렬화되는 데이터 모델 유지
-keep class your.package.name.model.** { *; }
#============================================

# RxBinding
-keep class com.jakewharton.rxbinding2.** { *; }
-dontwarn com.jakewharton.rxbinding2.**

# RxJava
-keep class io.reactivex.** { *; }
-dontwarn io.reactivex.**

# RxAndroid
-keep class io.reactivex.android.** { *; }
-dontwarn io.reactivex.android.**

# RxKotlin
-keep class io.reactivex.kotlin.** { *; }
-dontwarn io.reactivex.kotlin.**
#==============================


#google map
-keepnames class com.google.android.gms.** {*;}
#============================

# Firebase Authentication
-keepattributes *Annotation*

## Google Play Services 4.3.23 specific rules ##
## https://developer.android.com/google/play-services/setup.html#Proguard ##


-keep public class com.google.android.gms.common.internal.safeparcel.SafeParcelable {
    public static final *** NULL;
}

-keepnames @com.google.android.gms.common.annotation.KeepName class *
-keepclassmembernames class * {
    @com.google.android.gms.common.annotation.KeepName *;
}

-keepnames class * implements android.os.Parcelable {
    public static final ** CREATOR;
}
#=======================

# Firebase Messaging
-keep class com.google.firebase.messaging.** { *; }

# BouncyCastle
-keep class org.bouncycastle.jsse.** { *; }

# Conscrypt
-keep class org.conscrypt.** { *; }

# OpenJSSE
-keep class org.openjsse.** { *; }

 

gradle.properties

# R8 (optimization)
# AGP 8.0version ??? = true(default) - 최적화되기 때문에 만약 true일 때 실행이 되지 않는다면 false로 변경
android.enableR8.fullMode=false

 

빌드할 때 빌드가 끝나고 app-build-outputs-mapping에서 missing_rules.txt를 보고 참조하면 빠르게 끝낼 수 있다

 

# Please add these rules to your existing keep rules in order to suppress warnings.
# This is generated automatically by the Android Gradle plugin.
-dontwarn com.google.firebase.messaging.TopicOperation$TopicOperations
-dontwarn org.bouncycastle.jsse.BCSSLParameters
-dontwarn org.bouncycastle.jsse.BCSSLSocket
-dontwarn org.bouncycastle.jsse.provider.BouncyCastleJsseProvider
-dontwarn org.conscrypt.Conscrypt$Version
-dontwarn org.conscrypt.Conscrypt
-dontwarn org.conscrypt.ConscryptHostnameVerifier
-dontwarn org.openjsse.javax.net.ssl.SSLParameters
-dontwarn org.openjsse.javax.net.ssl.SSLSocket
-dontwarn org.openjsse.net.ssl.OpenJSSE

 

내 경우에는 이런 힌트를 받고 또한 빌드할 떄 로그로 남는걸 하나씩 적용하여 마무리지을 수 있었다

Missing class com.google.firebase.messaging.TopicOperation$TopicOperations (referenced from: void com.google.firebase.messaging.TopicOperation.<init>(java.lang.String, java.lang.String))
Missing class org.bouncycastle.jsse.BCSSLParameters (referenced from: void okhttp3.internal.platform.BouncyCastlePlatform.configureTlsExtensions(javax.net.ssl.SSLSocket, java.lang.String, java.util.List) and 1 other context)
Missing class org.bouncycastle.jsse.BCSSLSocket (referenced from: void okhttp3.internal.platform.BouncyCastlePlatform.configureTlsExtensions(javax.net.ssl.SSLSocket, java.lang.String, java.util.List) and 5 other contexts)
Missing class org.bouncycastle.jsse.provider.BouncyCastleJsseProvider (referenced from: void okhttp3.internal.platform.BouncyCastlePlatform.<init>())
Missing class org.conscrypt.Conscrypt$Version (referenced from: boolean okhttp3.internal.platform.ConscryptPlatform$Companion.atLeastVersion(int, int, int))
Missing class org.conscrypt.Conscrypt (referenced from: boolean okhttp3.internal.platform.ConscryptPlatform$Companion.atLeastVersion(int, int, int) and 4 other contexts)
Missing class org.conscrypt.ConscryptHostnameVerifier (referenced from: okhttp3.internal.platform.ConscryptPlatform$DisabledHostnameVerifier)
Missing class org.openjsse.javax.net.ssl.SSLParameters (referenced from: void okhttp3.internal.platform.OpenJSSEPlatform.configureTlsExtensions(javax.net.ssl.SSLSocket, java.lang.String, java.util.List))
Missing class org.openjsse.javax.net.ssl.SSLSocket (referenced from: void okhttp3.internal.platform.OpenJSSEPlatform.configureTlsExtensions(javax.net.ssl.SSLSocket, java.lang.String, java.util.List) and 1 other context)
Missing class org.openjsse.net.ssl.OpenJSSE (referenced from: void okhttp3.internal.platform.OpenJSSEPlatform.<init>())

 

+추가적으로

 

테스트 환경에서 최적화를 비활성화해 경고의 근본적인 원인을 파악할 수도 있습니다. 예를 들어, 아래와 같이 -dontoptimize를 사용하여 최적화를 중단할 수 있답니다.

-dontoptimize

 

 

5.9MB 정도 작아진 어플을 볼 수 있다

반응형