Contents

Why & Motivation

I wanted to monitor my mobile network traffic after I found some wierd dns logs in my custom dns server, I could not figure out which app made that request thus I found out this app in playstore called ‘No Root Firewall, Internet Data Blocker Protection’. it had good ui and satisfied 60% of my usage needs. The downside of this is they require a montly subscription for app features. This is bit much if you ask me so decided to mod it for personal use. In my defense, I did purchase it for a month.

Proof that I bought this thing
Proof that I bought this thing

My past posts cover which tools I use, how to decompile, configs, etc.

Decompiling

Extracing the app.


pm path com.placeholder.app

adb pull path_of_apk # one by one

# we get the following files

-rw-rw-r--  1 nouser nouser 6273850  base.apk
-rw-rw-r--  1 nouser nouser   90809  split_config.arm64_v8a.apk
-rw-rw-r--  1 nouser nouser  126542  split_config.xxhdpi.apk


This app uses app bundle so we make use of –use-aapt2 flag in apk-tool I am using apktool v2.5.0

Finding where to patch

after decompiling, immediately in mainActivity we find a call to this,

//from
public static boolean hasSubscription(Context context) {
    if (Settings.isLicenseActive(context)) {
        return true;
    }
    try {
        Subscription subscription = (Subscription) new TinyDB(context).getObject(Settings.SAVE_KEY_SUBSCRIPTION, Subscription.class);
        if (subscription == Subscription.Month || subscription == Subscription.Year || subscription == Subscription.Lifetime) {
            return true;
        }
        return false;
    } catch (Exception unused) {
        return false;
    }
}

// to
public static boolean hasSubscription(Context context){
    return true;
}

//from

public static boolean isLifeTime(Context context) {
    if (!hasSubscription(context)) {
        return false;
    }
    try {
        if (((Subscription) new TinyDB(context).getObject(Settings.SAVE_KEY_SUBSCRIPTION, Subscription.class)) == Subscription.Lifetime) {
            return true;
        }
        return false;
    } catch (Exception unused) {
        return false;
    }
}

//to
public static boolean isLifeTime(Context context){
    return true;
}

Smali logic for return true.


const/4 v1, 0x1

return v1

Patching, recompiling and installing it on device

while recompiling and signing, sign all the split apks with the same key, even if you did not touch the other split apks. (this is because android expects all the split apks to have the same signature)


zip -d ./split_config.arm64_v8a.apk META-INF/\*

zip -d ./split_config.xxhdpi.apk META-INF/\*

then sign them as mentioned from my previous posts.

For installing, since we have 3 apks


├── base.apk
├── split_config.arm64_v8a.apk
└── split_config.xxhdpi.apk


adb install-multiple ./base.apk ./split_config.arm64_v8a.apk  ./split_config.xxhdpi.apk

Premium features does not work fully

After installing and using it, it was apparent that some of the premium features were not working, like filter lists provided by the app, ip information and location presented visually in map.

In situations like this, there could be only one possible scenario and that is an additional check, it may be in the form of

  • app integrity, whether the app was tampered
  • was it not installed from google play store
  • verifying the purchase information via google services,
  • based on implementor’s thought process..etc,

Finding integrity checks, digging deeper for final patch.

Digging deeper we find the app along with integrity check,


    public static boolean detect(Context context) {
        PackageInfo packageInfo;
        Signature[] signatureArr;
        try {
            if (Build.VERSION.SDK_INT >= 28) {
                packageInfo = context.getPackageManager().getPackageInfo(context.getPackageName(), 134217728);
            } else {
                packageInfo = context.getPackageManager().getPackageInfo(context.getPackageName(), 64);
            }
            if (Build.VERSION.SDK_INT < 28) {
                signatureArr = packageInfo.signatures;
            } else if (packageInfo.signingInfo.hasMultipleSigners()) {
                signatureArr = packageInfo.signingInfo.getApkContentsSigners();
            } else {
                signatureArr = packageInfo.signingInfo.getSigningCertificateHistory();
            }
            try {
                String calculateSHA = calculateSHA("SHA-1", signatureArr[0].toByteArray());
                if (calculateSHA.equalsIgnoreCase("44F217A05AE9E2A8FB0F627528EABF8855FBC274") || calculateSHA.equalsIgnoreCase("8aa072cdd29068b79d30cbff9afb2d3ab89e7ec4") || calculateSHA.equalsIgnoreCase("dfe607e53da092636e47789497365fca36589cba")) {
                    return false;
                }
                return true;
            } catch (Exception e) {
                e.printStackTrace();
                return false;
            }
        } catch (Throwable unused) {
            return false;
        }
    }

    private static String calculateSHA(String str, byte[] bArr) {
        try {
            return FileUtils.byte2HexFormatted(MessageDigest.getInstance(str).digest(bArr));
        } catch (Exception e) {
            e.printStackTrace();
            return "";
        }
    }
}

So add it to the list of item to patch.

smali equivalent


const/4 v1,0x0

return v1

Interestingly we also find lucky patcher detection, I don’t use lucky patcher, so ignorning this.


 public static boolean hasLuckyPatcher(Context context) {
            PackageManager packageManager = context.getPackageManager();
            return isPackageInstalled("ru.aaaaaaad.installer", packageManager) || isPackageInstalled("ru.sxbuIDfx.pFSOyagrF", packageManager) || isPackageInstalled("ru.HUounqZv.qGDvALdrY", packageManager) || isPackageInstalled("ru.yFarPSsi.lSWLCBgGE", packageManager) || isPackageInstalled("ru.auLSaZJK.OldqqVPqY", packageManager) || isPackageInstalled("ru.HvZVLLax.FuBLzbTId", packageManager) || isPackageInstalled("ru.FxCVdppm.yVDnvQgJU", packageManager) || isPackageInstalled("ru.oCHfhtgN.LaiQlIeIK", packageManager) || isPackageInstalled("ru.ohHbeFjR.uZvxvLPnK", packageManager) || isPackageInstalled("ru.oSFnVIfs.fUUFExgWn", packageManager) || isPackageInstalled("ru.PDOIPrWH.abjKeIKLW", packageManager) || isPackageInstalled("ru.UaLzEHLI.yXTTBtSFW", packageManager) || isPackageInstalled("ru.uBVJgfKc.udsaLjziD", packageManager) || isPackageInstalled("com.chelpus.lackypatch", packageManager) || isPackageInstalled("com.dimonvideo.luckypatcher", packageManager) || isPackageInstalled("com.luckypatchers.luckypatcherinstaller", packageManager) || isPackageInstalled("com.android.vending.billing.InAppBillingService.LACK", packageManager) || isPackageInstalled("com.android.vending.billing.InAppBillingService.COIN", packageManager) || isPackageInstalled("com.android.vending.billing.InAppBillingService.LOCK", packageManager) || isPackageInstalled("com.android.vending.billing.InAppBillingService.CRAC", packageManager) || isPackageInstalled("com.android.vending.billing.InAppBillingService.COIO", packageManager);
        }

This time, we also mod the isLicenseActive setting function, since there are a number of calls from others parts of the app, I did not notice this earlier.

// from
public static boolean isLicenseActive(Context context) {
    TinyDB tinyDB = new TinyDB(context);
    String string = tinyDB.getString(LicenseActivation.SAVE_KEY_EXPIRE_DATE, "");
    if (!string.isEmpty()) {
        if (string.equals("0")) {
            return true;
        }
        try {
            if (new Date().getTime() <= new SimpleDateFormat("yyyyMMdd", Locale.getDefault()).parse(string).getTime()) {
                return true;
            }
            tinyDB.removeKey(LicenseActivation.SAVE_KEY_EXPIRE_DATE);
            checkProfessional(context, false, null);
            return false;
        } catch (ParseException unused) {
        }
    }
    return false;
}

//to

public static boolean isLicenseActive(Context context) {
    return true;
}

Patch the two places noted before, repeat the recompiling and installation step.

Now The app works with all the premium features.

End

I dont have to pay 3k every year for using this app.

If this post offends you in any way, contact me to it take it down.