MichaelRocks / paranoid

String obfuscator for Android applications.
Apache License 2.0
670 stars 79 forks source link

R8 guildelines #31

Open headsvk opened 4 years ago

headsvk commented 4 years ago

Hi Micheal! Thanks for the neat project. It took me a while to test it with R8 and BuildConfig and I'd like to contribute back by writing these guidelines. Do they make sense? Do you use it in any other way?

I would say issue #11 is not necessary to do if obfuscation is used like in my example.

Usage with R8 and BuildConfig

A common use case is to have configuration specific variables in the generated BuildConfig class which cannot be obfuscated by adding @Obfuscate, for example:

public final class BuildConfig {
  public static final String API_URL = "https://github.com/";
}

Instead of using the variables directly, we can define another class that will wrap the BuildConfig fields and optionally add its own.

@Obfuscate
object Secrets {
    val API_SECRET = "user:password"
    val API_URL = BuildConfig.API_URL
}

If you are using R8, it's important that the fields are NOT const val as they would get moved to usage sites and not obfuscated. That also means that R8 will remove all public static final String fields from the compiled BuildConfig class, so they only have to be obfuscated where they are used.

Decompiled Secrets.kt with R8 enabled shows that Deobfuscator is called in the constructor to initialize API_SECRET and API_URL fields and the generated getter functions can access them afterwards without calling Deobfuscator again.

.class public final Lcom/example/app/Secrets;
.super Ljava/lang/Object;
.source "Secrets.kt"

# static fields
.field private static final API_SECRET:Ljava/lang/String;

.field private static final API_URL:Ljava/lang/String;

.field public static final INSTANCE:Lcom/example/app/Secrets;

# direct methods
.method static constructor <clinit>()V
    .registers 2
    const-wide v0, -0x52ca9f23686ca7e4L    # -6.559274792077535E-91
    invoke-static {v0, v1}, Lio/michaelrocks/paranoid/Deobfuscator$app$App;->getString(J)Ljava/lang/String;
    move-result-object v0
    sput-object v0, Lcom/example/app/Secrets;->API_SECRET:Ljava/lang/String;
    const-wide v0, -0x52ca9f31686ca7e4L    # -6.559209248608569E-91
    invoke-static {v0, v1}, Lio/michaelrocks/paranoid/Deobfuscator$app$App;->getString(J)Ljava/lang/String;
    move-result-object v0
    sput-object v0, Lcom/example/app/Secrets;->API_URL:Ljava/lang/String;
    .line 1
    new-instance v0, Lcom/example/app/Secrets;
    invoke-direct {v0}, Lcom/example/app/Secrets;-><init>()V
    sput-object v0, Lcom/example/app/Secrets;->INSTANCE:Lcom/example/app/Secrets;
    const-wide v0, -0x52ca9f1d686ca7e4L    # -6.559302882135663E-91
    .line 2
    invoke-static {v0, v1}, Lio/michaelrocks/paranoid/Deobfuscator$app$App;->getString(J)Ljava/lang/String;
    move-result-object v0
    sput-object v0, Lcom/example/app/Secrets;->API_SECRET:Ljava/lang/String;
    const-wide v0, -0x52ca9f6b686ca7e4L    # -6.558937711379997E-91
    .line 3
    invoke-static {v0, v1}, Lio/michaelrocks/paranoid/Deobfuscator$app$App;->getString(J)Ljava/lang/String;
    move-result-object v0
    sput-object v0, Lcom/example/app/Secrets;->API_URL:Ljava/lang/String;
    return-void
.end method

.method private constructor <init>()V
    .registers 1
    .line 1
    invoke-direct {p0}, Ljava/lang/Object;-><init>()V
    return-void
.end method

# virtual methods
.method public final getAPI_SECRET()Ljava/lang/String;
    .registers 2
    .line 1
    sget-object v0, Lcom/example/app/Secrets;->API_SECRET:Ljava/lang/String;
    return-object v0
.end method

.method public final getAPI_URL()Ljava/lang/String;
    .registers 2
    .line 1
    sget-object v0, Lcom/example/app/Secrets;->API_URL:Ljava/lang/String;
    return-object v0
.end method
MichaelRocks commented 4 years ago

Hi Marek,

Thank you so much for these guidelines. I'll add it to README.md if you don't mind.

And I'd still like to implement caching because there're other scenarios when you may want to obfuscate string literals in a class. For example one may want to hide inlined log tags or http headers to make reverse engineering of particular classes more difficult.

headsvk commented 4 years ago

Yeah sure, pick what makes sense, I'm not skilled in writing documentation.

Okay yes if you don't store the string literal in a variable, then it's useful to cache it.

ubuntudroid commented 2 years ago

@MichaelRocks the R8 stuff hasn't found its way into the README yet. Any reason for that? Did you encounter cases in where the suggested solutions don't work?