blob: 514defc2366f7addb5633a118e9714a5fa29c6a2 [file] [log] [blame]
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import art.*;
import java.lang.ref.*;
import java.lang.reflect.*;
import java.util.*;
import java.util.function.Consumer;
import sun.misc.Unsafe;
public class Main {
public static class Transform {
static {
}
public static Object SECRET_ARRAY = new byte[] {1, 2, 3, 4};
public static long SECRET_NUMBER = 42;
public static void foo() {}
}
/* Base64 for
* public static class Trasform {
* static {}
* public static Object AAA_PADDING;
* public static Object SECRET_ARRAY;
* public static long SECRET_NUMBER;
* public static void foo() {}
* public static void bar() {}
* }
*/
public static final byte[] REDEFINED_DEX_FILE =
Base64.getDecoder()
.decode(
"ZGV4CjAzNQDdmsOAlizFD4Ogb6+/mfSdVzhmL8e/mRcYBAAAcAAAAHhWNBIAAAAAAAAAAGADAAAU"
+ "AAAAcAAAAAcAAADAAAAAAQAAANwAAAADAAAA6AAAAAUAAAAAAQAAAQAAACgBAADQAgAASAEAAKwB"
+ "AAC2AQAAvgEAAMsBAADOAQAA4AEAAOgBAAAMAgAALAIAAEACAABLAgAAWQIAAGgCAABzAgAAdgIA"
+ "AIMCAACIAgAAjQIAAJMCAACaAgAAAwAAAAQAAAAFAAAABgAAAAcAAAAIAAAADQAAAA0AAAAGAAAA"
+ "AAAAAAEABQACAAAAAQAFAAoAAAABAAAACwAAAAEAAAAAAAAAAQAAAAEAAAABAAAADwAAAAEAAAAQ"
+ "AAAABQAAAAEAAAABAAAAAQAAAAUAAAAAAAAACQAAAFADAAAhAwAAAAAAAAAAAAAAAAAAmgEAAAEA"
+ "AAAOAAAAAQABAAEAAACeAQAABAAAAHAQBAAAAA4AAAAAAAAAAACiAQAAAQAAAA4AAAAAAAAAAAAA"
+ "AKYBAAABAAAADgAHAA4ABgAOAAsADgAKAA4AAAAIPGNsaW5pdD4ABjxpbml0PgALQUFBX1BBRERJ"
+ "TkcAAUoAEExNYWluJFRyYW5zZm9ybTsABkxNYWluOwAiTGRhbHZpay9hbm5vdGF0aW9uL0VuY2xv"
+ "c2luZ0NsYXNzOwAeTGRhbHZpay9hbm5vdGF0aW9uL0lubmVyQ2xhc3M7ABJMamF2YS9sYW5nL09i"
+ "amVjdDsACU1haW4uamF2YQAMU0VDUkVUX0FSUkFZAA1TRUNSRVRfTlVNQkVSAAlUcmFuc2Zvcm0A"
+ "AVYAC2FjY2Vzc0ZsYWdzAANiYXIAA2ZvbwAEbmFtZQAFdmFsdWUAdn5+RDh7ImNvbXBpbGF0aW9u"
+ "LW1vZGUiOiJkZWJ1ZyIsIm1pbi1hcGkiOjEsInNoYS0xIjoiYTgzNTJmMjU0ODg1MzYyY2NkOGQ5"
+ "MDlkMzUyOWM2MDA5NGRkODk2ZSIsInZlcnNpb24iOiIxLjYuMjAtZGV2In0AAgMBEhgCAgQCDgQJ"
+ "ERcMAwAEAAAJAQkBCQCIgATIAgGBgATcAgEJ9AIBCYgDAAAAAAACAAAAEgMAABgDAABEAwAAAAAA"
+ "AAAAAAAAAAAADwAAAAAAAAABAAAAAAAAAAEAAAAUAAAAcAAAAAIAAAAHAAAAwAAAAAMAAAABAAAA"
+ "3AAAAAQAAAADAAAA6AAAAAUAAAAFAAAAAAEAAAYAAAABAAAAKAEAAAEgAAAEAAAASAEAAAMgAAAE"
+ "AAAAmgEAAAIgAAAUAAAArAEAAAQgAAACAAAAEgMAAAAgAAABAAAAIQMAAAMQAAACAAAAQAMAAAYg"
+ "AAABAAAAUAMAAAAQAAABAAAAYAMAAA==");
private interface TConsumer<T> {
public void accept(T t) throws Exception;
}
private interface ResetIterator<T> extends Iterator<T> {
public void reset();
}
private static final class BaseResetIter implements ResetIterator<Object[]> {
private boolean have_next = true;
public Object[] next() {
if (have_next) {
have_next = false;
return new Object[0];
} else {
throw new NoSuchElementException("only one element");
}
}
public boolean hasNext() {
return have_next;
}
public void reset() {
have_next = true;
}
}
public static void main(String[] args) throws Exception {
System.loadLibrary(args[0]);
Redefinition.setTestConfiguration(Redefinition.Config.COMMON_REDEFINE);
// Get the Unsafe object.
Field f = Unsafe.class.getDeclaredField("THE_ONE");
f.setAccessible(true);
Unsafe u = (Unsafe) f.get(null);
// Get the offsets into the original Transform class of the fields
long off_secret_array = genericFieldOffset(Transform.class.getDeclaredField("SECRET_ARRAY"));
long off_secret_number = genericFieldOffset(Transform.class.getDeclaredField("SECRET_NUMBER"));
System.out.println("Reading normally.");
System.out.println("\tOriginal secret number is: " + Transform.SECRET_NUMBER);
System.out.println("\tOriginal secret array is: " + Arrays.toString((byte[])Transform.SECRET_ARRAY));
System.out.println("Using unsafe to access values directly from memory.");
System.out.println(
"\tOriginal secret number is: " + u.getLong(Transform.class, off_secret_number));
System.out.println(
"\tOriginal secret array is: "
+ Arrays.toString((byte[]) u.getObject(Transform.class, off_secret_array)));
// Redefine in a way that changes the offsets.
Redefinition.doCommonStructuralClassRedefinition(Transform.class, REDEFINED_DEX_FILE);
// Make sure the value is the same.
System.out.println("Reading normally post redefinition.");
System.out.println("\tPost-redefinition secret number is: " + Transform.SECRET_NUMBER);
System.out.println("\tPost-redefinition secret array is: " + Arrays.toString((byte[])Transform.SECRET_ARRAY));
// Get the (old) obsolete class from the ClassExt
Field ext_field = Class.class.getDeclaredField("extData");
ext_field.setAccessible(true);
Object ext_data = ext_field.get(Transform.class);
Field oc_field = ext_data.getClass().getDeclaredField("obsoleteClass");
oc_field.setAccessible(true);
Class<?> obsolete_class = (Class<?>) oc_field.get(ext_data);
// Try reading the fields directly out of memory using unsafe.
System.out.println("Obsolete class is: " + obsolete_class);
System.out.println("Using unsafe to access obsolete values directly from memory.");
System.out.println(
"\tObsolete secret number is: " + u.getLong(obsolete_class, off_secret_number));
System.out.println(
"\tObsolete secret array is: "
+ Arrays.toString((byte[]) u.getObject(obsolete_class, off_secret_array)));
// Try calling all the public, non-static methods on the obsolete class. Make sure we cannot get
// j.l.r.{Method,Field} objects or instances.
TConsumer<Class> cc =
(Class c) -> {
for (Method m : Class.class.getDeclaredMethods()) {
if (Modifier.isPublic(m.getModifiers()) && !Modifier.isStatic(m.getModifiers())) {
Iterable<Object[]> iter = CollectParameterValues(m, obsolete_class);
System.out.println("Calling " + m + " with params: " + iter);
for (Object[] arr : iter) {
try {
System.out.println(
m
+ " on "
+ safePrint(c)
+ " with "
+ deepPrint(arr)
+ " = "
+ safePrint(m.invoke(c, arr)));
} catch (Throwable e) {
System.out.println(
m + " with " + deepPrint(arr) + " throws " + safePrint(e) + ": " + safePrint(e.getCause()));
}
}
}
}
};
System.out.println("\n\nUsing obsolete class object!\n\n");
cc.accept(obsolete_class);
System.out.println("\n\nUsing non-obsolete class object!\n\n");
cc.accept(Transform.class);
}
public static Iterable<Object[]> CollectParameterValues(Method m, Class<?> obsolete_class) throws Exception {
Class<?>[] types = m.getParameterTypes();
final Object[][] params = new Object[types.length][];
for (int i = 0; i < types.length; i++) {
if (types[i].equals(Class.class)) {
params[i] =
new Object[] {
null, Object.class, obsolete_class, Transform.class, Long.TYPE, Class.class
};
} else if (types[i].equals(Boolean.TYPE)) {
params[i] = new Object[] {Boolean.TRUE, Boolean.FALSE};
} else if (types[i].equals(String.class)) {
params[i] = new Object[] {"NOT_USED_STRING", "foo", "SECRET_ARRAY"};
} else if (types[i].equals(Object.class)) {
params[i] = new Object[] {null, "foo", "NOT_USED_STRING", Transform.class};
} else if (types[i].isArray()) {
params[i] = new Object[] {new Object[0], new Class[0], null};
} else {
throw new Exception("Unknown type " + types[i] + " at " + i + " in " + m);
}
}
// Build the reset-iter.
ResetIterator<Object[]> iter = new BaseResetIter();
for (int i = params.length - 1; i >= 0; i--) {
iter = new ComboIter(Arrays.asList(params[i]), iter);
}
final Iterator<Object[]> fiter = iter;
// Wrap in an iterator with a useful toString method.
return new Iterable<Object[]>() {
public Iterator<Object[]> iterator() { return fiter; }
public String toString() { return deepPrint(params); }
};
}
public static String deepPrint(Object[] o) {
return Arrays.toString(
Arrays.stream(o)
.map(
(x) -> {
if (x == null) {
return "null";
} else if (x.getClass().isArray()) {
if (((Object[]) x).length == 0) {
return "new " + x.getClass().getComponentType().getName() + "[0]";
} else {
return deepPrint((Object[]) x);
}
} else {
return safePrint(x);
}
})
.toArray());
}
public static String safePrint(Object o) {
if (o instanceof ClassLoader) {
return o.getClass().getName();
} else if (o == null) {
return "null";
} else if (o instanceof Exception) {
String res = o.toString();
if (res.endsWith("-transformed)")) {
res = res.substring(0, res.lastIndexOf(" ")) + " <transformed-jar>)";
} else if (res.endsWith(".jar)")) {
res = res.substring(0, res.lastIndexOf(" ")) + " <original-jar>)";
}
return res;
} else if (o instanceof Transform) {
return "Transform Instance";
} else if (o instanceof Class && isObsoleteObject((Class) o)) {
return "(obsolete)" + o.toString();
} else if (o.getClass().isArray()) {
return Arrays.toString((Object[])o);
} else {
return o.toString();
}
}
private static class ComboIter implements ResetIterator<Object[]> {
private ResetIterator<Object[]> next;
private Object cur;
private boolean first;
private Iterator<Object> my_vals;
private Iterable<Object> my_vals_reset;
public Object[] next() {
if (!next.hasNext()) {
cur = my_vals.next();
first = false;
if (next != null) {
next.reset();
}
}
if (first) {
first = false;
cur = my_vals.next();
}
Object[] nv = next.next();
Object[] res = new Object[nv.length + 1];
res[0] = cur;
for (int i = 0; i < nv.length; i++) {
res[i + 1] = nv[i];
}
return res;
}
public boolean hasNext() {
return next.hasNext() || my_vals.hasNext();
}
public void reset() {
my_vals = my_vals_reset.iterator();
next.reset();
cur = null;
first = true;
}
public ComboIter(Iterable<Object> this_reset, ResetIterator<Object[]> next_reset) {
my_vals_reset = this_reset;
next = next_reset;
reset();
}
}
public static native long genericFieldOffset(Field f);
public static native boolean isObsoleteObject(Class c);
}