|
Dependency Library injection
How to extend any app without a single line of code.
|
Przemek Jakubczyk
Android Technical Lead
Applause
|
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;
@Override
protected 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 unpack
2.Resources
3.Android Manifest
4.Merge codebase
5.Find place to run the snippet
6.Paste the snippet
7.Sign the binary
8.Limitations
|
apktool d pola.apk
Decompile and unpack
unzip -d library lib.aar
|
Steps
1.Decompile and unpack
2.Resources
3.Android Manifest
4.Merge codebase
5.Find place to run the snippet
6.Paste the snippet
7.Sign the binary
8.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.smali
const v2, 0x7f080027
R$string.java
public static int dialog_delete_photo = 0x7f080027;
Java compiler treats them as constants and inserts a value instead of
reference
|
R$string.java
public 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 unpack
2.Resources
3.Android Manifest
4.Merge codebase
5.Find place to run the snippet
6.Paste the snippet
7.Sign the binary
8.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 unpack
2.Resources
3.Android Manifest
4.Merge codebase
5.Find place to run the snippet
6.Paste the snippet
7.Sign the binary
8.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 unpack
2.Resources
3.Android Manifest
4.Merge codebase
5.Find place to run the snippet
6.Paste the snippet
7.Sign the binary
8.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 unpack
2.Resources
3.Android Manifest
4.Merge codebase
5.Find place to run the snippet
6.Paste the snippet
7.Sign the binary
8.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 unpack
2.Resources
3.Android Manifest
4.Merge codebase
5.Find place to run the snippet
6.Paste the snippet
7.Sign the binary
8.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 unpack
2.Resources
3.Android Manifest
4.Merge codebase
5.Find place to run the snippet
6.Paste the snippet
7.Sign the binary
8.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

Android Auto instrumentation

  • 1.
    | Dependency Library injection Howto extend any app without a single line of code.
  • 2.
  • 3.
    | Motivation • Applause shipsits 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
  • 4.
  • 5.
  • 6.
  • 7.
    | Disassemble apk Dex compiledclasses Binary resources Smali files aka source code PNG assets Other xml based resources
  • 8.
    | Assemble back apk Dexcompiled classes Binary resources Smali files aka source code PNG assets Other xml based resources
  • 9.
    | Code example (Java) src/main/res/values/strings.xml <resources> ... <stringname="warning">Warning</string> </resources> src/main/java/com/example/MainActivity.java import com.example.R; @Override protected void onCreate(Bundle savedInstanceState) { ... Toast.makeText(this, R.string.warning, Toast.LENGTH_SHORT).show(); }
  • 10.
    | Code example (smali) .methodprotected 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
  • 11.
    | <resources> <string name="warning">Warning</string> <string name="no_worries">Noworries</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
  • 12.
    | Modify Smali code .methodprotected 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
  • 13.
    | Let’s do it Wehave: Binary Library Snippet
  • 14.
    | Steps 1.Decompile and unpack 2.Resources 3.AndroidManifest 4.Merge codebase 5.Find place to run the snippet 6.Paste the snippet 7.Sign the binary 8.Limitations
  • 15.
    | apktool d pola.apk Decompileand unpack unzip -d library lib.aar
  • 16.
    | Steps 1.Decompile and unpack 2.Resources 3.AndroidManifest 4.Merge codebase 5.Find place to run the snippet 6.Paste the snippet 7.Sign the binary 8.Limitations
  • 17.
    | cp -r library/respola-debug/res Resources
  • 18.
    | Resources - extendreferences 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>
  • 19.
    | Resources - extendR.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
  • 20.
    | While compiling anormal application all fields in R.java are public static final Resources - digression CreateReportActivity.smali const v2, 0x7f080027 R$string.java public static int dialog_delete_photo = 0x7f080027; Java compiler treats them as constants and inserts a value instead of reference
  • 21.
    | R$string.java public static intlibrary_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
  • 22.
    | • No needto 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
  • 23.
    | Steps 1.Decompile and unpack 2.Resources 3.AndroidManifest 4.Merge codebase 5.Find place to run the snippet 6.Paste the snippet 7.Sign the binary 8.Limitations
  • 24.
    | 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>
  • 25.
    | Steps 1.Decompile and unpack 2.Resources 3.AndroidManifest 4.Merge codebase 5.Find place to run the snippet 6.Paste the snippet 7.Sign the binary 8.Limitations
  • 26.
    | • Aar containerhas classes • Use dx --dex to convert classes to .dex • And use smali.jar to convert to .smali • Merge two smali dirs Merge codebase
  • 27.
    | Steps 1.Decompile and unpack 2.Resources 3.AndroidManifest 4.Merge codebase 5.Find place to run the snippet 6.Paste the snippet 7.Sign the binary 8.Limitations
  • 28.
    | • Having allcompiled 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
  • 29.
    | Find place torun 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
  • 30.
    | Steps 1.Decompile and unpack 2.Resources 3.AndroidManifest 4.Merge codebase 5.Find place to run the snippet 6.Paste the snippet 7.Sign the binary 8.Limitations
  • 31.
    | • Change onCreatemethod • Paste our code before applications’ code • Let’s check Pola’s code Paste the snippet - method I 31
  • 32.
    | @Override public voidonCreate() { 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
  • 33.
  • 34.
    | .method public onCreate()V .locals6 .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
  • 35.
    | .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
  • 36.
    | .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
  • 37.
    | .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
  • 38.
    | .line 49 return-void .line 37 .endlocal 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
  • 39.
    | • 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
  • 40.
    | • Replace application:namein 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” ... >
  • 41.
    | • Replace superclass 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"
  • 42.
    | Steps 1.Decompile and unpack 2.Resources 3.AndroidManifest 4.Merge codebase 5.Find place to run the snippet 6.Paste the snippet 7.Sign the binary 8.Limitations
  • 43.
    | • The sealis 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.
  • 44.
    | Steps 1.Decompile and unpack 2.Resources 3.AndroidManifest 4.Merge codebase 5.Find place to run the snippet 6.Paste the snippet 7.Sign the binary 8.Limitations
  • 45.
    | • 64k methodlimit. 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
  • 46.
  • 47.
  • 48.
    | THANK YOU Thanks and catchme on evening beer session

Editor's Notes

  • #4 Describe business case behind the project. Mind that it was done in Applause
  • #5 Take app from Google play Plus Take your library Produces Android application
  • #8 Why apktool where there are many others decompile tools? Works both ways and uses smali language -> text file not a binary (bytecode), easier to modify Add animation to arrows
  • #9 JVM to heap based register based jest dalvik
  • #11 A snippet from onCreate method
  • #13 A snippet from onCreate method
  • #14 Go go soldier !
  • #19 As shown before we have to extend the resource table 7f04002e Map values.xml (aar) to values.xml (apktool)
  • #20 Once resources are packed we have to address them in Java code. Android uses R.java to create the link. For strings there is R$strings.java file which has all mappings. Old_resource_name -> new value from previous step Repeat the step for each of resource types
  • #25 If your library has entries in Manifest we should add them too. Add all new Activities/Services etc. Extend permissions Merging two xml is easy.
  • #27 Apktool uses smali language as output and input. Application has been decompiled and inside there is a `smali` directory where you find all smali files. Aar contains a classes.jar - a container full of compiled java code *.class Use `dx` to compile to dex format. Use `smali` to convert dex to smali. Copy output to decompiled app `smali` dir
  • #29 Apktool uses smali language as output and input. Application has been decompiled and inside there is a `smali` directory where you find all smali files. Aar contains a classes.jar - a container full of compiled java code *.class Use `dx` to compile to dex format. Use `smali` to convert dex to smali. Copy output to decompiled app `smali` dir
  • #30 Since we have AndroidManifest.xml we can locate Application class which is going to be started by Android OS.
  • #32 Most of the apps have some kind of init in Application.onCreate() We must put our code before it Rename original onCreate() method to temp_onCreate() in order to not mess with method variables Paste smali representation of our snippet
  • #33 Most of the apps have some kind of init in Application.onCreate() We must put our code before it Rename original onCreate() method to temp_onCreate() in order to not mess with method variables Paste smali representation of our snippet
  • #35 JVM to heap based register based jest dalvik
  • #36 Most of the apps have some kind of init in Application.onCreate() We must put our code before it Rename original onCreate() method to temp_onCreate() in order to not mess with method variables Paste smali representation of our snippet
  • #37 Most of the apps have some kind of init in Application.onCreate() We must put our code before it Rename original onCreate() method to temp_onCreate() in order to not mess with method variables Paste smali representation of our snippet
  • #38 Most of the apps have some kind of init in Application.onCreate() We must put our code before it Rename original onCreate() method to temp_onCreate() in order to not mess with method variables Paste smali representation of our snippet
  • #39 Most of the apps have some kind of init in Application.onCreate() We must put our code before it Rename original onCreate() method to temp_onCreate() in order to not mess with method variables Paste smali representation of our snippet
  • #46 Compare out of an application using multidex. Classes.dex -> smali Classes2.dex -> smali2 Aar content should go to new smali dir. You code snippet should take plane in `smali` dir (first one). Classloaders and stuff