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