All checks were successful
/ test_checkout (push) Successful in 40s
93 lines
4.9 KiB
Typst
93 lines
4.9 KiB
Typst
#import "../lib.typ": todo, APK, DEX, JAR, OAT, eg
|
|
|
|
== Code Transformation <sec:th-trans>
|
|
|
|
#todo[Define code loading and reflection somewhere]
|
|
#todo[This is a draft, clean this up]
|
|
#todo[Reflectif call? Reflection call?]
|
|
|
|
In this section, we will see how we can transform the application code to make dynamic codeloading and reflexif calls analysable by static analysis tools.
|
|
|
|
=== Reflection <sec:th-trans-ref>
|
|
|
|
In Android, reflection can be used to do two things: instanciate a class, or call a method.
|
|
Either way, reflection starts by retreiving the `Class` object representing the class to use.
|
|
This class is usually retrived using a `ClassLoader` object, but can also be retrieved directly from the classloader of the class defining the calling method.
|
|
// elaborate? const-class dalvik instruction / MyClass.class in java?
|
|
One the class is retrieve, it can be instanciated using the deprecated method `Class.newInstance()`, like shown in @lst:-th-expl-cl-new-instance, or a specific method can be retrieved.
|
|
The current approche to instanciate a class is to retrieve the specific `Constructor` object, then calling `Constructor.newInstance(..)` like in @lst:-th-expl-cl-cnstr.
|
|
Similarly, to call a method, the `Method` object must be retrieved, then called using `Method.invoke(..)`, like shown in @lst:-th-expl-cl-call.
|
|
|
|
Although the process seems to differ between class instanciation and method call from the Java stand point, the runtime opperations are very similar.
|
|
When instanciating an object with `Object obj = cst.newInstance("Hello Void")`, the constructor method `<init>(Ljava/lang/String;)V`, represented by the `Constructor` `cst`, is called on the object `obj`.
|
|
|
|
#figure(
|
|
```java
|
|
ClassLoader cl = MainActivity.class.getClassLoader();
|
|
Class clz = cl.loadClass("com.example.Reflectee");
|
|
Object obj = clz.newInstance();
|
|
```,
|
|
caption: [Instanciating a class using `Class.newInstance()`]
|
|
) <lst:-th-expl-cl-new-instance>
|
|
|
|
#figure(
|
|
```java
|
|
Constructor cst = clz.getDeclaredConstructor(String.class);
|
|
Object obj = cst.newInstance("Hello Void");
|
|
```,
|
|
caption: [Instanciating a class using `Constructor.newInstance(..)`]
|
|
) <lst:-th-expl-cl-cnstr>
|
|
|
|
#figure(
|
|
```java
|
|
Method mth = clz.getMethod("myMethod", String.class);
|
|
String retData = (String) mth.invoke(obj, "an argument");
|
|
```,
|
|
caption: [Calling a method using reflection]
|
|
) <lst:-th-expl-cl-call>
|
|
|
|
To allow static analysis tools to analyse an application that use reflection, we want to replace the reflection call by the bytecode that does the actual calls.
|
|
|
|
One of the main reason to use reflection is to access classes not from the application.
|
|
Although allows the use classes that do not exist in the application in bytecode, at runtime, if the classes are not found in the current classloader, the application will crash.
|
|
Similarly, some analysis tools might have trouble analysis application calling non existing classes.
|
|
@sec:th-trans-cl deals with the issue of adding dynamically loaded bytecode to the application.
|
|
|
|
A notable issue is that a specific reflection call can call different methods.
|
|
@lst:th-worst-case-ref illustrate a worst case scenario where any method can be call at the same reflection call.
|
|
In those situation, we cannot garanty that we know all the methodes that can be called (#eg the name of the method called could be retrieved from a remote server).
|
|
|
|
|
|
#figure(
|
|
```java
|
|
Object myInvoke(Object obj, Method mth, Object[] args) throws .. {
|
|
return mth.invoke(obj, args);
|
|
}
|
|
```,
|
|
caption: [A reflection call that can call any method]
|
|
) <lst:th-worst-case-ref>
|
|
|
|
=== Code loading <sec:th-trans-cl>
|
|
|
|
#todo[custom class loaders]
|
|
|
|
An application can dynamically import code from several format like #DEX, #APK, #JAR or #OAT, either stored in memory or in a file.
|
|
Because it is an internal, platform dependant format, we elected to ignore the #OAT format.
|
|
Practically, #JAR and #APK files are zip files containing #DEX files.
|
|
This means that we only need to find a way to integrate #DEX files to the application.
|
|
|
|
We elected to simply add the dex files to the application, using the multi-dex feature introduced by the SDK 21 now used by all applications.
|
|
This gives access to the dynamically loaded code to static analysis tool.
|
|
|
|
#todo[add drawing of dex insertion]
|
|
|
|
We decided to leave untouched the original code that load the bytecode.
|
|
At runtime, although the bytecode is already present in the application, the application will still dynamically load the code.
|
|
This ensure that the application keep working as intended even if the transformation we applied are incomplete.
|
|
Specifically, to call dynamically loaded code, an application needs to use reflection, and we saw in @sec:th-trans-ref that we need to keep reflecton calls, and in order to keep reflection calls, we need the classloader created when loading bytecode.
|
|
|
|
=== Class Collisions <sec:th-class-collision>
|
|
|
|
=== Pitfalls
|
|
|
|
#todo[interupting try blocks: catch block might expect temporary registers to still stored the saved value]
|