Compare commits

..

2 commits

Author SHA1 Message Date
Jean-Marie Mineau
ad66b1293d
wip
All checks were successful
/ test_checkout (push) Successful in 56s
2025-07-19 02:00:25 +02:00
Jean-Marie Mineau
a1a5794250
wip 2025-07-17 21:34:01 +02:00
6 changed files with 266 additions and 10 deletions

View file

@ -1,4 +1,4 @@
#import "../lib.typ": todo, APK, DEX, JAR, OAT, eg, ART, paragraph #import "../lib.typ": todo, APK, DEX, JAR, OAT, eg, ART, paragraph, jm-note
/* /*
* Parler de dex lego et du papier qui encode les resultats d'anger en jimple * Parler de dex lego et du papier qui encode les resultats d'anger en jimple
@ -166,7 +166,7 @@ When loaded dynamically, the classes are in a different classloader, and the cla
We decided to restrain our scope to the use of class loader from the Android SDK. We decided to restrain our scope to the use of class loader from the Android SDK.
In the abscence of class collision, those class loader behave seamlessly and adding the classes to application maintains the behavior. In the abscence of class collision, those class loader behave seamlessly and adding the classes to application maintains the behavior.
#todo[this is redundant an messy:] #jm-note[
When we detect a collision, we rename one of the classes colliding in order to be able to differenciate both classes. When we detect a collision, we rename one of the classes colliding in order to be able to differenciate both classes.
To avoid breaking the application, we then need to rename all references to this specific class, an be carefull not to modify references to the other class. To avoid breaking the application, we then need to rename all references to this specific class, an be carefull not to modify references to the other class.
To do so, we regroup each classes by the classloaders defining them, then, for each colliding class name and each classloader, we check the actual class used by the classloader. To do so, we regroup each classes by the classloaders defining them, then, for each colliding class name and each classloader, we check the actual class used by the classloader.
@ -177,6 +177,7 @@ The pseudo-code in @lst:renaming-algo show the three steps of this algorithm:
- first we detect collision and rename classes definitions to remove the collisions - first we detect collision and rename classes definitions to remove the collisions
- then we rename the reference to the colliding classes to make sure the right classes are called - then we rename the reference to the colliding classes to make sure the right classes are called
- ultimately, we merge the modified dexfiles of each class loaders into one android application - ultimately, we merge the modified dexfiles of each class loaders into one android application
][this is redundant an messy]
#figure( #figure(
```python ```python

View file

@ -1,13 +1,69 @@
#import "../lib.typ": todo #import "../lib.typ": todo, SDK, API, ART, DEX, APK, JAR, ADB
== Collection Runtime Information <sec:th-dyn> == Collecting Runtime Information <sec:th-dyn>
In order to perform the transformations described in @sec:th-trans, we need information like the name and signature of the method called with reflection, or the actual bytecode loaded dynamically. In order to perform the transformations described in @sec:th-trans, we need information like the name and signature of the method called with reflection, or the actual bytecode loaded dynamically.
We are doing those transformation specifically because those information are difficult to extract statically. We are doing those transformation specifically because those information are difficult to extract statically.
Hence, we are using dynamic analysis to collect the runtime information we need. Hence, we are using dynamic analysis to collect the runtime information we need.
We use Frida(see @sec:bg-frida) to instrument the application and intercept calls of specific methods.
=== Collect Bytecode === Collecting Bytecode Dynamically Loaded
=== Collect Reflection Data Initially, we considered instrumenting the constructor methods of the classloaders of the Android #SDK.
However, this is a significant number of methods to instrument, and looking at older application, we realized that we missed the `DexFile` class.
`DexFile` is a now depreciated class but still usable class that can be used to load bytecode dynamically.
Instead of looking for all possible methods to load bytecode, we decided to look at the #ART source code an list all the places where the internal function used to parse bytecode is called.
We found that all those calls are from under either `DexFile.openInMemoryDexFilesNative(..)` or `DexFile.openDexFileNative(..)`, two hidden #API methods.
Those methods takes as argument a list of Androis code files, either in the form of in memory byte arrays or file path, and a reference to the classloader associated to the code.
The code files can have many format, usually #DEX files, or #APK / #JAR files containing #DEX files, but it can also be internal format like `.aot` #todo[check, aot explain somewhere?]. #todo[cf later to explain that only #DEX / #APK / #JAR are found?]
Instrumenting those methods allows us to collect all the #DEX files loaded by the #ART and associate them to their classloaders.
=== Collecting Reflection Data
Like describe in @sec:th-trans-ref, they are 3 methods that we need to instrument to capture reflection calls: `Class.newInstance()`, `Constructor.newInstance(..)` and `Method.invoke(..)`.
Because Java has polymorphism, we need not only the method name and defining class, but also the whole signature of the method.
In addition to that, in case they are several classes with the same name as the defining class, we also need the classloader of the defining class to distinguish it from the other classes.
A more challenging information to collect is the from where the reflection method is called.
In order to correctly modify the application, we need to know which specific call to a reflection method we intercepted.
Specifically, we need to known the caller method (once again, we need the method name, full signature, defining class and its classloader), and the speficic instruction that called the reflection method (in case the caller method call a reflection method several times).
This information is more difficult to collect than one would expect.
Those information are stored in the stack, but before the #SDK 34, the stack was not directly accessible programmatically.
Historically, when a reverse engineer needed to access the stack, they would trigger and catch an exception, get the stack from the exception.
The issue with this approche is that the data stored in the exception are meant for debbuging.
In particullar, the location of the call in the bytecode has a different meaning depending on the debug information encoded in the bytecode.
It can either be the address of the bytecode instruction invoking the callee method in the instruction array of the caller method, or the line number of original source code that call the callee method.
In the #SDK 34, Android introduced the `StackWalker` #API.
This #API allow to programatically travel the current stack and retrieve informations from it, including the bytecode address of the instruction calling the callee methods.
Considering that the line number is not a reliable information, we chose to use the new #API, despite the restriction that come with chosing such a recent Android version (it was released in october 2023, arround 2 years ago, and less than 50% of the current Android market share support this #API today #todo[archive ref https://gs.statcounter.com/android-version-market-share]).
=== Application Execution
Dynamic analysis requires actually running the application.
In order to test automatically multiple applications, we needed to simulate human interractions with the applications.
We found a few tools available, #todo[ref les outils testé, peut etre mettre dans state of the art?].
After some tests, the most suitable one we found were the Monkey, a standard Android tool from Google that generate random event, and GroddDroid #todo[ref].
We choose to avoir the Monkey because we noticed that it will often trigger event that will close the application (events likes pressing the 'home' button, or openning the general setting drop-down menu at the top of the screen).
GroddDroid different execution modes.
We choosed to use the most simple one, that explore the application following a depth-first search algorithm.
GroddDroid can do more advance explorations targetting suspicious section of the application en priority, but this require to perform heavy static analysis.
We elected to avoid this option to keep the exploiration lightwight and limit the chance to encontering a fatal issue.
Behind the scene, GroddDroid uses UI Automator to interact with the application, an standar Android API used intended for automatic testing.
Because we a using Frida, we do not need to use a custom version of Android with a modified #ART or kernel like some dynamica analysis framework. #todo[references]
However, we decided to not inject Frida in the original application, so we need to have root access to directly run Frida in Android, wich is not a normal thing to have on Android.
Because dynamic analysis can be slow, we also decided to run the applications on emulators.
This makes its easier to run several analysis in parallel.
The alternative would have been to run the application on actual smartphones, and would have required multiple phones to run the analysis in parallel.
For simplicity, we choosed to use Google Android emulator for our experiment.
We spawned multiple emulators, installed Frida on it, took a snapshot of the emulator before installing the application to analyse.
Then we run the application for a minute #todo[check la valeur exacte] with GroddRunner, and at the end of the analysis, we reload the snapshot in case the application modified the system in some unforseen way.
If at some point the emulator start responding for too long, we force kill it and restart it.
#todo[Droid donjon, dire qu'on est au niveau -1 de l'anti-evation]
As we will see in @sec:th-res #todo[donner la bonne subsection], our experimental setup is quite naive and still requiere improvement. #todo[Comment on dit proprement que c'est tout pété?]
Nonetheless, our analysis tool itself only require a #ADB connection to a phone with a rooted Android system to work.
To analyse a specific application, using an actual smartphone and exploring the application manually is still possible and a lot more stable than our automated batch analysis setup.
#todo[Futur work: Droiddonjon like, GroddDroid improved exploration, potentiellement faire de l'execution forcé avec frida]
=== Application Execition

View file

@ -1,8 +1,103 @@
#import "../lib.typ": todo #import "../lib.typ": todo, SDK, num, mypercent, ART, ie
#import "X_var.typ": *
== Result <sec:th-res> == Result <sec:th-res>
#todo[better section name for @sec:th-res] #todo[better section name for @sec:th-res]
=== Bytecode Loaded by Application <sec:th-code-collected> To studdy the impact of our transformation on analysis tools, we took reused application from the dataset we sampled in @sec:rasta-dataset.
Because we are running the application on a rescent version of Android (#SDK 34), we only took the most recent applications: the one collected in 2023.
This represent #num(5000) applications.
Among them, we could not retrieve 43 from Androzoo, leaving us with #num(dyn_res.all.nb) applications to test.
#todo[Bytecode collected: facebook, google, appsflyer] === The Limits of Our Dynamic Analysis <sec:th-dyn-failure>
After running the dynamic analysis on our dataset the first time we realised our dynamic setup was quite fragile.
We found that #mypercent(dyn_res.all.nb_failed_first_run, dyn_res.all.nb) of the execution failed with various errors.
The majority of those errors were related to faillures to connect to the Frida agent or start the activity from Frida.
Some of those errors seamed to come from Frida, while other seamed related to the emulator failing to start the application.
We found the simply relauching the analysis for the applications that failled was the most simple way to fix those issues, and after 6 passes we went from #num(dyn_res.all.nb_failed_first_run) to #num(dyn_res.all.nb_failed) application that could not be analyzed.
The remaining errors look more related to the application itself or Android, with #num(96) errors being a failure to install the application, and #num(110) other beeing a null pointer exception from Frida.
Infortunatly, although we managed to start the applications, we can see from the list of activity visited by GroddDroid that a majority (#mypercent(dyn_res.all.z_act_visited, dyn_res.all.nb - dyn_res.all.nb_failed)) of the application stopped before even starting one activity.
Some applications do not have an activity, and are not intended to interact with a user, but those are clearly a minority and do not explain such a high number.
We expected some issue related to the use of an emulator, like the lack of x86_64 library in the applications, or contermesures aborting the application if the emulator is detected.
We manually looked at some applications, but did not found a notable pattern.
In some cases, the application was just broken, for instance the application might be trying to load a native library that simply do not exists in the application.
In other case, Frida is to blame: we found some cases where calling a method from Frida can confuse the #ART.
`protected` methods needs to be called from the class that defined the method or one of its children calsses, but Frida might be considered by the #ART as an other class, leading to the #ART aborting the application.
#todo[jfl was suppose to test a few other app #emoji.eyes]
@tab:th-dyn-visited shows the number of applications that we analysed, if we managed to start at least one activity and if we intercepted code loading or reflection.
As shown in the table, even if the application fails to start an activity, some times it will still load external code or use reflection.
#figure(
table(
columns: 6,
stroke: none,
inset: 7pt,
align: center+horizon,
table.header(
table.hline(),
table.cell(colspan: 6, inset: 2pt)[],
table.cell(rowspan: 2)[],
table.cell(rowspan: 2)[nb apk],
table.vline(end: 3),
table.vline(start: 4),
table.cell(colspan: 2, inset: (bottom: 0pt))[nb failled],
table.vline(end: 3),
table.vline(start: 4),
table.cell(colspan: 2, inset: (bottom: 0pt))[activities visited],
[1#super[st] pass], [6#super[th] pass],
[0], [$>= 1$],
),
table.cell(colspan: 6, inset: 2pt)[],
table.hline(),
table.cell(colspan: 6, inset: 2pt)[],
[All], num(dyn_res.all.nb), num(dyn_res.all.nb_failed_first_run), num(dyn_res.all.nb_failed), num(dyn_res.all.z_act_visited), num(dyn_res.all.nz_act_visited),
[With Reflection], num(dyn_res.reflection.nb), [], [], num(dyn_res.reflection.z_act_visited), num(dyn_res.reflection.nz_act_visited),
[With Code Loading], num(dyn_res.code_loading.nb), [], [], num(dyn_res.code_loading.z_act_visited), num(dyn_res.code_loading.nz_act_visited),
table.cell(colspan: 3, inset: 2pt)[],
table.hline(),
),
caption: [Summary of the dynamic exploration of the applications from the RASTA dataset collected by Androzoo in 2023]
) <tab:th-dyn-visited>
The high number of application that did not start an activity means that our result will be highly biaised.
The code that might be loaded or method that might be called by reflection from inside activities is filtered out by the limit of or dynamic execution.
This biaised must be kept in mind when reading the next subsection that studdy the bytecode that we intercepted.
=== The Bytecode Loaded by Application <sec:th-code-collected>
We collected a total of #nb_bytecode_collected files for #dyn_res.code_loading.nb application that we detected loading bytecode dynamicatlly.
#num(92) of them were loaded by a `DexClassLoader`, #num(547) were loaded by a `InMemoryDexClassLoader` and #num(1) was loaded by a `PathClassLoader`.
Interressingly, once we compared the files, we found that we only collected #num(bytecode_hashes.len()) distinct files, and that #num(bytecode_hashes.at(0).at(0)) where identicals.
Once we looked more in details, we found that most of those files are advertisement libraries.
In total, we collected #num(nb_google) files containing Google ads librairies and #num(nb_facebook) files containing Facebook ads librairies.
In addition, we found #num(nb_appsflyer) files containing code that we believe to be AppsFlyer, and company that provides "measurement, analytics, engagement, and fraud protection technologies".
The remaining #num(nb_bytecode_collected - nb_google - nb_appsflyer - nb_facebook) files were custom code from high security applications (#ie banking, social security)
@tab:th-bytecode-hashes sumarize the information we collected about the most common bytecode files.
#figure(
table(
columns: 4,
stroke: none,
align: center+horizon,
table.header(
[Nb Occurences], [SHA 256], [Content], [Format]
),
table.hline(),
..bytecode_hashes.slice(0, 10)
.map(
(e) => (num(e.at(0)), [#e.at(1).slice(0, 10)...], ..e.slice(2))
).flatten(),
table.cell(colspan: 4)[...],
table.hline(),
),
caption: [Most common dynamically loaded files]
) <tab:th-bytecode-hashes>
=== Impact on Analysis Tools
#todo[Check if flowdroid improve, compare sucess rate of RASTA, show result for demo app]

101
5_theseus/X_var.typ Normal file
View file

@ -0,0 +1,101 @@
#import "../lib.typ": DEX, APK
#let dyn_res = (
all: (
nb: 4957,
nb_failed_first_run: 2136,
nb_failed: 209,
z_act_visited: 3860,
nz_act_visited: 888,
),
reflection: (
nb: 3948,
z_act_visited: 3152,
nz_act_visited: 796,
),
code_loading: (
nb: 598,
z_act_visited: 434,
nz_act_visited: 164,
)
)
#let bytecode_hashes = (
(273, "bee390afa2267bc48829ee7a0f4286895bf32ba2443ff447451f515818f7203b", "Lcom/facebook/ads/*", DEX),
(98, "7aae06433cff5967ac254484d784c2c348380891d0914c56de64e7e006668cd4", "Lcom/facebook/ads/*", DEX),
(70, "920e465a87a2409fc8d7186ea4e319c613c04d156bec75e8b91cb4d07b1deb75", "Lcom/facebook/ads/*", DEX),
(31, "51dd5ff34a950c026bd39ba3b923c4c47126c097e844951ab1b71ea7c0ed824f", "Lcom/google/android/ads/*", APK),
(12, "d44cfb6b41231f150cf310c7c4d399be9587294e3727197e046db4a1c2c3ca3b", "Lcom/google/android/ads/*", APK),
(9, "be87bb0a50395917f973b4b2691d62037c7c7c6bc8aef060b49240680f82ec06", "Lcom/facebook/ads/*", DEX),
(9, "26fb1a790377e11135bf8bfa7552cc2797d351df60154ea032ceeb4463776fdf", "Lcom/facebook/ads/*", DEX),
(7, "8395a0121e5c4fc47f9547f58b8b335c4f499a8c302023878184db88336456ff", "Lcom/facebook/ads/*", DEX),
(7, "1eea5584eb2332554753b4beec7fe8e972bfb3eeadbe0c05dba33de267f25a00", "Lcom/google/android/ads/*", APK),
(6, "94f66aa1ae29067590791b57c44943326ff7d0793e5c3b4943c88837d98bf5f2", "Lcom/appsflyer/internal/*", DEX),
(6, "76b0a168957403694b4067be8d5e0733c8b06b4165a0aaa85363517e8e972b41", "Lcom/appsflyer/internal/*", DEX),
(6, "0aaf38c4dbaebb1f97357d71fcd266a04d941edf7d6a90ce4d5457f9276e9bb2", "Lcom/google/android/ads/*", APK),
(5, "f14f45afd0a5d08b046dc89b394709bd6391245f64955cbfc8c0d5ed32fd13fd", "Lcom/appsflyer/internal/*", DEX),
(5, "77731deb52bfdbc177380141ce84f9a44558564438c069ad7582490b1071cabc", "Lcom/google/android/ads/*", APK),
(5, "2ef4bbdaeb41f79500d38885b7711ff5ea9464f565f32b060116670806b7b173", "Lcom/appsflyer/internal/*", DEX),
(5, "1431e7d7fd88b604a9c3e7025992617af92714e42c5b6ea4aaa230dbee54e32e", "Lcom/appsflyer/internal/*", DEX),
(5, "08db2ca6e3f51676dc1c9b114d522ac5ed211ae2e359bf6270066aca651e6932", "Lcom/facebook/ads/*", DEX),
(4, "a66a9b1d2d136dc5df624d3cf577e54952218157ab15d39254ab64963be5cc72", "Lcom/facebook/ads/*", DEX),
(4, "688d9c42adca2cacb59ccff544456da5b0faf33c47539753f4b6158d11681177", "Lcom/facebook/ads/*", DEX),
(4, "5c4e43425c7220af03957c560092dbcc613ac7e0894e2740e87a0a9441fef9fb", "Lcom/google/android/ads/*", APK),
(4, "38c5c0584805386dfce413656c636c4e73a968ade86cabd7a4ecac4f034a842b", "Lcom/facebook/ads/*", DEX),
(3, "f235eaf46d704697e94379372a77a2a2b35e0727d09db08e9dd1d7a55c13a38d", "Lcom/appsflyer/internal/*", DEX),
(3, "e5c7e239c5ff1e86317bfb009068311c0f0f89f76d7b7c7177aced74f718922d", "Lcom/google/android/ads/*", " Java archive data (JAR)"),
(3, "e4270ee2c7c624bde9dacf923066032fd734e12cd70c8c70c949aee553896ef2", "Lcom/google/android/ads/*", APK),
(3, "7cd311f806352c2a69f8c22a48f732a1c27fd00ad07690a8ae2b1fdb0aa7b1e8", "Lcom/facebook/ads/*", DEX),
(3, "430c38bdf4ff62180781e77d4c7282bcd87143ec5d0aaf4534272ed6c7654b50", "Lcom/google/android/ads/*", APK),
(3, "22ba7ab35067bf1343dcb20e1fa12a0277af5bafa4d8cbf62eda91c15983d180", "Lcom/google/android/ads/*", APK),
(2, "f809447486f89fcaa74f87e06d126d103d37eb2b3157e88f2c06d989b2c284ce", "Lcom/google/android/ads/*", APK),
(2, "b2d5fa3f1fa8128c2f086e933ebdc310ed9ae498161bd4f31b11d2a327e49a2d", "Lcom/google/android/ads/*", APK),
(2, "8ae01d69caedba5faec1bf99fd86b412e54ae01d6c1b1876cb4fd1b744329dad", "Lcom/google/android/ads/*", APK),
(2, "0bfad0c78e6e439d2c70d43568d1dc541bff8d4b4c5bfda9e81e03ae790dd864", "Lcom/facebook/ads/*", DEX),
(1, "f8bc60491994834867a77096af5282190d9b742acf46a0d5a8fc527b627f20e8", "Lcom/appsflyer/internal/*", DEX),
(1, "edd47be06bf71794d0402a70a909e8f288de394129f9ab3cd4b873690f034719", "Lcom/facebook/ads/*", DEX),
(1, "e8cc3f0bfc7988db9e0183c120b929c9b27c95a3d7245d1fadc3df3b60c7ef55", "Lcom/appsflyer/internal/*", DEX),
(1, "b97436f2e515c5846f0d23fae671271dc60795dae9f309ac2e6d85219a86f342", "", DEX),
(1, "b5ddd91296430681f34b34154b553adce8f896a9ae330af761eaff62c16e67c5", "Lcom/appsflyer/internal/*", DEX),
(1, "b550710e818a228ac6a40a6964f67dc3647b463239b9aa58554038960fce6321", "Lcom/facebook/ads/*", DEX),
(1, "ab8baa542a35f0cf5a02a6976be08d90b5c1982898844a5fdb7336a30db99f87", "", DEX),
(1, "aa5468adfa49b5bb6b77ea01f08f69b824c1db1942d73cb80a38f30b1350688b", "Lcom/appsflyer/internal/*", DEX),
(1, "a5cd22950a182ed86832b94018c75cb80d2225c75f3dace2c25fb186580cfb04", "", DEX),
(1, "a307f009ed3545218b58aa312a8ad906997e7333a8ad8fd483720f5b43d50808", "", DEX),
(1, "94aecdeaf153c6993abaf289e2be41b7ed9e513ba4c12c219225b4ada5928543", "Lcom/appsflyer/internal/*", DEX),
(1, "82ba14f2b885c2471fe2b8ff1059ce72763d6a9195e8e442e8fa47a0388e187c", "Lcom/appsflyer/internal/*", DEX),
(1, "7b65a03fea918ff93b53cdc1973a6c65dafb1e15b07b9cdd653da7808d8eaf71", "", DEX),
(1, "79f6c8048f803a069118db0ed11362c9c3a6db2f9e54e5d219830da20b98cec4", "Lcom/appsflyer/internal/*", DEX),
(1, "6febc4b01385c784fbf7c03af15e95c9e357f503f2aa1c002048a199fe1b3df4", "", DEX),
(1, "6d562af7cbdb7fde5a79fb2f319b4e553f61428fd31b1ceae4d4bee860845a99", "Lcom/appsflyer/internal/*", DEX),
(1, "6cd8d19dc74ba69f6c6d93c6f14e2f5872ba102fd66f8f3a0f47bd7c8bbb3e31", "Lcom/google/android/ads/*", APK),
(1, "6655124716f0da78a182d1fd647e59424cea8bcd91a74c7d570d112abd2b0773", "Lcom/appsflyer/internal/*", DEX),
(1, "607e10860109c11381a99f56036ca127c1c8352113d3eb612d704982694599f5", "Lcom/facebook/ads/*", DEX),
(1, "5f18e3b50fe739e6ada6cd47db5fcb19fffca7fbe20ca8d2176a230904027a2a", "Lcom/appsflyer/internal/*", DEX),
(1, "5ec35489b467484e6f28a054fd371ebbc058ef686919330bada7bb91bd8a7664", "Lcom/appsflyer/internal/*", DEX),
(1, "5dd0a0925f78c828ccafe52239bfc41fab98861839e85c4dbb597ef88042f783", "Lcom/appsflyer/internal/*", DEX),
(1, "58e6c3467d0ac66647166bbe111ea4e6aa8dff1639d85c10dc016bd3f768af04", "Lcom/google/android/ads/*", APK),
(1, "58a38bf508d3be01bef2f98de98892f8f2d015047b8808c0e471c13435436411", "Lcom/google/android/ads/*", APK),
(1, "55ceea5940c98162c39ff009eb36b23ba37650ff0b761da4d91aefba2ce144f9", "", APK),
(1, "4c07d1b3a5177cfee01aee86595c5a95bf818830f0a8d737df5e475c483a069f", "Lcom/appsflyer/internal/*", DEX),
(1, "3aedaba6f25a7fd04b16f68bb7ca12f71c5a464346556cbce78ebdb490df0362", "Lcom/facebook/ads/*", DEX),
(1, "39b387a92c8de51316ac103dd7998ad5561c6bbeb29bf7baa71f6f30eaabd7e9", "Lcom/appsflyer/internal/*", DEX),
(1, "36e9d49509eafde99a852053b20b0ba5f9978cdf6b2e3d981aba9ff7dcfe06c5", "Lcom/appsflyer/internal/*", DEX),
(1, "355d3417e140868277dd4b52f75e2e844ee1ec26d43e2d5f192218c3b6c261ca", "Lcom/appsflyer/internal/*", DEX),
(1, "297d6430b89f66c7dae4df9f7ba60e6fdf8f823afda7786521187ce61486dc22", "", DEX),
(1, "1fb49fb80e5dabd0f2897a64c9cf76f6aa2ad2504a89135e286339d77862d043", "", DEX),
(1, "1d42b43a4f2ca277b3dcc147c5f3d4f4f84b6b170b8912287b486fb7343fc1fb", "Lcom/appsflyer/internal/*", DEX),
(1, "1aaa4f587a73d18dbcadf23ddc92841ae807a31d4f729ba430495456b0422ae0", "", DEX),
(1, "1658b49396266d5811bed57bfaeb99504abb799c37356d720e45119153ab5702", "Lcom/appsflyer/internal/*", DEX),
(1, "1409d97cc8b3c23244507fa4f62fd4e5835b80209e1b1bd9589579c3166d4aeb", "", DEX),
(1, "11caa0ac837a8af9d3e45ad509e59c31d3d344bb8858cf40d1b0e1a1c29156ea", "Lcom/google/android/ads/*", APK),
(1, "0d2f47edb7a5f4a075deb9f1e5ee24786daf28492553f6a6f8918c6ea19da92f", "", APK),
(1, "0a446677e3eb0e015827f3d2d67df23ed9042e436bb5bab5cc9fae961e20600f", "", DEX),
)
// #let nb_bytecode_collected = 640
#let nb_bytecode_collected = bytecode_hashes.map((e) => e.at(0)).sum()
#let nb_google = bytecode_hashes.filter((e) => "google" in e.at(2)).map((e) => e.at(0)).sum()
#let nb_facebook = bytecode_hashes.filter((e) => "facebook" in e.at(2)).map((e) => e.at(0)).sum()
#let nb_appsflyer = bytecode_hashes.filter((e) => "appsflyer" in e.at(2)).map((e) => e.at(0)).sum()

View file

@ -5,5 +5,6 @@
#todo[theseus chapter title for @sec:th] #todo[theseus chapter title for @sec:th]
#include("1_static_transformation.typ") #include("1_static_transformation.typ")
#include("2_dynamic_data_collection.typ")
#include("3_results.typ") #include("3_results.typ")
#include("4_ttv.typ") #include("4_ttv.typ")

View file

@ -36,3 +36,5 @@
#let mypercent(numerator, denominator, digits: 2) = [#num(calc.round((100 * numerator) / denominator, digits: digits))%] #let mypercent(numerator, denominator, digits: 2) = [#num(calc.round((100 * numerator) / denominator, digits: digits))%]
#let jm-note = note.with(stroke: purple + 1pt)
#let jfl-note = note.with(stroke: green + 1pt)