thesis/5_theseus/5_limits.typ
Jean-Marie Mineau 7f61637b64
Some checks failed
/ test_checkout (push) Failing after 0s
wip
2025-09-02 17:34:12 +02:00

49 lines
4.9 KiB
Typst

#import "../lib.typ": paragraph, ART, DEX, APK
#import "../lib.typ": todo, jfl-note, jm-note
== Limitations <sec:th-limits>
#todo[Structure the section]
#paragraph()[Custom Classloaders][
#jfl-note(side: right)[The first obvious limitation is that we do not know what custom classloadrs do, so we cannot accuratly reproduce statically their behavior.][est ce que c'est une limite des 2 transformations proposées? j'ai l'impression que tu veux faire une 3ieme transformation]
We elected to fallback to the behavior of the `BaseDexClassLoader`, which is the highest Android specific classloader in the inheritance hierarchy, and whose behavior is shared by all classloaders safe `DelegateLastClassLoader`.
The current implementation of the #ART enforce some restrictions on the classloaders behavior to optimize the runtime performance by caching classes.
This gives us some garanties that custom classesloaders will keep a some coherences will the classic classloaders.
For instance, a class loaded dynamically must have the same name as the name used in `ClassLoader.loadClass()`.
This make `BaseDexClassLoader` a good estimation for legitimate classloaders, however, an obfuscated application could use the techniques discussed in @sec:cl-cross-obf, in wich case our model would be entirelly wrong.
]
#paragraph()[Multiple Classloaders for one `Method.invoke()`][
Although we managed to handle call to different methods from one `Method.invoke()` site, we do not handle calling methods from different classloaders with colliding classes definition.
The first reason is that it is quite challenging to compare classloaders statically.
At runtime, each object has an unique identifier that can be used to compare them over the course of the same execution, but this identifier is reset each time the application starts.
This means we cannot use this identifier in an `if` condition to differentiate the classloaders.
Ideally, we would combine the hash of the loaded #DEX files, the classloader class and parent to make an unique, static identifier, but the #DEX files loaded by a classloader cannot be accessed at runtime without accessing the process memory at arbitrary locations.
For some classloaders, the string representation returned by `Object.toString()` list the location of the loaded #DEX file on the file system.
This is not the case for the commonly used `InMemoryClassLoader`.
In addition, the #DEX files are often located in the application private folder, whose name is derived from the hash of the #APK itself.
Because we modify the application, the path of the private folder also change, and so will the string representation of the classloaders.
Checking the classloader of a classes can also have side-effect on classloaders that delegate to the main application classloader:
because we inject the classes in the #APK, the classes of the classloader are now already in the main application classloader, which in most case will have priority on the other classloaders, and lead to the class beeing loaded by the application classloader instead of the original classloader.
If we check for the classloader, we would need to considere such cases en rename each classes of each classloader before reinjecting them to the in the application.
This would greatly increase the risk of breaking the application during its transformation.
Instead, we elected to ignore the classloaders when selecting the method to invoque.
This leads to potential invalid runtime behaviore, as the first method that matching the class name will be called, but the alternative methods from other classloader still appears in the new application, albeit in a block that might be flagged as dead-code by a sufficiently advenced static analyser.
]
#paragraph()[`ClassNotFoundException` may not be raised][
In the very specific situation where the original application tries to access a class from dynamically loaded bytecode without actually accessing this bytecode, the patched application behavior will differ.
The original application should raise a `ClassNotFoundException`, but in the patched application, the class will be accible and the exception will not be raised.
In pactice, their is not a lot of reason to do such thing.
One could be to check if the #APK as been tempered with, but their are easier ways to do thins, like checking the application signature.
#jm-note[Exception oriented programming worth mentioning? ]
Another would be to check if the class is already available, and if not, load it dynamically, in wich case it does not matter as code loaded dynamically is already present.
In any cases, statically, because we remove neither the calls to the function that load the classes (like `ClassLoader.loadClass(..)`) nor the `try` / `catch` blocks, static analysis tools those can handle the original behavior should still be hable to access the old behavior.
]
#todo[
- Use multidex: min SDK >= 21 (android 5.0, published in 2014, should be ok)
- No support for OAT (platform dependent)
]