Using ProGuard to remove logs

A couple of days ago the following two tweets appeared on my twitter feed:

These tweets reminded me of something: If you’re an Android developer and you want to use ProGuard’s code removal feature to remove your logs and use method renaming at the same time, be careful. Understand if you just want the logs to be removed from logcat or if you also want the strings and log information to be removed from your binary. As ProGuard is NOT a security product, it shouldn’t be used as one - especially if you want to take advantage of obfuscation and log removal.

Here’s the problem: Proguard will remove method calls - nothing beyond that. If you expect it to remove all the strings you’d normally see in your calls from your binary, it won’t always do that.

Let’s explain why with an example. Say you have the following class in your Android code:

public class SecurityUtils {
    static String doSecurityThing(){
        int rnd = new Random().nextInt();
        Log.d("ProguardTest", "SecurityUtils.doSecurityThing() running method. Secret random is "+Integer.toString(rnd));
        return "test";
    }
}

Obviously, the Log.d() call exposes the random number AND the name of the class and the function to whoever has access to logs.

After enabling ProGuard without custom rules, here’s what happens:

public final class a {
    static String a() {
        int a = new Random().nextInt();
        Log.d("ProguardTest", "SecurityUtils.doSecurityThing() running method. Secret random is "+Integer.toString(a));
        return "test";
    }
}

You can see the class name and the function name got renamed to something that doesn’t make sense. Good. However the logging call remains.. Now you decide to use ProGuard’s Log removal feature:

-assumenosideeffects class android.util.Log {
 public static int d(...);
 public static int v(...);
}

Now, logs are not there at logcat any more. But did the strigns get removed from your binary? Here’s what really happened:

public final class a {
    static String a() {
        new StringBuilder("SecurityUtils.doSecurityThing() running method. Secret random is ").append(Integer.toString(new Random().nextInt()));
        return "test";
    }
}

As you can see, what happened here is that the string concatenation operation inside the logging call was left in the binary; a string is built using the StringBuilder class but is not actually logged. ProGuard’s log removal just removed the invocation of Log.d() but not the constructor of the StringBuilder object.

Even though nothing will get logged, anyone looking at the code can figure out the initial method and class name, and thus understand what that piece of code does, essentially ‘reversing’ the class renaming that was done by ProGuard.

TLDR; it’s absolutely OK to remove logging in release builds, but note that ProGuard shouldn’t be relied upon for binary protection.

mobile security, static & dynamic analysis, automation, payments

London, UK