This commit is contained in:
Jean-Marie 'Histausse' Mineau 2025-10-07 04:30:38 +02:00
parent 2d5cb2459e
commit 590b446f15
Signed by: histausse
GPG key ID: B66AEEDA9B645AD2
5 changed files with 54 additions and 18 deletions

View file

@ -225,28 +225,31 @@ The pseudo-code in @lst:renaming-algo shows the three steps of this algorithm:
* #todo[interupting try blocks: catch block might expect temporary registers to still stored the saved value] ?
*/
=== Implementation Details
=== Implementation Details <sec:th-implem>
Most of the contributions we saw performing instrumentation in the state of the art rely on Soot.
Our initial idea was to use Apktool, but in @sec:rasta, we found that many errors raised by tools were due to trying to parse Smali incorrectly.
Thus, we decided to avoid Apktool.
Most of the contributions of the state of the art that perform instrumentation rely on Soot.
Soot works on an intermediate representation, Jimple, that is easier to manipulate.
However, Soot can be cumbersome to set up and use, and we initially wanted better control over the modified bytecode.
Our initial idea was to use Apktool, but in @sec:rasta, we found that many errors raised by tools were due to trying to parse Smali incorrectly.
In addition, although it might be due to the fact that they performed more complex analysis, tools based on Soot showed a trend of consuming a lot of memory and failing with unclear errors, supporting us in our idea of avoiding Soot.
For these reasons, we decided to make our own instrumentation library from scratch.
That library requires being able to parse, modify and generate valid #DEX files.
That library, Androscalpel, requires being able to parse, modify and generate valid #DEX files.
It was not as difficult as one would expect, thanks to the clear documentation of the Dalvik format from Google#footnote[https://source.android.com/docs/core/runtime/dex-format].
In addition, when we had doubts about the specification, we had the option to check the implementation used by Apktool#footnote[https://github.com/JesusFreke/smali], or the code used by Android to check the integrity of the #DEX files#footnote[https://cs.android.com/android/platform/superproject/main/+/main:art/libdexfile/dex/dex_file_verifier.cc;drc=11bd0da6cfa3fa40bc61deae0ad1e6ba230b0954].
We chose to use Rust to implement this library.
It has both good performance and ergonomics.
For instance, we could parallelise the parsing and generation of #DEX files without much effort.
Because we are not using a high-level intermediate language like Jimple (used by Soot), the management of registers has to be done manually (by the user of the library), the same way it has to be done when using Apktool.
Because we are not using a high-level intermediate language like Jimple (used by Soot), the management of the Dalvik registers in the methods has to be done manually (by the user of the library), the same way it has to be done when using Apktool.
This poses a few challenges.
A method declares a number of internal registers it will use (let's call this number $n$), and has access to an additional number of registers used to store the parameters (let's call this number $p$).
Each register is referred to by a number from $0$ to $65535$.
The internal registers are numbered from $0$ to $n$, and the parameter registers from $n$ to $n+p$.
This means that when adding new registers to the method when instrumenting it (let's say we want to add $k$ registers, the new registers will be numbered from $n$ to $n+k$, and the parameter registers will be renumbered from $[|n, n+p[|$ to $[|n+k, n+k+p[|$.
This means that when adding new registers to the method when instrumenting it (let's say we want to add $k$ registers), the new registers will be numbered from $n$ to $n+k$, and the parameter registers will be renumbered from $[|n, n+p[|$ to $[|n+k, n+k+p[|$.
In general, this is not an issue, but some instructions can only operate on some registers (#eg `array-length`, which stores the length of an array in a register, only works on registers numbered between $0$ and $8$ excluded).
This means that adding registers to a method can be enough to break a method.
We solved this by adding instructions that move the content of registers $[|n+k, n+k+p[|$ to the registers $[|n, n+p[|$, and keeping the original register numbers ($[|n, n+p[|$) for the parameters in the rest of the body of the method.
@ -254,7 +257,7 @@ We solved this by adding instructions that move the content of registers $[|n+k,
The next challenge arises when we need to use one of the new registers with an instruction that only accepts registers lower than $n+p$.
In such cases, a lower register must be used, and its content will be temporarily saved in one of the new registers.
This is not as easy as it seems: the Dalvik instructions differ depending on whether the register stores a reference or a scalar value, and Android does check that the register types match the instructions.
The type of the register can be computed from the control flow graph of the method (we added the computation of such a graph, with the type of each register, as a feature in our library).
The type of the register can be computed from the control flow graph of the method (we added the computation of such a graph, with the type of each register, as a feature in Androscalpel).
An edge case that must not be overlooked is that each instruction inside a `try` block is branching to each of the `catch` blocks.
This is a problem: it prevents us from restoring the registers to their original values before entering the `catch` blocks (or, if we restore the values at the beginning of the `catch` blocks and an exception is raised before the value is saved, the register will be overwritten by an invalid value).
This means that when modifying the content of a `try` block, the block must be split into several blocks to prevent impromptu branching.
@ -264,7 +267,7 @@ We also found that some applications deliberately store files with names that wi
For this reason, we also used our own library to modify the #APK files.
We took special care to process the least possible files in the #APKs, and only strip the #DEX files and signatures, before adding the new modified #DEX files at the end.
Unfortunately, we did not have time to compare the robustness of our solution to existing tools like Apktool and Soot.
Unfortunately, we did not have time to compare the robustness of our solution to existing tools like Apktool and Soot, but we did a quick performance comparison, summarised in @sec:th-lib-perf.
In hindsight, we probably should have taken the time to find a way to use smali/backsmali (the backend of Apktool) as a library or use SootUp to do the instrumentation, but neither option has documentation to instrument applications this way.
At the time of writing, the feature is still being developed, but in the future, Androguard might also become an option to modify #DEX files.
Nevertheless, we published our instrumentation library, Androscalpel, for anyone who wants to use it. #todo[ref to code]

View file

@ -298,6 +298,38 @@ In red on the figure however, we have the calls that were hidded by reflection i
caption: [Call Graph of `Main.main()` generated by Androguard after patching],
) <fig:th-cg-after>
=== Androscalpel Performances <sec:th-lib-perf>
Because we implemented our own instrumentation library, we wanted to compare it to other existing options.
Unfortunately, we did not have time to compare the robustness and correctness of the generated applications.
However, we did compare the performances of our library, Androscalpel, to Apktool and Soot.
Due to time constraints, we could not test a complex transformation, as adding registers requires complex operations for both Androscalpel and Apktool (see @sec:th-implem for more details).
We decided to test two operations: travelling the instructions of an application (a read-only operation), and regenerating an application, without modification (a read/write operation).
It should be noted that all three of the tested tools have multiprocessing support, but we disabled the option when testing the generation of an application with Soot, as it raised errors.
#figure({
let nb_col = 5
table(
columns: (1fr, 1fr, 1fr, 1fr, 1fr),
align: center+horizon,
table.header(
table.cell(colspan: 2)[Tool], [Soot], [Apktool], [Androscalpel],
),
table.cell(rowspan: 2)[Read],
[Time], [], [], [],
[Mem], [], [], [],
table.cell(rowspan: 2)[Read/Write],
[Time], [], [], [],
[Mem], [], [], [],
)},
caption: [Average time and memory consumption of Soot, Apktool and Androscalpel]
) <tab:th-compare-perf>
@tab:th-compare-perf compares the resources consumed by each tool for each operation.
#todo[Conlude depending on the results of the experiment]
#midskip
To conclude, we showed that our approach indeed improves the results of analysis tools without impacting their finishing rates much.

View file

@ -78,6 +78,6 @@ Beyond the classic comparison of static versus dynamic, DroidRA has a similar go
Two notable comparison criteria would be the failure rate and the number of edges added to an application call graph.
The first criterion indicates how much the results can be used by other tools, while the second indicates how effective the approaches are.
Because we elected to make our own software to modify the bytecode of the #APKs, it would be insightful to compare the finishing rate and performances of simple transformations with our tool, to the same transformation made with Apktool, Soot or SootUp.
Because we elected to make our own software to modify the bytecode of the #APKs, it would be insightful to compare the finishing rate and performances of simple transformations with our tool, to the same transformation made with Apktool, Soot or SootUp (we only compared the performances for re-generating and application without transformations).
An example of a transformation to test would be to log each method call and its return value.
More than finding which solution is the best to instrument an application, this would allow us to compare the weaknesses of each tool and find if some recurring issues for some tools can be solved using a technical solution implemented by another tool (#eg some applications deliberately include files with names that crash the standard Java zip library).