|
@@ -27,7 +27,7 @@ working...
|
27
|
27
|
At first it seemed like ProGuard was refusing to obfuscate any class with a
|
28
|
28
|
`keep` rule. With a simple test class:
|
29
|
29
|
|
30
|
|
-```kotlin
|
|
30
|
+{{< highlight kotlin >}}
|
31
|
31
|
class Test {
|
32
|
32
|
|
33
|
33
|
private val secret = 123
|
|
@@ -47,21 +47,21 @@ class Test {
|
47
|
47
|
)
|
48
|
48
|
|
49
|
49
|
}
|
50
|
|
-```
|
|
50
|
+{{< / highlight >}}
|
51
|
51
|
|
52
|
52
|
And a ProGuard rule of:
|
53
|
53
|
|
54
|
|
-```proguard
|
|
54
|
+{{< highlight proguard >}}
|
55
|
55
|
-keep public class Test {
|
56
|
56
|
public void greet();
|
57
|
57
|
}
|
58
|
|
-```
|
|
58
|
+{{< / highlight >}}
|
59
|
59
|
|
60
|
60
|
I expected that the `Test` class and the `greet` method would remain, but both
|
61
|
61
|
fields and the `guess` method would be obfuscated. When I built the project
|
62
|
62
|
and opened the class from Android Studio's APK inspector I was disappointed:
|
63
|
63
|
|
64
|
|
-```kotlin
|
|
64
|
+{{< highlight kotlin >}}
|
65
|
65
|
public final class Test public constructor() {
|
66
|
66
|
public final var name: kotlin.String /* compiled code */
|
67
|
67
|
|
|
@@ -71,7 +71,7 @@ public final class Test public constructor() {
|
71
|
71
|
|
72
|
72
|
private final fun guess(attempt: kotlin.Int): kotlin.Unit { /* compiled code */ }
|
73
|
73
|
}
|
74
|
|
-```
|
|
74
|
+{{< / highlight >}}
|
75
|
75
|
|
76
|
76
|
Having your secret sauce in a field labelled "secret" isn't exactly the level of
|
77
|
77
|
obfuscation I was hoping for. ProGuard has lots of knobs that you can twist to
|
|
@@ -93,29 +93,29 @@ like reading the ProGuard mapping file and automatically deobfuscating the
|
93
|
93
|
output for me. Looking at the mapping file it seems that ProGuard has
|
94
|
94
|
indeed decided to rename some things:
|
95
|
95
|
|
96
|
|
-```
|
|
96
|
+{{< highlight proguardmapping >}}
|
97
|
97
|
Test -> Test:
|
98
|
98
|
int secret -> a
|
99
|
99
|
java.lang.String name -> b
|
100
|
100
|
void greet() -> greet
|
101
|
101
|
void <init>() -> <init>
|
102
|
|
-```
|
|
102
|
+{{< / highlight >}}
|
103
|
103
|
|
104
|
104
|
The obvious solution is to look at the class file in something less smart
|
105
|
105
|
than Android Studio. A couple of unzips later and I could do a quick test
|
106
|
106
|
to see if the original names were still present:
|
107
|
107
|
|
108
|
|
-```console
|
|
108
|
+{{< highlight console >}}
|
109
|
109
|
$ strings Test.class | grep secret
|
110
|
110
|
secret
|
111
|
|
-```
|
|
111
|
+{{< / highlight >}}
|
112
|
112
|
|
113
|
113
|
The problem is evidently not with Android Studio, as `secret` shouldn't end
|
114
|
114
|
up in the class file at all: it should have been entirely replaced with `a` like
|
115
|
115
|
the mapping file says. The output from `javap -p` doesn't show any hint of the
|
116
|
116
|
original names, however:
|
117
|
117
|
|
118
|
|
-```console
|
|
118
|
+{{< highlight console >}}
|
119
|
119
|
$ javap -p Test
|
120
|
120
|
public final class Test {
|
121
|
121
|
private final int a;
|
|
@@ -123,13 +123,13 @@ public final class Test {
|
123
|
123
|
public final void greet();
|
124
|
124
|
public Test();
|
125
|
125
|
}
|
126
|
|
-```
|
|
126
|
+{{< / highlight >}}
|
127
|
127
|
|
128
|
128
|
But, given the names show up in `strings`, they must be kicking around
|
129
|
129
|
somewhere. None of the various outputs from `javap` helped until I hit
|
130
|
130
|
`-verbose`. Right at the end of the class is:
|
131
|
131
|
|
132
|
|
-```
|
|
132
|
+{{< highlight javap >}}
|
133
|
133
|
RuntimeVisibleAnnotations:
|
134
|
134
|
0: #58(#84=[I#2,I#2,I#4],#69=[I#2,I#1,I#3],#81=I#2,#70=[s#40],#71=[s#55,s#39,s#43,s#85,s#39,s#72,s#42,s#91,s#47,s#90,s#39,s#73,s#39,s#74,s#67,s#83])
|
135
|
135
|
kotlin.Metadata(
|
|
@@ -139,7 +139,7 @@ RuntimeVisibleAnnotations:
|
139
|
139
|
d1=["\u0000\"\n\u0002\u0018\u0002\n\u0002\u0010\u0000\n\u0002\b\u0002\n\u0002\u0010\u000e\n\u0002\b\u0005\n\u0002\u0010\b\n\u0000\n\u0002\u0010\u0002\n\u0002\b\u0003\u0018\u00002\u00020\u0001B\u0005¢\u0006\u0002\u0010\u0002J\u0006\u0010\u000b\u001a\u00020\fJ\u0010\u0010\r\u001a\u00020\f2\u0006\u0010\u000e\u001a\u00020\nH\u0002R\u001a\u0010\u0003\u001a\u00020\u0004X\u0086\u000e¢\u0006\u000e\n\u0000\u001a\u0004\b\u0005\u0010\u0006\"\u0004\b\u0007\u0010\bR\u000e\u0010\t\u001a\u00020\nX\u0082D¢\u0006\u0002\n\u0000¨\u0006\u000f"]
|
140
|
140
|
d2=["LTest;","","()V","name","","getName","()Ljava/lang/String;","setName","(Ljava/lang/String;)V","secret","","greet","","guess","attempt","lib_release"]
|
141
|
141
|
)
|
142
|
|
-```
|
|
142
|
+{{< / highlight >}}
|
143
|
143
|
|
144
|
144
|
There's a Kotlin *annotation* containing all of the symbols we were trying to
|
145
|
145
|
obfuscate away! Kotlin apparently uses this annotation for reflection and for
|
|
@@ -148,7 +148,7 @@ Java bytecode (such as members with `internal` access). Sure enough, switching
|
148
|
148
|
back to Android Studio and making it "decompile" the Kotlin code into Java
|
149
|
149
|
shows the annotation:
|
150
|
150
|
|
151
|
|
-```java
|
|
151
|
+{{< highlight java >}}
|
152
|
152
|
@Metadata(
|
153
|
153
|
mv = {1, 1, 15},
|
154
|
154
|
bv = {1, 0, 3},
|
|
@@ -157,7 +157,7 @@ shows the annotation:
|
157
|
157
|
d2 = {"LTest;", "", "()V", "name", "", "getName", "()Ljava/lang/String;", "setName", "(Ljava/lang/String;)V", "secret", "", "greet", "", "guess", "attempt", "lib_release"}
|
158
|
158
|
)
|
159
|
159
|
public final class Test {
|
160
|
|
-```
|
|
160
|
+{{< / highlight >}}
|
161
|
161
|
|
162
|
162
|
## Solving the right problem
|
163
|
163
|
|
|
@@ -177,9 +177,9 @@ Adding a `-printconfiguration` instruction to my configuration lets me see
|
177
|
177
|
the full configuration being passed to ProGuard, and the reason for keeping
|
178
|
178
|
quickly becomes obvious:
|
179
|
179
|
|
180
|
|
-```proguard
|
|
180
|
+{{< highlight proguard >}}
|
181
|
181
|
-keepattributes *Annotation*,*Annotation*
|
182
|
|
-```
|
|
182
|
+{{< / highlight >}}
|
183
|
183
|
|
184
|
184
|
This appears to be added by the Android build plugin before it invokes
|
185
|
185
|
ProGuard, and there's no obvious way to disable it. ProGuard doesn't offer
|
|
@@ -187,20 +187,20 @@ a way to reverse this instruction, either, but fortunately the build plugin
|
187
|
187
|
seems to concatenate all of the `-keepattribute` values together and puts our
|
188
|
188
|
user-supplied ones first. Adding a negative filter:
|
189
|
189
|
|
190
|
|
-```proguard
|
|
190
|
+{{< highlight proguard >}}
|
191
|
191
|
-keepattributes !*Annotation*
|
192
|
|
-```
|
|
192
|
+{{< / highlight >}}
|
193
|
193
|
|
194
|
194
|
Results in the following in the printed configuration:
|
195
|
195
|
|
196
|
|
-```proguard
|
|
196
|
+{{< highlight proguard >}}
|
197
|
197
|
-keepattributes !*Annotation*,*Annotation*,*Annotation*
|
198
|
|
-```
|
|
198
|
+{{< / highlight >}}
|
199
|
199
|
|
200
|
200
|
The negative filter prevents any subsequent filters from matching. Recompiling
|
201
|
201
|
and looking at the class file again looks a lot more sensible:
|
202
|
202
|
|
203
|
|
-```java
|
|
203
|
+{{< highlight java >}}
|
204
|
204
|
import kotlin.io.ConsoleKt;
|
205
|
205
|
|
206
|
206
|
public final class Test {
|
|
@@ -219,6 +219,6 @@ public final class Test {
|
219
|
219
|
}
|
220
|
220
|
}
|
221
|
221
|
}
|
222
|
|
-```
|
|
222
|
+{{< / highlight >}}
|
223
|
223
|
|
224
|
224
|
And that's the story of how I spent half a day making a one line change.
|