#import "@preview/polylux:0.4.0": * #import "slides/lib.typ": * #import "slides/icons.typ" as ico #import "@preview/codly:1.3.0": * #import "@preview/codly-languages:0.1.1": * #show: codly-init.with() #let default-codly = ( display-name: false, display-icon: false, zebra-fill: none, fill: luma(240), radius: 1em, inset: (y: 0.15em), highlighted-default-color: highlight-color, highlight-fill: it => it.lighten(40%), //highlight-color, ) #codly-disable() #let analyse-apk = move(dx: -50pt, image("slides/imgs/apk-analysis.svg", width: 300pt)) #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", title-font: "TeX Gyre Heros", aspect-ratio : "16-9", title : [From Large Scale Analysis to Dynamic Deobfuscation], subtitle : [The Woes of Android Reverse Engineering], footer-text : [Jean-Marie Mineau -- PhD Defense], //short-event : [Rennes, 2025/12/9], title-size : 32pt, section-size : 18pt, size : 22pt, //logo-1 : image("slides/imgs/logo_irisa.png"), //logo-2 : image("slides/imgs/logo_pirat.png"), // colormap : sns-polylux-template_sns-pirat, authors : ( { set text(weight: "bold") [MINEAU Jean-Marie] v(1em) }, text(size: 16pt)[LALANDE Jean-François, PhD supervisor], text(size: 16pt)[VIET TRIEM TONG Valérie, PhD co-supervisor], //text(size: 15pt)[NICOMETTE Vincent, Rapporteur], //text(size: 15pt)[SIGNOLES Julien, Rapporteur], //text(size: 15pt)[BARAIS Olivier, Jury], //text(size: 15pt)[AONZO Simone, Jury], ), date : datetime(year: 2025, month: 12, day: 9), ) /* * Intro: * Dear jury, gentle people of the audience, here and online, thank you for your presence. * I am Jean-Marie Mineau, and today I will be defending my thesis about Android Application reverse engineering and the many difficulties a reverse engineer might encounter. * This thesis was suppervised by Jean-François Lalande and Valerie Viet Triem Tong, within the PIRAT research team at IRISA. */ #title-slide( logo: grid(columns: 2, image("slides/imgs/logo_pirat.png"), image(width: 500pt, "slides/imgs/logo_cs.png"), image("slides/imgs/logo_irisa.png"), image("slides/imgs/platypus.png"), ) ) #slide( new-sec: true, title: [Introduction], hide-title: true, /*foreground: { ghost-5(x: 10%, y: 30pt) ghost-4(x: 95%, y: 80%) //ghost-4(x: 45%, y: 43%) }*/ )[ #set align(center+horizon) #grid( columns: (1fr, 1fr), image("slides/imgs/google.png", width: 200pt), //image("slides/imgs/phone.png", height: 350pt) ico.phone( height: 350pt, body: { ico.android(height: 150pt, stroke: none) } ) ) #v(2em) ] #counter("logical-slide").update( n => n - 1 ) #slide( foreground: ghost-4(x: 60%, y: 25%, rot: 45deg) )[ #set align(center+horizon) #grid( columns: (3fr, 2fr), stack(dir: ltr, item-by-item[ - Personal Data and PII - Computing Power - Phone - Mic, Camera, \ Geolocalisation ], [ $ => $ ], item-by-item()[ - Ransomware/Spyware - Cryptojacker - Expander (phone billing) - Stalkerware ] ), { move( dx: 20pt, ico.phone( height: 350pt, body: { ico.android(height: 150pt, stroke: none) } ) ) } ) ] #slide( title: [Obfuscation], //foreground: eye-1(x: 95%, y: 85%, mirror: true) )[ #set list(marker: [-]) Applications might use *obfuscation* to either: - protect their IP - hide malicious behaviour #v(1em)#uncover(2)[ We will focus on two techniques: - *Dynamic Code Loading* - *Reflection* /* * Low effort yet efficiant, commonly found */ ] ] #for i in range(4) { if i != 0 { counter("logical-slide").update( n => n - 1 ) } show: yes-codly slide( title: [Obfuscation], subtitle: if i == 0 [Example] else if i == 1 [Dynamic Code Loading] else if i in (2, 3) [Reflection] else { none }, foreground: eye-1(x: 95%, y: 85%, mirror: true) )[ #if i == 0 { codly(..default-codly) } else if i == 1 { codly( highlighted-lines: (1, 5, 6, 7, 8), ..default-codly ) } else if i == 2 { codly( highlighted-lines: (2, 3), highlights: ( (line: 10, start: 42, end: 59, fill: pirat-color.blue), (line: 13, start: 3, end: 21, fill: pirat-color.blue), ), ..default-codly ) } else if i == 3 { codly( highlighted-lines: (10,), highlights: ( (line: 12, start: 14, end: 34, fill: pirat-color.blue), (line: 15, start: 2, end: 19, fill: pirat-color.blue), ), ..default-codly ) } #scale(70%, reflow: true)[ ```java String DEX = "ZGV4CjA [...] EAAABEAwAA"; String className = "W5f3 [...] 3sls="; String methodName = "n6WGYJzjDrUvR9cYljlNlw=="; ClassLoader cl = new InMemoryDexClassLoader( ByteBuffer.wrap(Base64.decode(DEX, 2)), Main.class.getClassLoader() ); Class loadedClass = this.cl.loadClass(decrypt(className)); Object obj = "FooBar"; Object ret = loadedClass.getMethod( decrypt(methodName), String.class ).invoke(null, obj); ```] ] } #counter("logical-slide").update( n => n - 1 ) #slide( title: [Obfuscation], subtitle: [Deobfuscated], foreground: { place-fg(x: 44%, y: 55%, $ lr(}, size: #130pt) $ ) place-fg(x: 44%, y: 26%, $ lr(}, size: #110pt) $ ) arrow((385pt, -260pt), (450pt, -230pt)) arrow((385pt, -125pt), (450pt, -110pt)) }, )[ #show: yes-codly #grid( columns: (1fr, 4em, 1fr), [ #scale(60%, reflow: true)[ ```java String DEX = "ZGV4CjA [...] EAAABEAwAA"; String className = "W5f3 [...] 3sls="; String methodName = "n6WGYJzjDrUvR9cYljlNlw=="; ClassLoader cl = new InMemoryDexClassLoader( ByteBuffer.wrap(Base64.decode(DEX, 2)), Main.class.getClassLoader() ); Class loadedClass = this.cl.loadClass(decrypt(className)); Object obj = "FooBar"; Object ret = loadedClass.getMethod( decrypt(methodName), String.class ).invoke(null, obj); ```] ], [], [ #codly( skips: ((3, 10),), ..default-codly ) #scale(100%)[ ```java public class Foo { public static String bar(String arg) { } } ``` ```java String ret = Foo.bar("FooBar"); ```] ] ) ] #slide( title: [Analysis Methods], foreground: eye-3(x: 3%, y: 5%) )[ #set align(center+horizon) #analyse-apk ] #counter("logical-slide").update( n => n - 1 ) #slide( foreground: ghost-5(x: 10%, y: 7%) )[ #set align(center+horizon) #grid( columns: (1fr, 1fr), gutter: 2em, [ == Dynamic Analysis #item-by-item[ - Run the application - _See_ dynamically loaded bytecode - _See_ reflection calls - Limited by code coverage ] ], [ == Static Analysis #item-by-item(start: 5)[ - Do *not* run the application - *Not* limited by code coverage - But only for the *code available* //- Some values cannot be computed ] ], //grid.cell(colspan: 2, uncover(8)[ // #text(size: 30pt)[Can we combine both?] //]), ) ] #slide( title: [Which Tools?], foreground: eye-3(x: 3%, y: 5%) )[ #set align(center+horizon) #analyse-apk ] #counter("logical-slide").update( n => n - 1 ) #slide( title: [Which Tools are Working?], foreground: eye-3(x: 3%, y: 5%) )[ #set align(center+horizon) #analyse-apk ] #slide( title: [Problem Statement 1], )[ #item-by-item[ - Which tool to use? - Are they easy to install? - Are they working? ] #highlight-block(pb1-text) ] #slide( title: [How does Class Loading works?], foreground: eye-3(x: 3%, y: 5%) )[ #set align(center+horizon) #analyse-apk ] #counter("logical-slide").update( n => n - 1 ) #slide( title: [Class Loading], )[ #set align(center) #show: yes-codly #grid( columns: (2fr, 1em, 1fr), scale(70%, reflow: true)[ #codly( highlights: ( (line: 1, start: 0, end: 11, fill: pirat-color.blue), (line: 1, start: 22, end: 43, fill: pirat-color.blue), (line: 3, start: 14, end: 27, fill: pirat-color.blue), (line: 6, start: 32, end: 40, fill: pirat-color.blue), ), ..default-codly ) ```java ClassLoader cl = new InMemoryDexClassLoader( ByteBuffer.wrap(Base64.decode(DEX, 2)), Main.class.getClassLoader() ); Class loadedClass = this.cl.loadClass(decrypt(className)); ``` ], [], uncover(2, scale(70%, reflow: true)[ #codly( ..default-codly ) ```java class A { public static void foo() { B b = new B(); b.bar(); } } ``` Where is the class loader? ]) ) ] #counter("logical-slide").update( n => n - 1 ) #slide( title: [Class Loading], )[ #item-by-item[ - Used to select classes implementation - More complexe than it looks - Doubious documentation - Not studied in the context of Android Static Analysis ] #highlight-block(pb2-text) ] #slide( title: [Can we Deobfuscate?], foreground: eye-3(x: 3%, y: 5%) )[ #set align(center+horizon) #analyse-apk ] #counter("logical-slide").update( n => n - 1 ) #slide( title: [Deobuscation], )[ #set align(center+horizon) #grid( columns: (1fr, 1fr), gutter: 2em, [ == Dynamic Analysis Easier to solve Dynamic Code Loading and Reflection Calls ], [ == Static Analysis Better code coverage ], grid.cell(colspan: 2, uncover(2)[ #text(size: 30pt)[Can we combine both?] ]), ) ] //#counter("logical-slide").update( n => n - 1 ) #slide( title: [Problem Statement 3], )[ #item-by-item[ - Dynamic analysis is good against DCL and reflection - Dynamic analysis is limited by code coverage - Static analysis is not - How to use existing tools without modifying them? ] #highlight-block(pb3-text) ] #for i in range(3) { let stage = ( "static-only", "static-vs-dyn", "theseus", ).at(i) if i != 0 { counter("logical-slide").update( n => n - 1 ) } slide( //foreground: rotate(30deg, smallcaps(text(fill: pirat-color.red, size: 50pt)[#stage])) )[ #if i == 0 { place( top+left, dx: 70%, dy: 25%, align(center, text(fill: pirat-color.red.darken(15%))[ *PB 1*: Static Analysis \ are the tools working? ])) } #if i == 1 { place( top+left, dx: 35%, dy: 25%, align(center, text(fill: pirat-color.red.darken(15%))[ *PB 2*: Do Static Tools \ match Android Runtime? ])) } #if i == 2 { place( top+left, dx: 5%, dy: 5%, align(center, text(fill: pirat-color.red.darken(15%))[ *PB 3*: Improve any Static Tools \ with dynamic analysis? ])) } #set align(horizon+center) #theseus-outline(stage: stage) ] } #new-section-slide([Tool Reusability]) #slide( title: [State of the Art], )[ #set list(spacing: 0.5em) Li #etal (2017): #v(0pt) #item-by-item[ - Systematic literature review for Android static analysis - Lists open-sourced tools - Does not test the tools ] #uncover("4-")[Reaves #etal (2016):] #v(0pt) #item-by-item(start: 4)[ - Tests 7 Android analysis tools - Tests analysing 16 real-world applications - Raises concerns about reusability and analysis of real-world applications ] ] #slide( title: [Methodology] )[ #set align(center+horizon) #show figure.caption: none #scale(100%, get_figure()) #v(1em) #text(size: 25pt)[22 tools selected, 2 we could not package] /* #stack(dir: ltr, scale(40%, reflow: true, get_figure()), scale(55%, reflow: true, get_figure()), )*/ ] #slide( title: [Methodology], foreground: place( bottom + left, dx: 88%, dy: -63%, )[ #set align(center+horizon) #set text(size: 15pt) 62 525 APKs #v(-1.5em) from #v(-1.5em) 2010 to 2023 ] )[ #set align(center+horizon) #show figure.caption: none #scale(90%, get_figure()) #text(size: 25pt)[We check if the results *exist* after running a tool] ] #slide( title: [Results], foreground: ghost-2(x: 97%, y: 10%) )[ #set align(center+horizon) #show figure.caption: none #scale(100%, get_figure()) ] #counter("logical-slide").update( n => n - 1 ) #slide( title: [Results], foreground: { ghost-2(x: 97%, y: 10%) let x_0 = 112pt let y_0 = -116pt let w = 21pt let h = 236pt let dx = 33.3 let h_legend = 60pt for i in range(20) { let color = if i in (2, 4, 6, 7, 8, 9, 14, 16, 18, 19) { white.transparentize(100%) } else { white.transparentize(10%) } if i == 1 { place( bottom + left, dx: x_0 + i*dx*1pt + w/2, dy: y_0, rect( width: w/2, height: h_legend, //stroke: red, fill: color, ) ) } let (y_0, h) = if i in (0, 1) { (y_0 - h_legend, h - h_legend) } else { (y_0, h) } place( bottom + left, dx: x_0 + i*dx*1pt, dy: y_0, rect( width: w, height: h, //stroke: red, fill: color, ) ) } place(bottom + left, line( start: (x_0 - 20pt, y_0 - h/2), end: (x_0 + dx * 20 * 1pt, y_0 - h/2), stroke: pirat-color.red + 3pt )) } )[ #set align(center+horizon) #show figure.caption: none #scale(100%, get_figure()) ] /* #counter("logical-slide").update( n => n - 1 ) #slide( title: [Results], foreground: { ghost-2(x: 97%, y: 10%) let x_0 = 112pt let y_0 = -117pt let w = 21pt let h = 235pt let dx = 33.3 for i in range(20) { let color = if i in (3, 10) { white.transparentize(100%) } else { white.transparentize(10%) } place( bottom + left, dx: x_0 + i*dx*1pt, dy: y_0, rect( width: w, height: h, //stroke: red, fill: color, ) ) } place(bottom + left, line( start: (x_0 - 20pt, y_0 - h/2), end: (x_0 + dx * 20 * 1pt, y_0 - h/2), stroke: pirat-color.red + 3pt )) } )[ #set align(center+horizon) #show figure.caption: none #scale(100%, get_figure()) ] #slide( title: [Results over Time], )[ #set align(center+horizon) #show figure.caption: none #scale(150%, get_figure()) ] #slide( title: [Bytecode Size], )[ #set align(center+horizon) #show figure.caption: none #scale(120%, get_figure()) #text(size: 22pt)[Finishing rate as a function of the bytecode size, for APKs discovered in 2022] ] */ #slide( title: [Conclusion], )[ #v(1fr) //#set align(center) #item-by-item[ - Over 22 tools, 10 are usable - Newer applications are harder to analyse - Applications with more bytecode are harder to analyse - Applications targetting more recent versions of Android are harder to analyse - Confirms and extends Reaves #etal ] #v(1fr) #align(center, text(fill: pirat-color.blue.darken(30%))[International Conference on Software and Systems Reuse (ICSR 2024)]) #v(1em) ] #new-section-slide([Class Shadowing]) #slide( title: [Class Loading], )[ #set align(center) #show: yes-codly #grid( columns: (2fr, 1em, 1fr), scale(70%, reflow: true)[ #codly( highlights: (/* (line: 1, start: 0, end: 11, fill: pirat-color.blue), (line: 1, start: 22, end: 43, fill: pirat-color.blue), (line: 3, start: 14, end: 27, fill: pirat-color.blue), (line: 6, start: 32, end: 40, fill: pirat-color.blue), */), ..default-codly ) ```java ClassLoader cl = new InMemoryDexClassLoader( ByteBuffer.wrap(Base64.decode(DEX, 2)), Main.class.getClassLoader() ); Class loadedClass = this.cl.loadClass(decrypt(className)); ``` ], [], scale(70%, reflow: true)[ #codly( ..default-codly ) ```java class A { public static void foo() { B b = new B(); b.bar(); } } ``` ] ) ] #slide( title: [State of the Art], //foreground: rotate(30deg, text(fill: pirat-color.red, size: 50pt)[State of the Art]) )[ #set list(spacing: 3em) #item-by-item[ - Previous contributions focus on Java runtime (#eg Gong 1998) - Android related contributions focus on Dynamic Code Loading (#eg Zhang #etal 2015) ] ] #for i in range(5) { let strong-at(i, str-idxs, body) = { if i in str-idxs { strong(body) } else { body } } slide( title: [Android Ecosystem], foreground: { let c = white.transparentize(10%) let place-rect(x, y, w, h) = place-fg( x: x, y: y, rect( width: w, height: h, stroke: c, fill: c, ) ) // phone if i in () { place-rect(15%, 15%, 16.5%, 55%) } // Platform Classes if i in (1,) { place-rect(18%, 38%+4pt, 12%, 15%) place-rect(21%, 54%, 8%, 5%) } // API access if i in (1,4) { place-rect(26%-4pt, 31%+2.3pt, 5%, 7%+2pt) } // APK file if i in (2,3, 4) { place-rect(19%+3pt, 22%, 9%, 9%+1pt) place-rect(21%+5pt, 32%, 4%, 3%) } // doc if i in (1,2,4) { place-rect(31.5%, 45%, 30%, 25%) } // dev if i in (2,4) { place-rect(39.5%, 15%, 22%, 30%) } // Dev SDK if i in (1,) { place-rect(49.6%, 18%, 10%, 15%) } // Dev classes if i in (3,) { place-rect(41.5%-1pt, 18%, 8%+2pt, 10%) } // compil if i in (2,3, 4) { place-rect(31.5%, 22%, 8%, 5%) } } )[ #set align(center+horizon) #show figure.caption: none #grid( columns: (3fr, 1fr), scale(reflow: true, get_figure()), [ #set align(left) #set text(size: 20pt) #set list(marker: [-]) === Types of classes: - #strong-at(i, (1,))[APK Classes] - #strong-at(i, (2,))[Platform Classes] - #strong-at(i, (3,))[SDK Classes] - #strong-at(i, (4,))[Hidden APIs] ] ) ] } #slide( title: [Android ClassLoaders], foreground: { let stroke = black + 5pt let y0 = -177pt let y1 = -270pt let x0 = 250pt let x1 = 272pt let x2 = 570pt let x3 = 600pt arrow( (x0, y0), (x0, y1), (x1, y1), stroke: stroke ) arrow( (x3, y0), (x3, y1), (x2, y1), stroke: stroke ) place-fg(x: x0 - 2.5em, y: (y0+y1)/2)[Delegate] place-fg(x: x3 + 2.5em, y: (y0+y1)/2)[Delegate] }, { let stroke = black + 3pt let width = 13em let height = 4.5em set align(center+horizon) set rect(width: 250pt, height: 75pt, radius: 20pt, inset: 20pt) v(1fr) rect( stroke: stroke, height: height, width: width, { [*Boot Class Loader*] v(-0.5em) line(length: 80%) v(-0.5em) [_Platform Classes_] } ) v(1fr) stack( dir: ltr, 1fr, rect( stroke: stroke, height: height, width: width, { [*System Class Loader*] v(-0.5em) line(length: 80%) v(-0.5em) sym.emptyset } ), 1fr, rect( stroke: stroke, height: height, width: width, { [*APK Class Loader*] v(-0.5em) line(length: 80%) v(-0.5em) [_APK Classes_] } ), 1fr, ) v(1fr) }) #slide( title: [MultiDex] )[ #set align(center + horizon) #only(1)[ #apk-block[ #set align(left+top) === `app.apk` #line(length: 30%) ``` AndroidManifest.xml resources.arsc META-INF/ res/ classes.dex ``` ] ] #only(2)[ #apk-block[ #set align(left+top) === `app.apk` #line(length: 50%) #stack(dir: ltr, ``` AndroidManifest.xml resources.arsc META-INF/ res/ classes.dex ```, h(2em),[ ``` classes2.dex classes3.dex ``` ] ) ] ] #only(3)[ #apk-block[ #set align(left+top) === `app.apk` #line(length: 75%) #stack(dir:ltr, ``` AndroidManifest.xml resources.arsc META-INF/ res/ classes.dex classes2.dex classes3.dex ```, h(2em), ``` classes4.dex classes5.dex classes6.dex classes7.dex classes8.dex classes9.dex classes10.dex ```, h(2em), ``` classes11.dex classes12.dex classes13.dex classes14.dex classes15.dex classes16.dex ... ``` ) ] #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%) #show "classes.dex": set text(fill: pirat-color.red.darken(10%)) #show "classes2.dex": set text(fill: pirat-color.red.darken(10%)) #show "classes3.dex": set text(fill: pirat-color.red.darken(10%)) ``` 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" ```) ] ) ] ] #for i in range(3) { counter("logical-slide").update( n => n - 1 ) show table.cell: c => { if c.y >= 1 and ((i < 2 and c.x == 2) or (i < 1 and c.x == 1)) { [] } else { c } } slide( title: [Self-Shadowing], )[ #grid( columns: (2fr, 0.5em, 3fr), 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" ```) #align(bottom, text(size: 0.75em)[#sym.star estimation, non-deterministic]) ], [], { set text(size: 24pt) show table.cell: set align(center+horizon) let r = text.with(fill: pirat-color.red.darken(10%)) table( columns: (1fr, 1fr, 1fr), //inset: (x,y) => if y == 0 { 10pt } else { 3pt }, stroke: none, table.header( [Android], table.vline(), [Soot], table.vline(), [Androguard#super[#sym.star]] ), table.hline(), [`classes.dex`], [`classes.dex`], [`classes10.dex`], ..if (i != 0) {( [], r[`classes1.dex`], [], [], r[`classes10.dex`], [], )}, [`classes2.dex`], [`classes2.dex`], [`classes9.dex`], [`classes3.dex`], [`classes3.dex`], [`classes8.dex`], table.cell(colspan: 3, inset: -3pt)[...], [`classes9.dex`], [`classes9.dex`], [`classes2.dex`], [`classes10.dex`], [], [`classes1.dex`], strike[`classes1.dex`], [], [`classes.dex`], )} ) ] } #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 == 2 { codly( highlighted-lines: (7, 8), ..default-codly ) } else if i == 1 { 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(55%, 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() ```) #v(-1em) #smallcaps(text(size: 15pt)[Android Class Loading Algorithm]) ], [ #set align(left) #set text(size: 18pt) #partial-hide(i, hidden: (0,), partial: (2,))[ === Self Shadowing Trick the tool into using an APK class instead of another APK class ] #partial-hide(i, hidden: (0, 1))[ === SDK shadowing Trick the tool into using an APK class instead of an SDK class ] #partial-hide(i, hidden: (0, 1))[ === Hidden API shadowing Trick the tool into using an APK class instead of an hidden API class ] ] ) ] } #slide( title: [Impact on Tools], // TODO: IF PR: Add REF foreground: move(dx: 300pt, rotate(30deg, text(fill: pirat-color.red, size: 30pt)[We want PR!])), )[ #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 } #show "not working": "attack failed" #show "working": "attack sucessful" #show "works": "sucessful" #scale(100%, reflow: true, get_figure()) ] #slide( title: [Androguard on Toy Malware], 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(), figure( image("slides/imgs/call_graph_expected.svg", width: 100%), caption: [Expected], supplement: none, ), //get_figure() figure( image("slides/imgs/call_graph_obf.svg", width: 100%), caption: [Computed], supplement: none, ) ) ] #for i in range(3) { if i != 0 { counter("logical-slide").update( n => n - 1 ) } slide( title: [In the Wild: 49 975 APKs], )[ #set align(center+horizon) // TODO: Simplifier table, mettre nb apk dans titre // enlever SDK et 1ere partie 100% //#scale(90%, reflow: true, get_figure()) #import "4_class_loader/X_var.typ": scan_50k, scan_only_shadow #import "lib.typ": num #let nb_col = 7 #scale(90%, reflow: true, table( columns: nb_col, stroke: none, align: center+horizon, fill: (x, y) => { if ( i == 1 and (x, y) in ((1, 4), (2, 4), (4, 4), (6, 4)) ) or ( i == 2 and (x, y) in ((1, 5), (2, 5)) ) { highlight-color } else { none } }, inset: (x: 0% + 7pt, y: 0% + 5pt), table.hline(), table.header( table.cell(colspan: nb_col, inset: 3pt)[], table.cell(rowspan: 2, eye-2(x: 35pt, y: 30pt, height: 70pt)), table.vline(end: 3), table.vline(start: 4), table.cell(colspan: 3)[*Number of apps*], table.vline(end: 3), table.vline(start: 4), table.cell(colspan: 2)[*Nb Shadow Classes*], table.vline(end: 3), table.vline(start: 4), table.cell(rowspan: 2)[*Identical Code*], [], [%], [% malware], [Average], [Median], ), table.hline(), table.cell(colspan: nb_col, inset: 3pt)[], ..scan_only_shadow.map(e => ( [*#e.method*], num(e.nbapp), [#e.ratioapp%], [#e.ratiomal%], num(e.avgshadow), num(e.median), [#e.id%] )).flatten(), table.cell(colspan: nb_col, inset: 3pt)[], table.hline(), )) ] } #slide( title: [Conclusion] )[ #v(1fr) #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 ] #v(1fr) #align(center, text(fill: pirat-color.blue.darken(30%))[Digital Threats: Research and Practice, vol. 6 (3), 2025]) #v(0.5em) ] #new-section-slide([The Application of Theseus]) // TODO put everywhere Theseus Transformeur #slide( title: [Dexhunter: Zang #etal (2015)], )[ #align(center+horizon, dexhunter-outline( small_icon_size: 75pt, big_icon_size: 200pt, )) ] #slide( title: [DroidRA: Li #etal (2016)], )[ #align(center+horizon, droidra-outline( small_icon_size: 75pt, big_icon_size: 150pt, )) ] #slide( title: [Theseus: Overview], )[ #set align(center+horizon) #only(1, theseus-outline(stage: "theseus-no-static")) #only(2, theseus-outline(stage: "theseus")) ] #slide( title: [Dynamic Analysis], )[ - Frida: intercepts method calls #v(2em) #uncover("2-")[ - Android Emulator: runs on computer/server - Grodd Runner: clicks buttons (Abraham #etal, 2015) ] #v(2em) #uncover(3)[ - Phone with adb enable: actuall hardware - Human: intelligent button clicker ] ] #slide( title: [Transformation: Dynamic Code Loading], foreground: { ghost-6(x: 80%, y: 15%, mirror: true) align(horizon+center, line(length: 80%, stroke: (thickness: 3pt, dash: (10pt, 5pt)))) place(horizon+right, dx: -1em)[ Collected at Runtime Patched Application ] } )[ // Split schema: observed dyn code loaded / new apk #set align(center+horizon) #show figure.caption: none #show image: box.with(width: 58%) #get_figure() ] #for i in range(4) { // TODO: plutot barrer les lignes au lieux de les remplacer if i != 0 { counter("logical-slide").update( n => n - 1 ) } slide( title: [Transformation: 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: (6,), ..default-codly ) } else if i == 4 { codly( offset: 5, highlights: ( (line: 6, start: 0, end: 25, fill: pirat-color.blue), ), ..default-codly ) } else { codly( offset: 5, ..default-codly ) } ```java String retData = (String) mth.invoke(obj, args); ``` #v(-0.5em) #align(center+horizon, sym.arrow.b.stroked) #v(-0.5em) #if i == 1 { codly( offset: 5, highlighted-lines: (10,), ..default-codly ) } else if i == 3 { 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+9, end: 32, fill: pirat-color.blue), ), ..default-codly ) } else if i == 4 { codly( offset: 5, highlights: ( (line: 8, start: 12, end: 19, fill: pirat-color.blue), (line: 12, start: 0, end: 25, fill: pirat-color.blue), ), ..default-codly ) } else if i == 2 { 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( foreground: { arrow( stroke: 10pt + pirat-color.red, ( 250pt, -400pt, ), ( 250pt, -350pt, ) ) arrow( stroke: 10pt + pirat-color.red, ( 130pt, -400pt, ), ( 180pt, -320pt, ) ) arrow( stroke: 10pt + pirat-color.red, ( 370pt, -400pt, ), ( 320pt, -320pt, ) ) } )[ #set align(center+horizon) #theseus-outline() ] #counter("logical-slide").update( n => n - 1 ) #slide( title: [Dynamic Analysis], foreground: ghost-1(x: 97%, y: 10%, height: 70pt) )[ #set align(center+horizon) #show figure.caption: none // TODO: enlever 1er 6iem pass, garder nb failed, remplacer vide par '-' sous '209' // enlever nb activity #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) )[ /* * Explications, give example of Google Services: * whithout service: does not work * with service: service recognize the app as malware and block it */ #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()) ] #slide( foreground: { arrow( stroke: 10pt + pirat-color.red, ( 770pt, -250pt, ), ( 620pt, -350pt, ) ) arrow( stroke: 10pt + pirat-color.red, ( 620pt, -350pt, ), ( 770pt, -250pt, ) ) } )[ #set align(center+horizon) #theseus-outline(stage: "theseus-vs-static") ] #for i in range(3) { counter("logical-slide").update( n => n - 1 ) slide( title: [Added Method Calls], )[ #set align(center+horizon) //#show link.where(dest: ): it => it.body //#show link.where(dest: ): it => it.body //#show figure.caption: none //#scale(90%, reflow: true, get_figure()) #import "5_theseus/X_var.typ": compared_callgraph #import "lib.typ": num #let nb_col = 3 #table( columns: (1fr, 1.1fr, 1fr), align: center+horizon, stroke: none, fill: (x, y) => if ( i == 1 and (x == 1 and y > 1 and y != 9) ) or ( i == 2 and (x == 2 and y > 1) ){ highlight-color } else { none }, table.hline(), table.header( //[SHA 256], [Original CG edges], [New CG edges], [Edges added], [Reflection edges added], table.cell(rowspan: 2)[APK SHA 256], table.cell(colspan: nb_col - 1)[Number of Call Graph edges], [Diff (Total After - Before)], [Added Reflection], ), table.hline(), ..compared_callgraph.map( (e) => ( [#lower(e.sha256).slice(0, 10)...], num(e.added), num(e.added_ref_only) )).flatten(), [#lower("5D2CD1D10ABE9B1E8D93C4C339A6B4E3D75895DE1FC49E248248B5F0B05EF1CE").slice(0, 10)...], table.cell(colspan: nb_col - 1)[_Transformation Crashed_], table.hline(), ) ] } /* #slide( title: [Toy Example: New Call Graph], foreground: ghost-3(x: 93%, y: 10%) )[ // TODO: Légende des couleurs #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], foreground: { let strk = 3pt + pirat-color.blue import "slides/icons.typ": arrow arrow((290pt, -335pt), (320pt, -310pt), strk: strk) arrow((400pt, -335pt), (340pt, -310pt), strk: strk) } )[ #set align(center+horizon) #show figure.caption: none #move(dx: -70pt)[Original #h(2em) Transformed] /* * JFL bet on a question about SAAF */ #box(width: 80%, 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[ We showed that: #item-by-item[ - Most Static analysis tools are no longer usable after a few years. The size of the application seems to be the most significant factor. - Android behaviour is complex and not 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( title: [Future Works], 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: [Future Works] )[ #item-by-item[ - Benchmark of APKs to evaluate finishing rate - Make tools reusing sources from Android Open Source Project - Require developpers to provide high coverage tests inputs with the APKs ] ] #empty-slide[ Questions ] /* #slide()[ #get_figure()) ] #slide()[ #pl.toolbox.slide-number #context({ pl.toolbox.all-sections((sections, current) => { for i in sections { repr(i.at("body").at("text")) } }) }) ] */ #pagebreak() #set page(height: auto, margin: 25mm) #bibliography("bibliography.bib") /* * RETOUR 1: * * Pas de retour en arriere * Ne pas se retourner * * Bon premier jet. * * - slide text bof * - Parti 3: plus dure a comprendre * - Expliquer ce qui est fait avant le résultat (surtout parti 3) * * slite titre problemenatiques: PB1, PB2 PB3 * * Plus d'état de l'art, dans chapitres? redonner contexte au debut des chapitre, en profiter pour l'état de l'art. Pas plus d'un ou deux papiers, si important. * Remplacer l'état de l'art dans l'intro par intuition et mettre soa dans chapitre * * PQ: focu on dcl & refl * * RQ1: Static, RQ2: Check coherence static / dynamic, RQ3: transformation * */