wip
All checks were successful
/ test_checkout (push) Successful in 40s

This commit is contained in:
Jean-Marie Mineau 2025-07-04 13:38:17 +02:00
parent c87fe714a9
commit d02129a531
Signed by: histausse
GPG key ID: B66AEEDA9B645AD2
2 changed files with 64 additions and 4 deletions

View file

@ -41,7 +41,8 @@ When instanciating an object with `Object obj = cst.newInstance("Hello Void")`,
#figure(
```java
Method mth = clz.getMethod("myMethod", String.class);
String retData = (String) mth.invoke(obj, "an argument");
Object[] args = {(Object)"an argument"}
String retData = (String) mth.invoke(obj, args);
```,
caption: [Calling a method using reflection]
) <lst:-th-expl-cl-call>
@ -55,8 +56,7 @@ Similarly, some analysis tools might have trouble analysis application calling n
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).
In those situation, we cannot garanty that we know all the methods that can be called (#eg the name of the method called could be retrieved from a remote server).
#figure(
```java
@ -67,6 +67,66 @@ In those situation, we cannot garanty that we know all the methodes that can be
caption: [A reflection call that can call any method]
) <lst:th-worst-case-ref>
To handle those situation, instead of entirely removing the reflection call, we can modify the application code to test if the `Method` (or `Constructor`) object match any expected method, and if yes, directly call the method.
If the object does not match any expected method, the code can fallback to the original reflection call.
@lst:-th-expl-cl-call-trans demonstrate this transformation on @lst:-th-expl-cl-call.
It should be noted that we do the transformation at the bytecode level, the code in the listing correspond to the output of JADX #todo[Ref to list of common tools?] reformated for readability.
The method check is done in a separate method injected inside the application to avoid clutering the application too much.
Because Java (and thus Android) uses polymorphic methods, we cannot just check the method name and its class, but also the whole method signature.
We chose to limit the transformation to the specific instruction that call `Method.invoke(..)`.
This drastically reduce the risks of breaking the application, but leads to a lot of type casting.
Indeed, the reflection call uses the generic `Object` class, but actual methods usually use specific classes (#eg `String`, `Context`, `Reflectee`) or scalar types (#eg `int`, `long`, `boolean`).
This means that the method parameters and object on which the method is called must be downcast to their actual type before calling the method, then the returned value must be upcasted back to an `Object`.
Scalar types especially require special attention.
Java (and Android) distinguish between scalar type and classes, and they cannot be mixed: a scalar cannot be cast into an `Object`.
However, each scalar type has an associated class that can be use when doing reflection.
For example, the scalar type `int` is associated with the class `Integer`, the method `Integer.valueOf()` can convert an `int` scalar to an `Integer` object, and the method `Integer.intValue()` convert back an `Integer` object to an `int` scalar.
Each time the method called by reflection used scalars, the scalar-object convertion must be made before calling it.
And finally, because the instruction following the reflection call expect an `Object`, the return value of the method must be cast into an `Object`.
This back and forth between types might confuse some analysis tools.
This could be improved in futur works by analysing the code around the reflection call.
For example, if the result of the reflection call is imediatly cast into the expected type (#eg in @lst:-th-expl-cl-call, the result is cast to a `String`), they should not be any need to cast it to Object in between.
Similarly, it is common to have the method parameter arrays generated just before the reflection call never be used again (This is due to `Method.invoke(..)` beeing a varargs method: the array can be generated by the compiler at compile time).
In those cases, the parameters could be used directly whithout the detour inside an array.
#figure(
```java
class T {
static boolean check_is_reflectee_mymethod_e398(Method mth) {
Class<?>[] paramTys = mth.getParameterTypes();
return (
meth.getName().equals("myMethod") &&
paramTys.length == 1 &&
paramTys[0].descriptorString().equals(
String.class.descriptorString()
) &&
mth.getReturnType().descriptorString().equals(
String.class.descriptorString()
) &&
mth.getDeclaringClass().descriptorString().equals(
Reflectee.class.descriptorString()
)
)
}
}
...
Method mth = clz.getMethod("myMethod", String.class);
Object[] args = {(Object)"an argument"}
Object objRet;
if (T.check_is_reflectee_mymethod_e398abf7d3ce6ede(mth)) {
objRet = (Object) ((Reflectee) obj).myMethod((String)args[0]);
} else {
objRet = mth.invoke(obj, args);
}
String retData = (String) objRet;
```,
caption: [@lst:-th-expl-cl-call after the de-reflection transformation]
) <lst:-th-expl-cl-call-trans>
=== Code loading <sec:th-trans-cl>
#todo[custom class loaders]

View file

@ -1,6 +1,6 @@
#import "@local/template-thesis-matisse:0.0.1": todo
#let keywords-en = ("Android", "malware analysis", "static analysis", "class loading", "code obfuscation", todo[More Keywords])
#let keywords-en = ("Android", "malware analysis", "static analysis", "class loading", "code obfuscation", todo[Keywords])
#let keywords-fr = ("Android", "analyse de maliciels", "analyse statique", "chargement de classe", "brouillage de code")