diff --git a/4_class_loader/2_classloading.typ b/4_class_loader/2_classloading.typ index 4a0112d..d499891 100644 --- a/4_class_loader/2_classloading.typ +++ b/4_class_loader/2_classloading.typ @@ -99,6 +99,7 @@ In reality, the #platc are loaded by `bootClassLoader` and the classes from the while file_exists_in_apk(dex_file) and \ not class_found_in_dex_file(class_name, dex_file): index += 1 + dex_file = get_mutli_dex_classses_dex_name(index) if file_exists_in_apk(dex_file): return load_from_file(dex_file, class_name) else: diff --git a/slides.typ b/slides.typ index a181e53..09f45d8 100644 --- a/slides.typ +++ b/slides.typ @@ -18,6 +18,8 @@ #set text(lang: "en") #set list(marker: none) +#set par(leading: 0.2em) +#set list(spacing: 1em) #show: sns-polylux-template.with( txt-font: "New Computer Modern", @@ -598,13 +600,15 @@ title: [MultiDex] )[ #set align(center + horizon) + #let apk-block = block.with( + //fill: green.lighten(50%), + stroke: black, + inset: 10pt, + radius: 12pt, + ) #only(1)[ - #block( - fill: green.lighten(50%), - inset: 10pt, - radius: 12pt, - )[ + #apk-block[ #set align(left+top) === `app.apk` @@ -619,11 +623,7 @@ ] ] #only(2)[ - #block( - fill: green.lighten(50%), - inset: 8pt, - radius: 8pt, - )[ + #apk-block[ #set align(left+top) === `app.apk` #line(length: 50%) @@ -645,11 +645,7 @@ ] ] #only(3)[ - #block( - fill: green.lighten(50%), - inset: 8pt, - radius: 8pt, - )[ + #apk-block[ #set align(left+top) === `app.apk` #line(length: 75%) @@ -685,7 +681,225 @@ ] #ghost-4(x: 2%, y: 2%, mirror: true) ] + + #only(4)[ + #grid( + columns: (1fr, 1fr), + apk-block[ + #set align(left+top) + === `app.apk` + #line(length: 30%) + ``` + AndroidManifest.xml + resources.arsc + META-INF/ + res/ + classes.dex + classes2.dex + classes3.dex + ``` + ], + yes-codly[ + #scale(100%, reflow: true, + ```python + def get_dex_name(index: int): + if index == 0: + return "classes.dex" + else: + return f"classes{index+1}.dex" + ```) + ] + ) + ] + + #only(5)[ + #grid( + columns: (1fr, 1fr), + apk-block[ + #set align(left+top) + + === `app.apk` + #line(length: 30%) + ``` + AndroidManifest.xml + resources.arsc + META-INF/ + res/ + classes.dex + classes2.dex + classes3.dex + ``` + ], + yes-codly[ + #set list(spacing: 0.5em) + #scale(100%, reflow: true, + ```python + def get_dex_name(index: int): + if index == 0: + return "classes.dex" + else: + return f"classes{index+1}.dex" + ```) + - `classes0.dex` ? + - `classes1.dex` ? + - `classes02.dex` ? + - `classes10.dex` ? + ] + ) + #ghost-4(x: 2%, y: 2%, mirror: true) + ] +] + +#for i in range(4) { + if i != 0 { counter("logical-slide").update( n => n - 1 ) } + slide( + title: [Shadow Attacks] + )[ + + #if i in (0, 4) { + codly( + ..default-codly + ) + } else if i == 1 { + codly( + highlighted-lines: (7, 8), + ..default-codly + ) + } else if i == 2 { + codly( + highlighted-lines: (1, 2, 3, 4, 5, 10, 11, 12, 13, 14, 15), + ..default-codly + ) + } + #let partial-hide( + i, + hidden: (), + partial: (), + body + ) = { + if i in hidden { + hide(body) + } else if i in partial { + set text(fill: luma(200)) + body + } else { + body + } + } + #set align(center+horizon) + + #grid( + columns: (1fr, 1fr), + column-gutter: 2em, + yes-codly[ + #scale(60%, reflow: true, + ```python + def get_dex_name(index: int): + if index == 0: + return "classes.dex" + else: + return f"classes{index+1}.dex" + def load_class(class_name: str): + if is_platform_class(class_name): + return boot_cl.load(class_name) + else: + index = 0 + dex_file = get_dex_name(index) + while exists_in_apk(dex_file) and \ + class_name not in classes_of(dex_file): + index += 1 + dex_file = get_dex_name(index) + if file_exists_in_apk(dex_file): + return load_from_file(dex_file, class_name) + else: + raise ClassNotFoundError() + ```) + ], [ + #set align(left) + #set text(size: 18pt) + #partial-hide(i, hidden: (0,), partial: (2,))[ + === SDK shadowing + Trick the tool into using an APK class instead of an SDK class + ] + #partial-hide(i, hidden: (0,), partial: (2,))[ + === Hidden API shadowing + Trick the tool into using an APK class instead of an hidden API class + ] + #partial-hide(i, hidden: (0,1))[ + === Self Shadowing + Trick the tool into using an APK class instead of another APK class + ] + ] + ) + ] +} + +#slide( + title: [Impact on Tools], +)[ + #set align(center+horizon) + #show figure.caption: none + #show link.where(dest: ): it => it.body + #show figure: it => { + set align(left) + show table: set align(center+horizon) + it + } + #scale(100%, reflow: true, get_figure()) +] + +#slide( + title: [Example: Androguard], + foreground: eye-4(x: 97%, y: 85%, mirror: true) +)[ + #set align(center+horizon) + #show image: scale.with(250%) + #set figure(gap: 4em) + #v(3em) + #grid( + columns: (1fr, 1fr), + get_figure(), + get_figure() + ) +] + +#for i in range(3) { + if i != 0 { counter("logical-slide").update( n => n - 1 ) } + slide( + title: [In the Wild], + foreground: eye-2(x: 8%, y: 67%, height: 70pt) + )[ + #set align(center+horizon) + #show figure.caption: none + #show link.where(dest: ): it => it.body + #set table( + fill: (x, y) => { + if ( + i == 1 and (x, y) in ((1, 13), (2, 13), (4, 13), (8, 13)) + ) or ( + i == 2 and (x, y) in ((1, 14), (2, 14)) + ) { + highlight-color + } else { + none + } + } + ) + #scale(90%, reflow: true, get_figure()) + ] +} + +#slide( + title: [Conclusion] +)[ + #item-by-item[ + - We modeled the class loading algorithm + - Static Analysis Tools did not + - We introduced obfuscation techniques based on this model + - Ambiguous cases exists in the wild + - We did not find deliberate shadow attacks + ] ] #slide[ @@ -701,20 +915,293 @@ #new-section-slide([The Application of Theseus]) -#slide[Th 1 - +#slide( + title: [Overview], +)[ + #set align(center+horizon) + #show figure.caption: none + #scale(100%, reflow: true, get_figure()) ] -#slide[Th 2 +#slide( + title: [Dynamic Analysis], +)[ + - Frida: intercepts method calls + + #v(2em) + + #uncover("2-")[ + - Android Emulator: runs on computer/server + - Grodd Runner: clicks buttons + ] + + #v(2em) + #uncover(3)[ + - Phone with adb enable: actuall hardware + - Human: intelligent button clicker + ] +] + +#slide( + title: [Dynamic Code Loading], + foreground: ghost-6(x: 80%, y: 15%, mirror: true) +)[ + #set align(center+horizon) + #show figure.caption: none + #show image: box.with(width: 58%) + #get_figure() +] + +#for i in range(4) { + if i != 0 { counter("logical-slide").update( n => n - 1 ) } + + slide( + title: [Reflection], + //foreground: ghost-6(x: 80%, y: 15%, mirror: true) + )[ + #show: yes-codly + #set align(center+horizon) + + #if i == 1 { + codly( + highlighted-lines: (6,), + ..default-codly + ) + } else if i == 3 { + codly( + offset: 5, + ..default-codly + ) + } else { + codly(..default-codly) + } + + #if i in (0, 1) { + ```java + ClassLoader cl = MainActivity.class.getClassLoader(); + Class clz = cl.loadClass("Reflectee"); + Object obj = clz.newInstance(); + Method mth = clz.getMethod("myMethod", String.class); + Object[] args = {(Object)"an argument"}; + String retData = (String) mth.invoke(obj, args); + ``` + } else if i == 2{ + ```java + ClassLoader cl = MainActivity.class.getClassLoader(); + Class clz = cl.loadClass(getFromInternet()); + Object obj = clz.newInstance(); + Method mth = clz.getMethod(getFromInternet(), String.class); + Object[] args = {(Object)getFromInternet()}; + String retData = (String) mth.invoke(obj, args); + ``` + } else { + ```java + String retData = (String) mth.invoke(obj, args); + ``` + } + ] +} + +#for i in range(5) { + if i != 0 { counter("logical-slide").update( n => n - 1 ) } + + slide( + title: [Reflection Transformation] + )[ + #show: yes-codly + #set align(center+horizon) + #if i == 1 { + codly( + offset: 5, + highlighted-lines: (10,), + ..default-codly + ) + } else if i == 2 { + codly( + offset: 5, + highlights: ( + (line: 6, start: 0, end: 13, fill: pirat-color.blue), + (line: 8, start: 3, end: 8, fill: pirat-color.blue), + (line: 10, start: 3, end: 8, fill: pirat-color.blue), + (line: 12, start: 18, end: 32, fill: pirat-color.blue), + ), + ..default-codly + ) + } else if i == 3 { + codly( + offset: 5, + highlights: ( + (line: 8, start: 12, end: 19, fill: pirat-color.blue), + ), + ..default-codly + ) + } else if i == 4 { + codly( + offset: 5, + highlights: ( + (line: 7, start: 5, end: 43, fill: pirat-color.blue), + (line: 8, start: 21, end: 31, fill: pirat-color.blue), + (line: 8, start: 38, end: 45, fill: pirat-color.blue), + (line: 8, start: 47, end: 54, fill: pirat-color.blue), + ), + ..default-codly + ) + } else { + codly( + offset: 5, + ..default-codly + ) + } + ```java + Object objRet; + if (T.check_is_reflectee_mymethod_XXXX(mth)) { + objRet = (Object)((Reflectee) obj).myMethod((String)args[0]); + } else { + objRet = mth.invoke(obj, args); + } + String retData = (String) objRet; + ``` + ] +} + +#slide( + title: [Dynamic Analysis], + foreground: ghost-1(x: 97%, y: 10%, height: 70pt) +)[ + #set align(center+horizon) + #show figure.caption: none + #set table( + fill: (x, y) => { + if ( + x == 4 and y > 2 + ) { + highlight-color + } else { + none + } + } + ) + #scale(100%, reflow: true, get_figure()) +] + +#slide( + title: [Collected Bytecode], + foreground: eye-1(x: 5%, y: 70%, height: 70pt) +)[ + #set align(center+horizon) + #show link.where(dest: ): it => it.body + #show link.where(dest: ): it => it.body + #show figure.caption: none + #set table( + fill: (x, y) => { + if ( + x == 4 and y > 2 + ) { + highlight-color + } else { + none + } + } + ) + #scale(90%, reflow: true, get_figure()) +] + +#for i in range(3) { + if i != 0 { counter("logical-slide").update( n => n - 1 ) } + + slide( + title: [Added Calls], + )[ + #set align(center+horizon) + #show link.where(dest: ): it => it.body + #show link.where(dest: ): it => it.body + #show figure.caption: none + #set table( + fill: (x, y) => { + if ( + i == 1 and (x == 3 and y > 1) + ) or ( + i == 2 and (x == 4 and y > 1) + ){ + highlight-color + } else { + none + } + } + ) + #scale(90%, reflow: true, get_figure()) + ] +} + + +#slide( + title: [Added Calls], + foreground: ghost-3(x: 93%, y: 10%) +)[ + #import "@preview/diagraph:0.3.5": render + #set align(center+horizon) + #scale(47%, box(render( + read("5_theseus/figs/patched_main_main.dot"), + //width: 100%, + labels: (name) => { move(dy: -7pt, scale(140%, text(size: 10pt, weight: "bold", name))) } + ))) +] + +#slide( + title: [Impact on Finishing Rate], +)[ + #set align(center+horizon) + #show figure.caption: none + #scale(90%, reflow: true, get_figure()) +] + +#slide( + title: [Conclusion], +)[ + #item-by-item[ + - We can statically analyse APKs with reflection and dynamic code loading with our method + - Our dynamic analysis is questionable + - The dynamically loaded bytecode we intercepted is mainly telemetry and advertisement related + - We released a tool to instrument APKs + ] ] #new-section-slide([Conclusion]) -#slide[Conclusion 1 +#slide[ + We showed that: + #item-by-item[ + - After five years, more than half the static analysis tools are no longer usable. + The size of the application seems to be the most significant factor. + - Android behaviour is complex and well known. + In the specific case of class loading, we showed that state-of-the-art tools do not match Android, leading to invalid analyses. + - APKs can be augmented with instrumentation to improve further analyses with any other tools. + - Also, dynamic analysis is still very much not trivial. + ] ] -#slide[Conclusion 2 +#slide( + title: [Futur Work], + foreground: { + ghost-3(x: 80%, y: 70%) + ghost-6(x: 20%, y: 30%) + ghost-7(x: 70%, y: 20%) + } +)[ + #set align(center+horizon) + A lot of engineering, preferably spearheaded by Google. +] + +#counter("logical-slide").update( n => n - 1 ) +#slide( + title: [Futur Work] +)[ + #item-by-item[ + - Benchmark to evaluate finishing rate + - Make tools reusing sources from Android Open Source Project + - Require developpers to provide high coverage tests inputs with the APKs + ] ]