Why some package-private classes are not obfuscated by Proguard? - android

Working with an Android project in Android Studio 3.2, having enabled Proguard and some specific rules, I'm not able to figure out the following:
a specific package (and its subpackages) in a library module, used by client code, is preserved through the rule:
-keep public class com.mylib.mypackage.** {
public protected *;
}
Now, within this package there are also a number of package-private classes, which should not be picked by that rule. Some of those classes are effectively obfuscated, both in their own names and their member names, which is fine.
Instead there are some classes, implementing public interfaces, whose class names are not obfuscated, while I'd expect they should. For completeness, their member names, when not part of interface, are effectively obfuscated.
Example:
/* package */ class InternalComponent implements ExternalInterface {
// ExternalInterface is kept: Ok
// InternalComponent is kept: don't like, I'd like it renamed
#Override
public void ExternalMethod() {
// this is kept: Ok
}
public void InternalMethod() {
// this is renamed: Ok
}
}
I'd like to highlight that InternalComponent is created within some other (kept) class and only returned to client code through the ExternalInterface.
How can I also obfuscate their class names as well, if possible?
Edit #1
After #emandt answer on Proguard output files, I double checked and com.mylib.mypackage.InternalComponent is listed in seeds.txt, which according to this blog post lists all items matched by keep rules. So, for some reason, the rule above also picks package-private classes, which still seems wrong to me.
Edit #2
In the meantime, I ended up doing exactly the same approach proposed by #shizhen. For completeness, in order to extend the exclusion to any package named internal, I modified my proguard rule as:
-keep public class !com.mylib.mypackage.**.internal.*, com.mylib.mypackage.** {
public protected *;
}
(note the first part before the comma, prefixed by !)
I'll mark #shizhen answer, though I'd like to be curious as to why the original rule is also picking package-private components.

Are you working on an Android Library project? Probably YES.
In order to achieve your purpose, I am afraid that you need to re-organise your packages into something like below:
Public interfaces
com.my.package.apiforusers
Private/Internal implementations
com.my.package.apiforusers.internal
Then for your obfuscation rules, you can have it like below:
-keep public class com.my.package.apiforusers.** { public *; }
So that only the public classes/interfaces are kept and all those ones inside com.my.package.apiforusers.internal will be obfuscated.
Please note the double-asterisk at the end so that public classes/interface are also kept for the sub-packages.

In "/build/outputs/mapping/release/" folder there are few files ("usage.txt", "seeds.txt", etc..) that contain the REASONS of why and which classes/variables/methods/etc.. are not-processed/not-shrinked/ot-obfuscated via ProGuard utilities.

Related

Storing (partial) Proguard obfuscation mapping in code

In an android Java code like this:
String methodName = "myMethod"
public void myMethod() {
}
Is it possible to figure out the obfuscated name of the method at runtime?
I know I can ask Proguard to not obfuscate that method, but I'm looking for a way to avoid that and still be able to use it through reflection.
I can't find a Proguard configuration for that, but I'm wondering if there could be a gradle task for merging the generated mapping?
If you need the method name from inside the method, you can use
String methodName;
public void myMethod() {
methodName = new Object() {}.getClass().getEnclosingMethod().getName();
}
Or, in newer Java versions
String methodName;
public void myMethod() {
methodName = StackWalker.getInstance(Set.of(), 1)
.walk(s -> s.map(StackWalker.StackFrame::getMethodName).findFirst())
.orElseThrow();
}
When you need the name from the outside, i.e. with calling it, you can mark the method with an annotation and search for it.
#Retention(RetentionPolicy.RUNTIME) #interface Marker {}
#Marker
public void myMethod() {
}
String name = Arrays.stream(ContainingClass.class.getDeclaredMethods())
.filter(m -> m.isAnnotationPresent(Marker.class))
.map(Method::getName)
.findFirst().orElseThrow();
As said by others, not to use Reflection might be the better option. E.g., referring to the method using a matching functional interface, is not a reflective access:
Runnable method = this::myMethod;
public void myMethod() {
}
This will keep working after obfuscation.
Code obfuscation usually just renames classes and variables to short and random text, keeping proper dependencies at the same time. There is no way to get the obfuscated name of the function as it is generated randomly and always different per each run.
Here are some options:
Instead, exclude the class from obfuscation and name your function with random text if you want to keep it unreadable.
-keep class <com.package.DontObfuscate>
After the first obfuscation, keep mapping.txt file and add a line to your proguard-rules.pro
-applymapping mapping.txt
That's will keep from generating new names for classes/variables.
-applymapping - Specifies to reuse the given name mapping that was printed out in a previous obfuscation run of ProGuard. Classes and class members that are listed in the mapping file receive the names specified along with them. Classes and class members that are not mentioned receive new names.
Look for a way to not use reflection in your code with class/interface extensions.

How to keep method names in Android Proguard R8?

I'm developing an Android Library and I'd like to have this result for Proguard.
I want to keep all package names, which I'm using "-keeppackagenames" to achieve.
I want to keep all class names.
I want to keep method names and method argument names for all public methods.
Other than that, I'd like to obfuscate and shrink everything else, including but not limited to all of non-public methods and even the body of the public methods.
How can I achieve that result?
Thank you very much beforehand for your help!
R8 itself is distributes as a library which has been run through R8. For that library we want to achieve exactly what you ask to ensure that developers using the library will not by accident use any APIs which are not supposed to be public (this has nothing to do with protecting IP as the project is open source).
The rules can be found here and I will explain the rationale for them below.
We try to be indirect and use annotations, so the main rules are:
-keep #com.android.tools.r8.Keep class * { public *; }
-keep #com.android.tools.r8.KeepForSubclassing class * { public *; protected *; }
All API classes are then annotated with #Keep or #KeepForSubclassing.
Then a number of attrubutes are kept
-keepattributes SourceFile, LineNumberTable, InnerClasses, EnclosingMethod, Exceptions, Signature
SourceFile and LineNumberTable are kept to get retracable stack traces. The rest for javac compilation and IDE integration for library users.
Then
-keepparameternames
is there for IDE integration getting the names of arguments in the API, and
-keeppackagenames com.android.tools.r8
-repackageclasses com.android.tools.r8.internal
to move as many classes as possible into the internal name space so the renamed classes does not show up in IDE's.
There are a few additional rules in the file to handle cases not covered by the annotation based approach.

Android Data Binding: Wrong BR class generated for obfuscated AAR

I'd like to provide an obfuscated AAR library which makes use of Android data binding. When I use the library from a test app, everything works fine as long as the library isn't minified by ProGuard. However, after enabling ProGuard the test app doesn't compile any more since BR fields in the generated data binding classes can't be found.
Since I couldn't find any official documentation on this "specific" subject, I tried to understand the magic behind Android data binding. The mechanism seem to be like this (please correct me, if I'm wrong):
In order to use data binding within an AAR lib, the embedding app must enable data binding, too.
This is because the the layout resources that contain data binding instructions are included without modification in the AAR.
It's therefore the embedding app's responsibility to generate the corresponding data binding classes for the layouts in the lib. (That's why the lib's view model classes must not be obfuscated, BTW.)
The challenge for the Android data binding generator is to tell the package names apart from both library and embedding app: The BR class for the library must be generated in the library's package (e. g. com.example.lib.databinding), since this class is accessed from the library's view model classes. On the other hand, the BR class of the embedding app should normally be generated within the app's package (com.example.app.databinding).
And this is exactly where my problems begin. I don't know Android exactly rises to this challenge, I only know that in my case, it works with an unobfuscated lib, and it doesn't with an obfuscated one. When I look into the generated source of the embedding app, I see:
When using the unobfuscated lib, both the BR and all *Binding.java classes are generated within the library's package, and the app compiles.
When using the obfuscated lib, both the BR and all *Binding.java classes are generated within the apps's package. Even worse, the BR only contains constants for the model variable names in the XML resource, not for the properties in the view model classes. Consequently, the app doesn't compile.
I tried to set the package of the data binding classes explicitely to the lib's package in the XML declaration, but this doesn't solve the problem with the incomplete BR class.
I have no clue where these differences come from and I' already fearing that the only solution could be removing all my nice data binding stuff from the lib ... Does anybody made similar experiences and could give me a hint, please?
These are the ProGuard exceptions I've already added to my lib:
-keep public class **.BR { public *; }
-keep public class **.BR$* { public *; }
-keepclassmembers class **.BR$* {
public static <fields>;
}
-keepclassmembers class **.R$* {
public static <fields>;
}
-keep class android.databinding.** { *; }
-keep class * extends android.databinding.** { *; }
-keep class com.example.lib.databinding.** { *; }
I managed to make it run in the meantime, but the "solution" is so weird that I really don't want to take it over to a productive version ...
When comparing the AAR files of the obfuscated and unobfuscated lib, I noticed that the classes.jar of the unobfuscated one contained these three files:
/com/example/lib/com.example.lib-br.bin
/com/example/lib/com.example.lib-layoutinfo.bin
/com/example/lib/com.example.lib-setter_store.bin
These binary files contain some of my data binding class names and are apparently important for the code generation process. I just tried to copy these files into the corresponding place of my obfuscated AAR and ... it worked!!!
But this cannot be the final solution. At least it would be more reliable than coping if I can convince ProGuard to simply keep these non-class-files within the classes.jar. Any ideas how to do this?
Much more I would appreciate to get some information on the backgrounds of this mechanism and whether it is possible to avoid such ugly low-level operations for solving a problem that actually should be standard.
Thanks in advance for any answer!
Apparently Google has resolved the issue with Gradle plugin 2.3.0, see https://code.google.com/p/android/issues/detail?id=229684.

How to tell Proguard to obfuscate class names

I would like proguard to obfuscate classnames. I have this line in Proguard.cfg
-keepclasseswithmembers class * {
public static <fields>;
}
-keepnames class * implements java.io.Serializable
-keep public class com.google.**
And I notice that what is not obfuscated is the class names. So running jdgui i see
com/test/abcd/ActualClass.java
public class ActualClassName extends Activity etc
moreover I see methods returning real classnames. like
ActualClassname aa();
and imports statements like
import com.abcd.ActualClassName
How do I get Proguard to obfuscate the classname itself. Its not just for Activities that I see this my Adapters are not being obfuscated. Well there is obfuscation going on but not class names.
Is the rules above what prevents the class names from being obfuscated?
Update: i have since removed the rules above and a Utility class that does not extend anything from Android is not being obfuscated. I'm now wondering if there is some implicit rule about keeping class names of classes that are referenced from classes that are being kept like Activity derivied classes? The classes whose names are not being obfuscated have a few things in common:
1) Static methods
2) Import of other types which are kept like those deriving from activity or serializable.
3) They have methods with parameters of other classes (Some of which might need to be kept).
However there is no where that I am specifically requesting that these utility classes should be kept.
There are several class that appear in your code that must retain the same fully qualified class name in order for android to be able to find them. One example as above are all Activity classes, as they are defined in the manifest by their full name as a String. If proguard were to rename the class then Android would no longer be able to find them.
The typical Proguard config will refer to the Proguard config in the Android SDK which will include several important lines like this one:
-keep public class * extends android.app.Activity
I think your problem is coming from your first line: '-keepclasseswithmembers' includes the class name, so in your case any class with a static field will keep its name. Changing it to simply '-keepclassmembers' will obfuscate the class names while leaving the static fields intact(which I'm assuming you want to do).
That said, I'm not sure why you want to do this, though. If you're trying to preserve the static variable names, shouldn't you want to preserve the class names as well, seeing as how that's how you'll be accessing the static fields? Why are you trying to preserve all of your static variable names?

android onClick events and pro guard

i'm trying to obfuscate my app before uploading to the market.
i've setup pro guard and i allready handled the Serialzie issue (prog guard maul - serialize) however i still have a problem (MethodNotFoundException) when trying to press one of the buttons in my home screen.
they are simple LinearLayout with background that in the xml have the android:onClick="doOnClick" attribute. in my HomeScreenActivity i have a method named :
public void doOnClick(View v){...}
that should be called whenever a button is pressed.
the code works GREAT when not obfuscated, but once obfuscated android class loader in unable to find my method. Before i move in to use only code and not xml callbacks (maybe advisable) i would like to know if there is a nice way around it.
Trying to prevent method obfuscation did not work for me (trying to prevent obfuscation of methods that extends Activity and are of the form public void on(android.view.View); or public void On(android.view.View).
If anyone have done it i'll appreciate the hint :-)
It's really simple, this should work.
-keepclasseswithmembers class MyActivity {
public void doOnClick(android.view.View);
}
Add this to your proguards file.
-keep class com.android.toto.ClassName {
public void doOnClick(android.view.View);
}
the -keep or keepclasswithmemebers do not apply since i already have -keep public class * extends android.app.Activity which will include ALL Activities.
I managed to find the problem in which the methods had been totally removed by proguard optimization and replaced with direct inline code. This happens because there are no specific calls to those functions in code; note that the XML calls to the code aren't taken into account.
adding the following to the proguard configuration solves the problem:
-keepclassmembers class * extends android.app.Activity {
public void *On*Click(android.view.View);
public void *on*Click(android.view.View);
}

Categories

Resources