diff --git a/test_apks/reflection/.gitignore b/test_apks/reflection/.gitignore new file mode 100644 index 0000000..de18d4e --- /dev/null +++ b/test_apks/reflection/.gitignore @@ -0,0 +1,3 @@ +build +ToyKey.keystore +java/classes/com/example/theseus/reflection/R.java diff --git a/test_apks/reflection/AndroidManifest.xml b/test_apks/reflection/AndroidManifest.xml new file mode 100644 index 0000000..a86febb --- /dev/null +++ b/test_apks/reflection/AndroidManifest.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + diff --git a/test_apks/reflection/Makefile b/test_apks/reflection/Makefile new file mode 100644 index 0000000..f2471a3 --- /dev/null +++ b/test_apks/reflection/Makefile @@ -0,0 +1,60 @@ +VERSION=34.0.0 +SDK_TOOLS=$(HOME)/Android/Sdk +JAVA_PATH=/usr/lib/jvm/java-17-openjdk/bin +JAVAC=/usr/lib/jvm/java-17-openjdk/bin/javac +JAR=/usr/lib/jvm/java-17-openjdk/bin/jar +PYTHON=python3 +APP=test_reflection + +PACKAGE=com.example.theseus.reflection +MAIN_ACTIVITY=MainActivity + + +VERSION_B=$(basename $(basename $(VERSION))) + +pass=ahahah + +export PATH := $(JAVA_PATH):$(PATH) + +all: $(shell mkdir -p build) +all: clean build/$(APP).apk +signature_v1: clean build/$(APP).v1.apk + +test: all + adb install build/$(APP).apk + adb shell am start -n $(PACKAGE)/.$(MAIN_ACTIVITY) + +build/%.v1signed.apk: ./build/%.unsigned.apk ./ToyKey.keystore + jarsigner -verbose -keystore ./ToyKey.keystore -storepass $(pass) -keypass $(pass) -signedjar $@ $< SignKey + +build/%.v1.apk: ./build/%.v1signed.apk + $(SDK_TOOLS)/build-tools/$(VERSION)/zipalign -v -f 4 $< $@ + +# TODO: fix dep somehow? cannot find a way to use % or $* in (shell ..) +build/%/classes: $(shell find java/ -type f -regex ".*\.java" ) + mkdir -p ./build/$*/classes + $(JAVAC) -d ./build/$*/classes -classpath build/deps.jar:$(SDK_TOOLS)/platforms/android-$(VERSION_B)/android.jar $$(find java/$*/ -type f -regex ".*\.java") + +build/%/classes.dex: build/%/classes + mkdir -p ./build/$* + $(SDK_TOOLS)/build-tools/$(VERSION)/d8 --classpath $(SDK_TOOLS)/platforms/android-$(VERSION_B)/android.jar $(shell find build/$*/classes -type f -regex ".*\.class" -printf "'%p'\n") --output ./build/$*/ + +build/%.unsigned.apk: build/classes/classes.dex + mkdir -p ./build/$*_files + mv ./build/classes/classes.dex ./build/$*_files/classes.dex + $(SDK_TOOLS)/build-tools/$(VERSION)/aapt package -v -f -M ./AndroidManifest.xml -I $(SDK_TOOLS)/platforms/android-$(VERSION_B)/android.jar -F $@ ./build/$*_files + +build/%.v2aligned.apk: ./build/%.unsigned.apk ./ToyKey.keystore + $(SDK_TOOLS)/build-tools/$(VERSION)/zipalign -v -f 4 $< $@ + +build/%.apk: ./build/%.v2aligned.apk + $(SDK_TOOLS)/build-tools/$(VERSION)/apksigner sign -ks ./ToyKey.keystore --v2-signing-enabled true --in $< --out $@ --ks-pass pass:$(pass) + +ToyKey.keystore : + keytool -genkeypair -validity 1000 -dname "CN=SomeKey,O=SomeOne,C=FR" -keystore $@ -storepass $(pass) -keypass $(pass) -alias SignKey -keyalg RSA -v + +clean: + $(RM) -r build/* + +clean_all: clean + $(RM) ToyKey.keystore diff --git a/test_apks/reflection/java/classes/com/example/theseus/reflection/MainActivity.java b/test_apks/reflection/java/classes/com/example/theseus/reflection/MainActivity.java new file mode 100644 index 0000000..a351fba --- /dev/null +++ b/test_apks/reflection/java/classes/com/example/theseus/reflection/MainActivity.java @@ -0,0 +1,94 @@ +package com.example.theseus.reflection; + +import android.app.Activity; +import android.os.Bundle; +import android.util.Log; + +import java.lang.ClassLoader; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Constructor; +import java.lang.ClassNotFoundException; + +import com.example.theseus.Utils; + +public class MainActivity extends Activity { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + try { + callVirtualMethod(); + callVirtualMethodReflectCall(); + callConstructorVirtualMethodReflectConstr(); + callVirtualMethodReflectOldConst(); + } catch(Exception e) { + Log.e("THESEUS", "Error: ", e); + } + } + + // A normal virtual method call + public void callVirtualMethod() { + String data = Utils.source("no reflect virt call"); + Reflectee r = new Reflectee("R1"); + String newData = r.transfer(data); + Utils.sink(this, newData); + } + + // A call to a virtual method through reflection + public void callVirtualMethodReflectCall() throws + ClassNotFoundException, + NoSuchMethodException, + IllegalAccessException, + InvocationTargetException + { + String data = Utils.source("reflect virt call"); + Reflectee r = new Reflectee("R2"); + ClassLoader cl = MainActivity.class.getClassLoader(); + Class clz = cl.loadClass("com.example.theseus.reflection.Reflectee"); + Method mth = clz.getMethod("transfer", String.class); + String newData = (String) mth.invoke(r, data); + Utils.sink(this, newData); + } + + // A call to a virtual method through reflection using an object instanciated + // through reflection. The sensitive data is passed to the constructor. + public void callConstructorVirtualMethodReflectConstr() throws + ClassNotFoundException, + NoSuchMethodException, + IllegalAccessException, + InvocationTargetException, + InstantiationException + { + String data = Utils.source("no reflect constr"); + ClassLoader cl = MainActivity.class.getClassLoader(); + Class clz = cl.loadClass("com.example.theseus.reflection.Reflectee"); + Constructor cst = clz.getDeclaredConstructor(String.class); + Object r = cst.newInstance(data); + Method mth = clz.getMethod("transfer", String.class); + String newData = (String) mth.invoke(r, ""); + Utils.sink(this, newData); + } + + // A call to a virtual method through reflection using an object instanciated + // through reflection using a deprecated method. + public void callVirtualMethodReflectOldConst() throws + ClassNotFoundException, + NoSuchMethodException, + IllegalAccessException, + InvocationTargetException, + InstantiationException + { + String data = Utils.source("no reflect constr"); + ClassLoader cl = MainActivity.class.getClassLoader(); + Class clz = cl.loadClass("com.example.theseus.reflection.Reflectee"); + Object r = clz.newInstance(); + Method mth = clz.getMethod("transfer", String.class); + String newData = (String) mth.invoke(r, data); + Utils.sink(this, newData); + } + + // TODO: many argument methods + // TODO: static + // TODO: factory patern +} diff --git a/test_apks/reflection/java/classes/com/example/theseus/reflection/Reflectee.java b/test_apks/reflection/java/classes/com/example/theseus/reflection/Reflectee.java new file mode 100644 index 0000000..b0f97fb --- /dev/null +++ b/test_apks/reflection/java/classes/com/example/theseus/reflection/Reflectee.java @@ -0,0 +1,19 @@ +package com.example.theseus.reflection; + + +public class Reflectee { + + String name; + + public Reflectee() { + this.name = ""; + } + + public Reflectee(String name) { + this.name = "[" + name + "] "; + } + + public String transfer(String data) { + return name + data; + } +} diff --git a/test_apks/reflection/java/classes/com/example/theseus/reflection/Utils.java b/test_apks/reflection/java/classes/com/example/theseus/reflection/Utils.java new file mode 100644 index 0000000..0c8ca2a --- /dev/null +++ b/test_apks/reflection/java/classes/com/example/theseus/reflection/Utils.java @@ -0,0 +1,26 @@ +package com.example.theseus; + +import android.app.Activity; +import android.app.AlertDialog; + + +public class Utils { + public static String source() { + return "Secret"; + } + public static String source(String tag) { + return "[" + tag + "] Secret"; + } + + public static void popup(Activity ac, String title, String msg) { + (new AlertDialog.Builder(ac)) + .setMessage(msg) + .setTitle(title) + .create() + .show(); + } + + public static void sink(Activity ac, String data) { + popup(ac, "Data leak:", data); + } +}