Android Auto instrumentation

  • View
    171

  • Download
    6

  • Category

    Mobile

Preview:

Citation preview

|

Dependency Library injectionHow to extend any app without a single line of code.

|

Przemek JakubczykAndroid Technical LeadApplause

|

Motivation

• Applause ships its SDK which monitors app state• Often clients don’t want to mess with development process• Product/Project owner can do it himself• Or to configure SDK different way

|

The hack

|

Build chain (source)

Dalvik Executable (DEX)

|

Build chain (resources)

Resources binary container

|

Disassemble apk

Dex compiled classes

Binary resources

Smali files aka source code

PNG assets

Other xml based resources

|

Assemble back apk

Dex compiled classes

Binary resources

Smali files aka source code

PNG assets

Other xml based resources

|

Code example (Java)

src/main/res/values/strings.xml<resources> ... <string name="warning">Warning</string></resources>

src/main/java/com/example/MainActivity.java

import com.example.R;@Overrideprotected void onCreate(Bundle savedInstanceState) {

... Toast.makeText(this, R.string.warning, Toast.LENGTH_SHORT).show();}

|

Code example (smali)

.method protected onCreate(Landroid/os/Bundle;)V...

# string constant number const v0, 0x7f060001 # second Toast parameter const/4 v1, 0x0

invoke-static {p0, v0, v1}, Landroid/widget/Toast;->makeText(Landroid/content/Context;II)Landroid/widget/Toast;

...

.end method

|

<resources> <string name="warning">Warning</string> <string name="no_worries">No worries</string></resources>

Add new resource

res/values/strings.xml

<resources>...

<public type="string" name="warning" id="0x7f060001" /> <public type="string" name="no_worries" id="0x7f060002" /></resources>

res/values/public.xml

|

Modify Smali code

.method protected onCreate(Landroid/os/Bundle;)V...

# string constant number const v0, 0x7f060002 # second Toast parameter const/4 v1, 0x0

invoke-static {p0, v0, v1}, Landroid/widget/Toast;->makeText(Landroid/content/Context;II)Landroid/widget/Toast;

...

.end method

|

Let’s do it

We have:

Binary Library Snippet

|

Steps

1.Decompile and unpack2.Resources3.Android Manifest4.Merge codebase5.Find place to run the snippet6.Paste the snippet7.Sign the binary8.Limitations

|

apktool d pola.apk

Decompile and unpack

unzip -d library lib.aar

|

Steps

1.Decompile and unpack2.Resources3.Android Manifest4.Merge codebase5.Find place to run the snippet6.Paste the snippet7.Sign the binary8.Limitations

|

cp -r library/res pola-debug/res

Resources

|

Resources - extend references table

pola-debug/res/values/public.xml

<resources> <public type="layout" name="zxing_capture" id="0x7f04002e" /> <public type="layout" name="first_library_layout" id="0x7f03002f" />

... <public type="string" name="yes" id="0x7f080045" /> <public type="string" name="first_library_string" id="0x7f080046" />

... <public type="id" name="action_twitter" id="0x7f0e0099" /> <public type="id" name="first_library_id" id="0x7f0e009a" />

...</resources>

|

Resources - extend R.java

pola/smali/pl/pola_app/R$string.smali

.class public final Lpl/pola_app/R$string;

.super Ljava/lang/Object;

.source "R.java"

# static fields

...

.field public static final zxing_capture:I = 0x7f080045

.field public static final first_library_string:I = 0x7f03002f

|

While compiling a normal application all fields in R.java are public static final

Resources - digression

CreateReportActivity.smaliconst v2, 0x7f080027

R$string.javapublic static int dialog_delete_photo = 0x7f080027;

Java compiler treats them as constants and inserts a value instead of reference

|

R$string.javapublic static int library_titile=0x7f0500d3;

Resources - digression

sget v1, Lcom/example/R$string;->library_titile:I

In library R.java all fields are public static not final! This means that compiler won’t treat them as constant and will leave references.

R$string.smali.field public static library_titile:I = 0x7f050021

|

• No need to modify library code referencing resources.• While compile a normal application all fields in R.java are public static final

so java compiler treats them as constans and inserts a value instead of reference.

R.string.zxing_capture -> 0x7f080045

• In library R.java all fields are public static not final! This means that compiler won’t treat them as constant and will leave references.

• R.string.first_library_string -> R.string.first_library_string

Resources

|

Steps

1.Decompile and unpack2.Resources3.Android Manifest4.Merge codebase5.Find place to run the snippet6.Paste the snippet7.Sign the binary8.Limitations

|

Android Manifest

<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="pl.pola_app" platformBuildVersionCode="23" platformBuildVersionName="6.0-2704002">

... <uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.BLUETOOTH" />

<application android:icon="@drawable/ic_launcher" android:label="@string/app_name"/> <activity android:name="pl.pola_app.ui.activity.MainActivity"/>

... <activity android:name="com.mylibrary.HelloActivity"/> </application></manifest>

|

Steps

1.Decompile and unpack2.Resources3.Android Manifest4.Merge codebase5.Find place to run the snippet6.Paste the snippet7.Sign the binary8.Limitations

|

• Aar container has classes• Use dx --dex to convert classes to .dex• And use smali.jar to convert to .smali• Merge two smali dirs

Merge codebase

|

Steps

1.Decompile and unpack2.Resources3.Android Manifest4.Merge codebase5.Find place to run the snippet6.Paste the snippet7.Sign the binary8.Limitations

|

• Having all compiled code in one place is a half-success. • We could use apktool to build back the binary but our code isn’t referenced

an application’s code.• Our business requirement was to start Applause as early as it’s possible.• android.app.Application.onCreate()

Find place to run the snippet

|

Find place to run the snippet

app-debug/AndroidManifest.xml

<manifest package="pl.pola_app" >...

<application android:name="pl.pola_app.PolaApplication"... />...

</application>

</manifest>

app-debug/smali/pl/pola_app/PolaApplication.smali

|

Steps

1.Decompile and unpack2.Resources3.Android Manifest4.Merge codebase5.Find place to run the snippet6.Paste the snippet7.Sign the binary8.Limitations

|

• Change onCreate method• Paste our code before applications’ code• Let’s check Pola’s code

Paste the snippet - method I

31

|

@Override public void onCreate() { super.onCreate();

component = PolaComponent.Initializer.init(this); if(BuildConfig.USE_CRASHLYTICS) { Fabric.with(this, new Crashlytics()); } ButterKnife.setDebug(BuildConfig.DEBUG);

if (BuildConfig.DEBUG) { Timber.plant(new Timber.DebugTree()); } else { Timber.plant(new CrashReportingTree()); }

OkHttpClient client = new OkHttpClient(); client.setConnectTimeout(Utils.TIMEOUT_SECONDS, TimeUnit.SECONDS); client.setReadTimeout(Utils.TIMEOUT_SECONDS, TimeUnit.SECONDS);

retrofit = new Retrofit.Builder() .baseUrl(this.getResources().getString(R.string.pola_api_url)) .addConverterFactory(GsonConverterFactory.create()) .client(client) .build();}

32

|

Transforms to ...

|

.method public onCreate()V .locals 6

.prologue const-wide/16 v4, 0x14

.line 26 invoke-super {p0}, Landroid/app/Application;->onCreate()V

.line 28 invoke-static {p0}, Lpl/pola_app/internal/di/PolaComponent$Initializer;->init(Lpl/pola_app/PolaApplication;)Lpl/pola_app/internal/di/PolaComponent; move-result-object v1 iput-object v1, p0, Lpl/pola_app/PolaApplication;->component:Lpl/pola_app/internal/di/PolaComponent;

.line 32 sget-boolean v1, Lpl/pola_app/BuildConfig;->DEBUG:Z invoke-static {v1}, Lbutterknife/ButterKnife;->setDebug(Z)V

Smali #1

34

|

.line 34 sget-boolean v1, Lpl/pola_app/BuildConfig;->DEBUG:Z if-eqz v1, :cond_0

.line 35 new-instance v1, Ltimber/log/Timber$DebugTree; invoke-direct {v1}, Ltimber/log/Timber$DebugTree;-><init>()V invoke-static {v1}, Ltimber/log/Timber;->plant(Ltimber/log/Timber$Tree;)V

.line 40 :goto_0 new-instance v0, Lcom/squareup/okhttp/OkHttpClient; invoke-direct {v0}, Lcom/squareup/okhttp/OkHttpClient;-><init>()V

.line 41 .local v0, "client":Lcom/squareup/okhttp/OkHttpClient; sget-object v1, Ljava/util/concurrent/TimeUnit;->SECONDS:Ljava/util/concurrent/TimeUnit; invoke-virtual {v0, v4, v5, v1}, Lcom/squareup/okhttp/OkHttpClient;->setConnectTimeout(JLjava/util/concurrent/TimeUnit;)V

Smali #2

35

|

.line 42 sget-object v1, Ljava/util/concurrent/TimeUnit;->SECONDS:Ljava/util/concurrent/TimeUnit; invoke-virtual {v0, v4, v5, v1}, Lcom/squareup/okhttp/OkHttpClient;->setReadTimeout(JLjava/util/concurrent/TimeUnit;)V

.line 44 new-instance v1, Lretrofit/Retrofit$Builder; invoke-direct {v1}, Lretrofit/Retrofit$Builder;-><init>()V

.line 45 invoke-virtual {p0}, Lpl/pola_app/PolaApplication;->getResources()Landroid/content/res/Resources; move-result-object v2 const v3, 0x7f08002f invoke-virtual {v2, v3}, Landroid/content/res/Resources;->getString(I)Ljava/lang/String; move-result-object v2 invoke-virtual {v1, v2}, Lretrofit/Retrofit$Builder;->baseUrl(Ljava/lang/String;)Lretrofit/Retrofit$Builder; move-result-object v1

Smali #3

36

|

.line 46 invoke-static {}, Lretrofit/GsonConverterFactory;->create()Lretrofit/GsonConverterFactory; move-result-object v2 invoke-virtual {v1, v2}, Lretrofit/Retrofit$Builder;->addConverterFactory(Lretrofit/Converter$Factory;)Lretrofit/Retrofit$Builder; move-result-object v1

.line 47 invoke-virtual {v1, v0}, Lretrofit/Retrofit$Builder;->client(Lcom/squareup/okhttp/OkHttpClient;)Lretrofit/Retrofit$Builder; move-result-object v1

.line 48 invoke-virtual {v1}, Lretrofit/Retrofit$Builder;->build()Lretrofit/Retrofit; move-result-object v1 sput-object v1, Lpl/pola_app/PolaApplication;->retrofit:Lretrofit/Retrofit;

Smali #4

37

|

.line 49 return-void

.line 37 .end local v0 # "client":Lcom/squareup/okhttp/OkHttpClient; :cond_0 new-instance v1, Lpl/pola_app/PolaApplication$CrashReportingTree; const/4 v2, 0x0 invoke-direct {v1, v2}, Lpl/pola_app/PolaApplication$CrashReportingTree;-><init>(Lpl/pola_app/PolaApplication$1;)V invoke-static {v1}, Ltimber/log/Timber;->plant(Ltimber/log/Timber$Tree;)V goto :goto_0.end method

Smali #5

38

|

• Paste Applause.startNewSession(context, “app_key”) smali equivalent

• Would be easy ...• But need to be very careful on smali registries - variable, fields, params

Paste the snippet - method I

|

• Replace application:name in Android Manifest to LibraryApplication

Paste the snippet - method II

import android.app.Application

class LibraryApplication extends Application {}

import pl.pola_app.PolaApplication

class LibraryApplication extends PolaApplication {}

• Change base class of our LibraryApplication to one found in original Android Manifest

• Ensure all super calls

<application android:name=".PolApplication” ... >

<application android:name=".LibraryApplication” ... >

|

• Replace super class in header to LibraryApplication

Paste the snippet - method II (PolaApplication.smali)

.method public onCreate()V .line 26 invoke-super {p0},

Landroid/app/Application; ->onCreate()V

.method public onCreate()V .line 26 invoke-super {p0},

Lcom/example/LibraryApplication; ->onCreate()V

• Change all super calls

.class public Lpl/pola_app/PolaApplication;.super Landroid/app/Application;.source "PolaApplication.java"

.class public Lpl/pola_app/PolaApplication;.super Lcom/example/LibraryApplication;.source "PolaApplication.java"

|

Steps

1.Decompile and unpack2.Resources3.Android Manifest4.Merge codebase5.Find place to run the snippet6.Paste the snippet7.Sign the binary8.Limitations

|

• The seal is broken

Sign the binary

md5(“pola.apk”) != md5(“new_pola.apk”)

• I am using a dummy certificate in order to run the binary on device• However some of the main application functionalities won’t work• For example Google Service are checking if certificate’s fingerprint equals

the one set in developer console.

|

Steps

1.Decompile and unpack2.Resources3.Android Manifest4.Merge codebase5.Find place to run the snippet6.Paste the snippet7.Sign the binary8.Limitations

|

• 64k method limit. Adding your library might hit the ceiling. A semi-solution is the check if app supports multi-dex and utilize more smali dirs

• Custom attributes are compiled a bit different. We’ve agreed not to use it to have simpler instrumentation script.

• Dexguard uses not genuine aapt compiler where apktool uses the one from Google which is more strict with file naming.

Limitations

|

Run and enjoy!

|

Questions?

|

THANK YOU

Thanks

and catch me on evening beer session

Recommended