1 /*
2  * Copyright (C) 2017 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 import art.*;
18 import java.lang.ref.*;
19 import java.lang.reflect.*;
20 import java.util.*;
21 import java.util.function.Consumer;
22 import sun.misc.Unsafe;
23 
24 public class Main {
25   public static class Transform {
26     static {
27     }
28 
29     public static Object SECRET_ARRAY = new byte[] {1, 2, 3, 4};
30     public static long SECRET_NUMBER = 42;
31 
foo()32     public static void foo() {}
33   }
34 
35   /* Base64 for
36    * public static class Trasform {
37    *   static {}
38    *   public static Object AAA_PADDING;
39    *   public static Object SECRET_ARRAY;
40    *   public static long SECRET_NUMBER;
41    *   public static void foo() {}
42    *   public static void bar() {}
43    * }
44    */
45   public static final byte[] REDEFINED_DEX_FILE =
46       Base64.getDecoder()
47           .decode(
48               "ZGV4CjAzNQDdmsOAlizFD4Ogb6+/mfSdVzhmL8e/mRcYBAAAcAAAAHhWNBIAAAAAAAAAAGADAAAU"
49                   + "AAAAcAAAAAcAAADAAAAAAQAAANwAAAADAAAA6AAAAAUAAAAAAQAAAQAAACgBAADQAgAASAEAAKwB"
50                   + "AAC2AQAAvgEAAMsBAADOAQAA4AEAAOgBAAAMAgAALAIAAEACAABLAgAAWQIAAGgCAABzAgAAdgIA"
51                   + "AIMCAACIAgAAjQIAAJMCAACaAgAAAwAAAAQAAAAFAAAABgAAAAcAAAAIAAAADQAAAA0AAAAGAAAA"
52                   + "AAAAAAEABQACAAAAAQAFAAoAAAABAAAACwAAAAEAAAAAAAAAAQAAAAEAAAABAAAADwAAAAEAAAAQ"
53                   + "AAAABQAAAAEAAAABAAAAAQAAAAUAAAAAAAAACQAAAFADAAAhAwAAAAAAAAAAAAAAAAAAmgEAAAEA"
54                   + "AAAOAAAAAQABAAEAAACeAQAABAAAAHAQBAAAAA4AAAAAAAAAAACiAQAAAQAAAA4AAAAAAAAAAAAA"
55                   + "AKYBAAABAAAADgAHAA4ABgAOAAsADgAKAA4AAAAIPGNsaW5pdD4ABjxpbml0PgALQUFBX1BBRERJ"
56                   + "TkcAAUoAEExNYWluJFRyYW5zZm9ybTsABkxNYWluOwAiTGRhbHZpay9hbm5vdGF0aW9uL0VuY2xv"
57                   + "c2luZ0NsYXNzOwAeTGRhbHZpay9hbm5vdGF0aW9uL0lubmVyQ2xhc3M7ABJMamF2YS9sYW5nL09i"
58                   + "amVjdDsACU1haW4uamF2YQAMU0VDUkVUX0FSUkFZAA1TRUNSRVRfTlVNQkVSAAlUcmFuc2Zvcm0A"
59                   + "AVYAC2FjY2Vzc0ZsYWdzAANiYXIAA2ZvbwAEbmFtZQAFdmFsdWUAdn5+RDh7ImNvbXBpbGF0aW9u"
60                   + "LW1vZGUiOiJkZWJ1ZyIsIm1pbi1hcGkiOjEsInNoYS0xIjoiYTgzNTJmMjU0ODg1MzYyY2NkOGQ5"
61                   + "MDlkMzUyOWM2MDA5NGRkODk2ZSIsInZlcnNpb24iOiIxLjYuMjAtZGV2In0AAgMBEhgCAgQCDgQJ"
62                   + "ERcMAwAEAAAJAQkBCQCIgATIAgGBgATcAgEJ9AIBCYgDAAAAAAACAAAAEgMAABgDAABEAwAAAAAA"
63                   + "AAAAAAAAAAAADwAAAAAAAAABAAAAAAAAAAEAAAAUAAAAcAAAAAIAAAAHAAAAwAAAAAMAAAABAAAA"
64                   + "3AAAAAQAAAADAAAA6AAAAAUAAAAFAAAAAAEAAAYAAAABAAAAKAEAAAEgAAAEAAAASAEAAAMgAAAE"
65                   + "AAAAmgEAAAIgAAAUAAAArAEAAAQgAAACAAAAEgMAAAAgAAABAAAAIQMAAAMQAAACAAAAQAMAAAYg"
66                   + "AAABAAAAUAMAAAAQAAABAAAAYAMAAA==");
67 
68   private interface TConsumer<T> {
accept(T t)69     public void accept(T t) throws Exception;
70   }
71 
72   private interface ResetIterator<T> extends Iterator<T> {
reset()73     public void reset();
74   }
75 
76   private static final class BaseResetIter implements ResetIterator<Object[]> {
77     private boolean have_next = true;
78 
next()79     public Object[] next() {
80       if (have_next) {
81         have_next = false;
82         return new Object[0];
83       } else {
84         throw new NoSuchElementException("only one element");
85       }
86     }
87 
hasNext()88     public boolean hasNext() {
89       return have_next;
90     }
91 
reset()92     public void reset() {
93       have_next = true;
94     }
95   }
96 
main(String[] args)97   public static void main(String[] args) throws Exception {
98     System.loadLibrary(args[0]);
99     Redefinition.setTestConfiguration(Redefinition.Config.COMMON_REDEFINE);
100 
101     // Get the Unsafe object.
102     Field f = Unsafe.class.getDeclaredField("THE_ONE");
103     f.setAccessible(true);
104     Unsafe u = (Unsafe) f.get(null);
105 
106     // Get the offsets into the original Transform class of the fields
107     long off_secret_array = genericFieldOffset(Transform.class.getDeclaredField("SECRET_ARRAY"));
108     long off_secret_number = genericFieldOffset(Transform.class.getDeclaredField("SECRET_NUMBER"));
109 
110     System.out.println("Reading normally.");
111     System.out.println("\tOriginal secret number is: " + Transform.SECRET_NUMBER);
112     System.out.println("\tOriginal secret array is: " + Arrays.toString((byte[])Transform.SECRET_ARRAY));
113     System.out.println("Using unsafe to access values directly from memory.");
114     System.out.println(
115         "\tOriginal secret number is: " + u.getLong(Transform.class, off_secret_number));
116     System.out.println(
117         "\tOriginal secret array is: "
118             + Arrays.toString((byte[]) u.getObject(Transform.class, off_secret_array)));
119 
120     // Redefine in a way that changes the offsets.
121     Redefinition.doCommonStructuralClassRedefinition(Transform.class, REDEFINED_DEX_FILE);
122 
123     // Make sure the value is the same.
124     System.out.println("Reading normally post redefinition.");
125     System.out.println("\tPost-redefinition secret number is: " + Transform.SECRET_NUMBER);
126     System.out.println("\tPost-redefinition secret array is: " + Arrays.toString((byte[])Transform.SECRET_ARRAY));
127 
128     // Get the (old) obsolete class from the ClassExt
129     Field ext_field = Class.class.getDeclaredField("extData");
130     ext_field.setAccessible(true);
131     Object ext_data = ext_field.get(Transform.class);
132     Field oc_field = ext_data.getClass().getDeclaredField("obsoleteClass");
133     oc_field.setAccessible(true);
134     Class<?> obsolete_class = (Class<?>) oc_field.get(ext_data);
135 
136     // Try reading the fields directly out of memory using unsafe.
137     System.out.println("Obsolete class is: " + obsolete_class);
138     System.out.println("Using unsafe to access obsolete values directly from memory.");
139     System.out.println(
140         "\tObsolete secret number is: " + u.getLong(obsolete_class, off_secret_number));
141     System.out.println(
142         "\tObsolete secret array is: "
143             + Arrays.toString((byte[]) u.getObject(obsolete_class, off_secret_array)));
144 
145     // Try calling all the public, non-static methods on the obsolete class. Make sure we cannot get
146     // j.l.r.{Method,Field} objects or instances.
147     TConsumer<Class> cc =
148         (Class c) -> {
149           for (Method m : Class.class.getDeclaredMethods()) {
150             if (Modifier.isPublic(m.getModifiers()) && !Modifier.isStatic(m.getModifiers())) {
151               Iterable<Object[]> iter = CollectParameterValues(m, obsolete_class);
152               System.out.println("Calling " + m + " with params: " + iter);
153               for (Object[] arr : iter) {
154                 try {
155                   System.out.println(
156                       m
157                           + " on "
158                           + safePrint(c)
159                           + " with "
160                           + deepPrint(arr)
161                           + " = "
162                           + safePrint(m.invoke(c, arr)));
163                 } catch (Throwable e) {
164                   System.out.println(
165                       m + " with " + deepPrint(arr) + " throws " + safePrint(e) + ": " + safePrint(e.getCause()));
166                 }
167               }
168             }
169           }
170         };
171     System.out.println("\n\nUsing obsolete class object!\n\n");
172     cc.accept(obsolete_class);
173     System.out.println("\n\nUsing non-obsolete class object!\n\n");
174     cc.accept(Transform.class);
175   }
176 
CollectParameterValues(Method m, Class<?> obsolete_class)177   public static Iterable<Object[]> CollectParameterValues(Method m, Class<?> obsolete_class) throws Exception {
178     Class<?>[] types = m.getParameterTypes();
179     final Object[][] params = new Object[types.length][];
180     for (int i = 0; i < types.length; i++) {
181       if (types[i].equals(Class.class)) {
182         params[i] =
183             new Object[] {
184               null, Object.class, obsolete_class, Transform.class, Long.TYPE, Class.class
185             };
186       } else if (types[i].equals(Boolean.TYPE)) {
187         params[i] = new Object[] {Boolean.TRUE, Boolean.FALSE};
188       } else if (types[i].equals(String.class)) {
189         params[i] = new Object[] {"NOT_USED_STRING", "foo", "SECRET_ARRAY"};
190       } else if (types[i].equals(Object.class)) {
191         params[i] = new Object[] {null, "foo", "NOT_USED_STRING", Transform.class};
192       } else if (types[i].isArray()) {
193         params[i] = new Object[] {new Object[0], new Class[0], null};
194       } else {
195         throw new Exception("Unknown type " + types[i] + " at " + i + " in " + m);
196       }
197     }
198     // Build the reset-iter.
199     ResetIterator<Object[]> iter = new BaseResetIter();
200     for (int i = params.length - 1; i >= 0; i--) {
201       iter = new ComboIter(Arrays.asList(params[i]), iter);
202     }
203     final Iterator<Object[]> fiter = iter;
204     // Wrap in an iterator with a useful toString method.
205     return new Iterable<Object[]>() {
206       public Iterator<Object[]> iterator() { return fiter; }
207       public String toString() { return deepPrint(params); }
208     };
209   }
210 
211   public static String deepPrint(Object[] o) {
212     return Arrays.toString(
213         Arrays.stream(o)
214             .map(
215                 (x) -> {
216                   if (x == null) {
217                     return "null";
218                   } else if (x.getClass().isArray()) {
219                     if (((Object[]) x).length == 0) {
220                       return "new " + x.getClass().getComponentType().getName() + "[0]";
221                     } else {
222                       return deepPrint((Object[]) x);
223                     }
224                   } else {
225                     return safePrint(x);
226                   }
227                 })
228             .toArray());
229   }
230 
231   public static String safePrint(Object o) {
232     if (o instanceof ClassLoader) {
233       return o.getClass().getName();
234     } else if (o == null) {
235       return "null";
236     } else if (o instanceof Exception) {
237       String res = o.toString();
238       if (res.endsWith("-transformed)")) {
239         res = res.substring(0, res.lastIndexOf(" ")) + " <transformed-jar>)";
240       } else if (res.endsWith(".jar)")) {
241         res = res.substring(0, res.lastIndexOf(" ")) + " <original-jar>)";
242       }
243       return res;
244     } else if (o instanceof Transform) {
245       return "Transform Instance";
246     } else if (o instanceof Class && isObsoleteObject((Class) o)) {
247       return "(obsolete)" + o.toString();
248     } else if (o.getClass().isArray()) {
249       return Arrays.toString((Object[])o);
250     } else {
251       return o.toString();
252     }
253   }
254 
255   private static class ComboIter implements ResetIterator<Object[]> {
256     private ResetIterator<Object[]> next;
257     private Object cur;
258     private boolean first;
259     private Iterator<Object> my_vals;
260     private Iterable<Object> my_vals_reset;
261 
262     public Object[] next() {
263       if (!next.hasNext()) {
264         cur = my_vals.next();
265         first = false;
266         if (next != null) {
267           next.reset();
268         }
269       }
270       if (first) {
271         first = false;
272         cur = my_vals.next();
273       }
274       Object[] nv = next.next();
275       Object[] res = new Object[nv.length + 1];
276       res[0] = cur;
277       for (int i = 0; i < nv.length; i++) {
278         res[i + 1] = nv[i];
279       }
280       return res;
281     }
282 
283     public boolean hasNext() {
284       return next.hasNext() || my_vals.hasNext();
285     }
286 
287     public void reset() {
288       my_vals = my_vals_reset.iterator();
289       next.reset();
290       cur = null;
291       first = true;
292     }
293 
294     public ComboIter(Iterable<Object> this_reset, ResetIterator<Object[]> next_reset) {
295       my_vals_reset = this_reset;
296       next = next_reset;
297       reset();
298     }
299   }
300 
301   public static native long genericFieldOffset(Field f);
302 
303   public static native boolean isObsoleteObject(Class c);
304 }
305