██     ████                                                       
              ██     ████                                                       
  ▒█████░   ████       ██      ██    ██   ██░████            ██▓█▒██▒   ░████▒  
 ████████   ████       ██      ██    ██   ███████            ████████  ░██████▒ 
 ██▒  ░▒█     ██       ██      ██    ██   ███░               ██░██░██  ██▒  ▒██ 
 █████▓░      ██       ██      ██    ██   ██                 ██ ██ ██  ████████ 
 ░██████▒     ██       ██      ██    ██   ██                 ██ ██ ██  ████████ 
    ░▒▓██     ██       ██      ██    ██   ██                 ██ ██ ██  ██       
 █▒░  ▒██     ██       ██▒     ██▒  ███   ██          ██     ██ ██ ██  ███░  ▒█ 
 ████████  ████████    █████   ▓███████   ██          ██     ██ ██ ██  ░███████ 
 ░▓████▓   ████████    ░████    ▓███░██   ██          ██     ██ ██ ██   ░█████▒ 

Modding an APK to disable tracking

by silur

Trinet, tracking and play store

Marketing people are the worst. They treat customers as their KPI for their paychecks, simple assets you mine value from. Your android apps are swarming with super-intrusive analytic softwares. Yes, plural, as it’s not uncommon for a single apk to contain over 15 different tracking/analytics SDKs… you know just in case.

Thankfully, tracking malware is banned from F-Droid (where I’m living my android life) but in some cases you JUST need that one closed-source app for a bigger reason than your privacy extremism.

For these cases I documented my steps on how I removed tracking malware from Hushed using apktool and a little smali hackery.

Enumerate trackers

I used the excellent exodus privacy tool to get a view how many trackers I had to disable: https://reports.exodus-privacy.eu.org/en/reports/111965/

hushed exodus report

Decompile the apk for modding

Modding apk’s mostly happen by using a combination of apktool and bytecode-viewer

For this project apktool in itself is enough because we’ll mostly edit manifests and only a small amount of smali (heh :)

In cases where deciphering the smali code by hand is too much, I like to cross-check the files with bytecode-viewer to get a high-level view in Java, and then edit the interesting part in the smali version.

apktool d hushed_original.apk

Search for easy killswitches

On my first trial, I immediately jumped into deleting com.firebase.analytics and some of it’s callers from the main HushedApp.smali activities and I had to realize quickly that this package had a lot of callers, and also used by other dependencies. The naive “replace every init call with stubs” method will quickly lead to Runtime Exceptions this way.

Then I was looking for one-liner killswitches as I expected that these SDKs have to offer a way to save their backend from pollution by debug builds.

My feeling was right, as firebase and facebook both offer meta-data tags that disable remote communication:

<meta-data android:name="firebase_analytics_collection_deactivated"
<meta-data android:name="google_analytics_adid_collection_enabled"
			android:value="false" />
        <meta-data android:name="google_analytics_ssaid_collection_enabled" 
			android:value="false" />
        <meta-data android:name="google_analytics_default_allow_ad_personalization_signals" 
			android:value="false" />


For Adjust analytics I didn’t find an easy manifest-level killswitch or a flag inside values.xml so I had to check out their API. I looked for signature calls on their github and found that in all cases I should see a constructor of AdjustConfig and a manual call to the static Adjust.onCreate (lol?).

grep -R AdjustConfig

Thankfully only com.hushed.base.core.HushedApp used it and it was not subject to ProGuard:

.method private m0()V
    .locals 3
    new-instance v0, Lcom/adjust/sdk/AdjustConfig;
    const-string v1, "b2ssal02vdvk"
    sget-object v2, Lg/e/a/a;->a:Ljava/lang/Boolean;
    invoke-virtual {v2}, Ljava/lang/Boolean;->booleanValue()Z
    move-result v2
    if-eqz v2, :cond_0
    const-string v2, "sandbox"
    goto :goto_0
    const-string v2, "production"
    invoke-direct {v0, p0, v1, v2}, Lcom/adjust/sdk/AdjustConfig;-><init>(Landroid/content/Context;Ljava/lang/String;Ljava/lang/String;)V
    invoke-static {v0}, Lcom/adjust/sdk/Adjust;->onCreate(Lcom/adjust/sdk/AdjustConfig;)V
    new-instance v0, Lcom/hushed/base/core/HushedApp$g;
    iget-object v1, p0, Lcom/hushed/base/core/HushedApp;->a:Lcom/hushed/base/promotions/h;
    invoke-direct {v0, v1}, Lcom/hushed/base/core/HushedApp$g;-><init>(Lcom/hushed/base/promotions/h;)V
    invoke-virtual {p0, v0}, Landroid/app/Application;->registerActivityLifecycleCallbacks(Landroid/app/Application$ActivityLifecycleCallbacks;)V
    .catch Ljava/lang/Exception; {:try_start_0 .. :try_end_0} :catch_0
    goto :goto_1
    move-exception v0
    invoke-static {v0}, Lcom/hushed/base/core/g/b;->b(Ljava/lang/Throwable;)V
.end method

I wasn’t sure from the documentation whether the sandbox mode would actually be offline or just send data to a different dashboard/bucket/whatever, so just in case I replaced the whole function with a stub:

.method private m0()V
	.locals 0
.end method


For this one I didn’t have a code sample to find the entry point so I just used my android-intuitions. I expected that I’d need an activity or even better, the application context and a call to a static method just like it was the case with Adjust. So I searched the splash launcher and the main activity for a function that had getApplicationContext mixed with some com.appboy.* calls and eventually found this:

.method private n0()V
    .locals 3
    new-instance v0, Lcom/appboy/d;
    invoke-direct {v0}, Lcom/appboy/d;-><init>()V
    invoke-virtual {p0, v0}, Landroid/app/Application;->registerActivityLifecycleCallbacks(Landroid/app/Application$ActivityLifecycleCallbacks;)V
    new-instance v0, Lcom/hushed/base/core/util/t;
    invoke-virtual {p0}, Landroid/app/Application;->getApplicationContext()Landroid/content/Context;
    move-result-object v1
    invoke-direct {v0, v1}, Lcom/hushed/base/core/util/t;-><init>(Landroid/content/Context;)V
    invoke-static {}, Lcom/appboy/ui/inappmessage/AppboyInAppMessageManager;->getInstance()Lcom/appboy/ui/inappmessage/AppboyInAppMessageManager;
    move-result-object v1
    invoke-virtual {v1, v0}, Lcom/appboy/ui/inappmessage/AppboyInAppMessageManagerBase;->setCustomInAppMessageManagerListener(Lcom/appboy/ui/inappmessage/listeners/IInAppMess
    new-instance v0, Lcom/appboy/l/a$b;
    invoke-direct {v0}, Lcom/appboy/l/a$b;-><init>()V
    invoke-virtual {p0}, Landroid/app/Application;->getResources()Landroid/content/res/Resources;
    move-result-object v1
    const v2, 0x7f13004e
    invoke-virtual {v1, v2}, Landroid/content/res/Resources;->getString(I)Ljava/lang/String;
    move-result-object v1
    invoke-virtual {v0, v1}, Lcom/appboy/l/a$b;->D(Ljava/lang/String;)Lcom/appboy/l/a$b;
    invoke-virtual {p0}, Landroid/app/Application;->getResources()Landroid/content/res/Resources;
    move-result-object v1
	const v2, 0x7f13004d
    invoke-virtual {v1, v2}, Landroid/content/res/Resources;->getString(I)Ljava/lang/String;
    move-result-object v1
    invoke-virtual {v0, v1}, Lcom/appboy/l/a$b;->C(Ljava/lang/String;)Lcom/appboy/l/a$b;
    invoke-virtual {v0}, Lcom/appboy/l/a$b;->k()Lcom/appboy/l/a;
    move-result-object v0
    invoke-static {p0, v0}, Lcom/appboy/a;->w(Landroid/content/Context;Lcom/appboy/l/a;)Z
.end method

Now I only needed to insert a call to disableSdk right efter we got the context. According to the documentation:

In order to comply with data privacy regulations, data tracking activity on the Android SDK can be stopped entirely using the method disableSDK() . This method will cause all network connections to be canceled, and the Braze SDK will not pass any data to Braze’s servers.

invoke-virtual {p0}, Landroid/app/Application;->getApplicationContext()Landroid/content/Context;
move-result-object v1
invoke-static {v1}, Lcom.appboy;->disableSdk(Landroid/content/Context;)V
invoke-direct {v0, v1}, Lcom/hushed/base/core/util/t;-><init>(Landroid/content/Context;)V


Rebuilding itself is easy, just make sure to use --use-aapt with apktool as newer apps use aapt2 now:

apktool b --use-aapt2 -o hushed_mod.apk hushed

However, to be able to actually install and test you need to re-sign it with a self-signed key as you just broke the original APK signature.

keytool -genkey -v -keystore mykey.keystore
jarsigner -keystore mykey.keystore hushed.apk mykey

Did it work?

To see whether my phone is actually free from facebook/google malware I made an AP on my linux machine and fired up wireshark. I was looking for hosts from StevenBlack’s repo and first I was dissapointed because I did see some requests going out to facebook, but thankfully it was the embedded login assets in the LoginActivity. I did see the logcat msg I was waiting for from firebase too:

I/FA: Collection disabled with firebase_analytics_collection_deactivated=1