This commit is contained in:
parent
7f61637b64
commit
e9bc1572e9
4 changed files with 3080 additions and 16 deletions
|
@ -110,11 +110,11 @@ We spawned multiple emulators, installed Frida on it, took a snapshot of the emu
|
||||||
Then we run the application for a five minutes with GroddRunner, and at the end of the analysis, we reload the snapshot in case the application modified the system in some unforseen way.
|
Then we run the application for a five minutes 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 terminate it and restart it.
|
If at some point the emulator start responding for too long, we terminate 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-dyn-failure, our experimental setup is quite naive and still requires improvement. #todo(strike(stroke: green)[Comment on dit proprement que c'est tout pété?])
|
||||||
As we will see in @sec:th-res #todo[donner la bonne subsection], our experimental setup is quite naive and still requiee improvement. #todo(strike(stroke: green)[Comment on dit proprement que c'est tout pété?])
|
For example, it does not implement any anti-evasion techniques, which can be a significant issue when analysing malware.
|
||||||
Nonetheless, the benefit of our implementation is that it only requires a #ADB connection to a phone with a rooted Android system to work.
|
Nonetheless, the benefit of our implementation is that it only requires a #ADB connection to a phone with a rooted Android system to work.
|
||||||
Of course, to analyse a specific application, a reverse engineer could use an actual smartphone and explore the application manually.
|
Of course, to analyse a specific application, a reverse engineer could use an actual smartphone and explore the application manually.
|
||||||
It wiykd be a lot more stable than our automated batch analysis setup.
|
It would be 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]
|
#todo[Futur work: Droiddonjon like, GroddDroid (or other) improved exploration, potentiellement faire de l'execution forcé avec frida]
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
#import "../lib.typ": todo, SDK, num, mypercent, ART, ie, jfl-note
|
#import "../lib.typ": todo, SDK, num, mypercent, ART, ie, APKs, jfl-note
|
||||||
#import "X_var.typ": *
|
#import "X_var.typ": *
|
||||||
#import "../3_rasta/X_var.typ": NBTOTALSTRING
|
#import "../3_rasta/X_var.typ": NBTOTALSTRING
|
||||||
|
|
||||||
|
@ -24,11 +24,13 @@ Infortunatly, although we managed to start the applications, we can see from the
|
||||||
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.
|
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 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.
|
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 some cases, the application was just broken -- for instance, an application was trying to load a native library that simply does 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.
|
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.
|
`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]
|
#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.
|
@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.
|
||||||
|
It also shows the average number of activities visited (when at least one activity was started).
|
||||||
|
This average slightly higher than 1, which seems reasonable: a lot of applications do not need more than one activity, but some do and we did manage to explore at least some of those additionnal activities.
|
||||||
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.
|
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({
|
#figure({
|
||||||
|
@ -59,9 +61,9 @@ As shown in the table, even if the application fails to start an activity, some
|
||||||
table.cell(colspan: nb_col, inset: 2pt)[],
|
table.cell(colspan: nb_col, inset: 2pt)[],
|
||||||
table.hline(),
|
table.hline(),
|
||||||
table.cell(colspan: nb_col, inset: 2pt)[],
|
table.cell(colspan: nb_col, 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), todo[],
|
[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), num(dyn_res.all.avg_nz_act),
|
||||||
[With Reflection], num(dyn_res.reflection.nb), [], [], num(dyn_res.reflection.z_act_visited), num(dyn_res.reflection.nz_act_visited), todo[],
|
[With Reflection], num(dyn_res.reflection.nb), [], [], num(dyn_res.reflection.z_act_visited), num(dyn_res.reflection.nz_act_visited), num(dyn_res.reflection.avg_nz_act),
|
||||||
[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), todo[],
|
[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), num(dyn_res.code_loading.avg_nz_act),
|
||||||
table.cell(colspan: nb_col, inset: 2pt)[],
|
table.cell(colspan: nb_col, inset: 2pt)[],
|
||||||
table.hline(),
|
table.hline(),
|
||||||
)},
|
)},
|
||||||
|
@ -103,8 +105,99 @@ The remaining #num(nb_bytecode_collected - nb_google - nb_appsflyer - nb_faceboo
|
||||||
caption: [Most common dynamically loaded files]
|
caption: [Most common dynamically loaded files]
|
||||||
) <tab:th-bytecode-hashes>
|
) <tab:th-bytecode-hashes>
|
||||||
|
|
||||||
=== Impact on Analysis Tools
|
=== Impact on Analysis Tools Finishing Rate
|
||||||
|
|
||||||
|
#todo[alt text @fig:th-status-npatched-vs-patched]
|
||||||
|
#todo[Check SAAF and IC3 results on patched]
|
||||||
|
#figure({
|
||||||
|
image(
|
||||||
|
"figs/comparision-of-exit-status.svg",
|
||||||
|
width: 100%,
|
||||||
|
alt: "",
|
||||||
|
)
|
||||||
|
place(center + horizon, rotate(24deg, text(red.transparentize(0%), size: 20pt, "PRELIMINARY RESULTS")))
|
||||||
|
},
|
||||||
|
caption: [Exist status of static analysis tools on original #APKs (left) and patched #APKs (right)]
|
||||||
|
) <fig:th-status-npatched-vs-patched>
|
||||||
|
|
||||||
#todo[Check if flowdroid improve, compare sucess rate of RASTA, show result for demo app]
|
#todo[Check if flowdroid improve, compare sucess rate of RASTA, show result for demo app]
|
||||||
|
|
||||||
#jfl-note[Combien d'app tranforme? on parle des 888? on fait les 2 tranformation sur chaque apk? ca reussit tout le temps?]
|
#jfl-note[Combien d'app tranforme? on parle des 888? on fait les 2 tranformation sur chaque apk? ca reussit tout le temps?]
|
||||||
|
|
||||||
|
=== Example
|
||||||
|
|
||||||
|
We use on our approach on a small #APK.
|
||||||
|
We handcrafted this application for the purpose of demonstrating how this can improve help a reverse engineer in its work.
|
||||||
|
Accordingly, this application is quite small and contains boff dynamic code loading and reflection.
|
||||||
|
We defined a method `Utils.source()` and `Utils.sink()` to model respectively a method that collect sensitive data and that exfiltrate data.
|
||||||
|
Those methods are the one we will use with Flowdroid to track data flows.
|
||||||
|
|
||||||
|
#figure(
|
||||||
|
```java
|
||||||
|
package com.example.theseus;
|
||||||
|
|
||||||
|
public class Main {
|
||||||
|
private static final String DEX = "ZGV4CjA [...] EAAABEAwAA";
|
||||||
|
Activity ac;
|
||||||
|
private Key key = new SecretKeySpec("_-_Secret Key_-_".getBytes(), "AES");
|
||||||
|
ClassLoader cl = new InMemoryDexClassLoader(ByteBuffer.wrap(Base64.decode(DEX, 2)), Main.class.getClassLoader());
|
||||||
|
|
||||||
|
public void main() throws Exception {
|
||||||
|
String[] strArr = {"n6WGYJzjDrUvR9cYljlNlw==", "dapES0wl/iFIPuMnH3fh7g=="};
|
||||||
|
Class<?> loadClass = this.cl.loadClass(decrypt("W5f3xRf3wCSYcYG7ckYGR5xuuESDZ2NcDUzGxsq3sls="));
|
||||||
|
Object obj = "imei";
|
||||||
|
for (int i = 0; i < 2; i++) {
|
||||||
|
obj = loadClass.getMethod(decrypt(strArr[i]), String.class, Activity.class).invoke(null, obj, this.ac);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public String decrypt(String str) throws Exception {
|
||||||
|
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
|
||||||
|
cipher.init(2, this.key);
|
||||||
|
return new String(cipher.doFinal(Base64.decode(str, 2)));
|
||||||
|
}
|
||||||
|
|
||||||
|
...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
caption: [Code of the main class of the application showed by Jadx, before patching],
|
||||||
|
)<fig:th-demo-before>
|
||||||
|
|
||||||
|
A first analysis of the contant of the application shows that the application contains one `Activity` that instanciate the class `Main` and call `Main.main()`.
|
||||||
|
@fig:th-demo-before shows the most of the code of `Main` as returned by Jadx.
|
||||||
|
We can see that the class contains another #DEX file encoded in base 64 and loaded in the `InMemoryDexClassLoader` `cl`.
|
||||||
|
A class is then loaded from this class loader, and two methods from this class loader are called.
|
||||||
|
The names of this class and methods are not directly accessible as they have been chipĥered and are decoded just before beeing used at runtime.
|
||||||
|
Here, the encryption key is available statically, and in theorie, a verry good static analyser implementing Android `Cipher` #API could compute the actual methods called.
|
||||||
|
However, we could easily imagine an application that gets this key from a remote command and control server.
|
||||||
|
In this case, it would be impossible to compute those methods with static analysis alone.
|
||||||
|
When running Flowdroid on this application, it computed a callgraph of 43 edges on this application, an no data leaks.
|
||||||
|
This is not particularly surprising considering the obfusctation methods used.
|
||||||
|
|
||||||
|
Then we run the dynamic analysis we described in @sec:th-dyn on the application and apply the transformation described in @sec:th-trans to add the dynamic informations to it.
|
||||||
|
This time, Flowdroid compute a larger callgraph of 76 edges, and does find a data leak.
|
||||||
|
Indeed, when looking at the new application with Jadx, we notice a new class `Malicious`, and the code of `Main.main()` is now as shown in @figth-demo-after:
|
||||||
|
the method called in the loop is either `Malicious.get_data`, `Malicious.send_data()` or `Method.invoke()`.
|
||||||
|
Although self explanatory, verifying the code of those methods indeed confirm that `get_data()` calls `Utils.source()` and `send_data()` calls `Utils.sink()`.
|
||||||
|
|
||||||
|
#figure(
|
||||||
|
```java
|
||||||
|
public void main() throws Exception {
|
||||||
|
String[] strArr = {"n6WGYJzjDrUvR9cYljlNlw==", "dapES0wl/iFIPuMnH3fh7g=="};
|
||||||
|
Class<?> loadClass = this.cl.loadClass(decrypt("W5f3xRf3wCSYcYG7ckYGR5xuuESDZ2NcDUzGxsq3sls="));
|
||||||
|
Object obj = "imei";
|
||||||
|
for (int i = 0; i < 2; i++) {
|
||||||
|
Method method = loadClass.getMethod(decrypt(strArr[i]), String.class, Activity.class);
|
||||||
|
Object[] objArr = {obj, this.ac};
|
||||||
|
obj = T.check_is_Malicious_get_data_fe2fa96eab371e46(method) ?
|
||||||
|
Malicious.get_data((String) objArr[0], (Activity) objArr[1]) :
|
||||||
|
T.check_is_Malicious_send_data_ca50fd7916476073(method) ?
|
||||||
|
Malicious.send_data((String) objArr[0], (Activity) objArr[1]) :
|
||||||
|
method.invoke(null, objArr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
caption: [Code of `Main.main()` showed by Jadx, after patching],
|
||||||
|
)<fig:th-demo-after>
|
||||||
|
|
||||||
|
|
||||||
|
#todo[androgard call graph]
|
||||||
|
|
|
@ -5,18 +5,21 @@
|
||||||
nb: 4957,
|
nb: 4957,
|
||||||
nb_failed_first_run: 2136,
|
nb_failed_first_run: 2136,
|
||||||
nb_failed: 209,
|
nb_failed: 209,
|
||||||
z_act_visited: 3860,
|
z_act_visited: 4025,
|
||||||
nz_act_visited: 888,
|
nz_act_visited: 723,
|
||||||
|
avg_nz_act: 1.3,
|
||||||
),
|
),
|
||||||
reflection: (
|
reflection: (
|
||||||
nb: 3948,
|
nb: 3948,
|
||||||
z_act_visited: 3152,
|
z_act_visited: 3298,
|
||||||
nz_act_visited: 796,
|
nz_act_visited: 650,
|
||||||
|
avg_nz_act: 1.3,
|
||||||
),
|
),
|
||||||
code_loading: (
|
code_loading: (
|
||||||
nb: 598,
|
nb: 598,
|
||||||
z_act_visited: 434,
|
z_act_visited: 453,
|
||||||
nz_act_visited: 164,
|
nz_act_visited: 145,
|
||||||
|
avg_nz_act: 1.2,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
2968
5_theseus/figs/comparision-of-exit-status.svg
Normal file
2968
5_theseus/figs/comparision-of-exit-status.svg
Normal file
File diff suppressed because it is too large
Load diff
After Width: | Height: | Size: 92 KiB |
Loading…
Add table
Add a link
Reference in a new issue