Upload
przemek-jakubczyk
View
171
Download
6
Embed Size (px)
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