Introduction️

Various applications related to banking environments or public administrations contain restrictions that prevent their execution on Android mobile devices that have been altered, for example, after installing Magisk and obtaining super administrator permissions, unlocking the boot loader, or if they are running in emulators and not on real devices, all of this to avoid analysis.️

In an example application, we will have to remove those restrictions from the source code of the application. One option will be to unpack the application and modify the .smali code, which is a type of low-level non-binary bytecode that can be read. For this, we will first extract the corresponding APK file for the application and decompile the source code of the application using the tool JADX.️

Extracting the APK file

The Android applications are composed of a compressed file with the .apk format (which is actually a ZIP file). They can be made up of only one file or several in the case of “split APKs”, divided applications that contain files corresponding to specific processor architecture, screen resolution or supported languages. We will use the adb tool to search and extract the files. In this case we are looking for the package with identifier com.testapplication.️

adb shell pm path com.testapplication

We will obtain a list of routes where the application packages are located, and we will also extract them using adb.️

adb pull /data/app/.../base.apk
...
adb pull /data/app/.../split_config.es.apk

When you open the app, it displays the message This application cannot be opened on this device due to security reasons. Next, we open the APK file, in this case base.apk, which contains the compiled Java or Kotlin code for the main application, using JADX. We then search for a piece of text with the identifier insecure_modified_app_os_body in the file /res/values/strings.xml.️

We are searching for this identifier in the extracted APK files. We found the activity com.testapplication.InsecureAppActivity. The source code is fully obfuscated with Proguard, but we can observe a series of variables in this class.️

public /* synthetic */ class a {
            /* renamed from: a */
            public static final /* synthetic */ int[] f8742a;
            static {
                int[] iArr = new int[EnumC2230a.values().length];
                iArr[EnumC2230a.ROOT_OS_APP.ordinal()] = 1;
                iArr[EnumC2230a.NO_BIOMETRY.ordinal()] = 2;
                iArr[EnumC2230a.OUT_OF_SERVICE.ordinal()] = 3;
                iArr[EnumC2230a.NEW_UPDATE.ordinal()] = 4;
                iArr[EnumC2230a.NO_INTERNET.ordinal()] = 5;
                f8742a = iArr;
            }
        }

It appears to be a series of conditions under which the application will not open. In our case, it seems that it’s the ROOT_OS_APP. We are looking for references to that variable and found a function called jd.a.️

public final void a(PiracyCheckerError error) {
        kotlin.jvm.internal.i.f(error, "error");
        dg.a aVar = dg.a.ROOT_OS_APP;
        int i10 = MainActivity.f8190j;
        MainActivity mainActivity = this.f14200a;
        mainActivity.k(aVar);
        kd.i iVar = mainActivity.f8194g;
        if (iVar != null) {
            iVar.a(a.a.G(new ih.h("InsecureAppType", aVar), new ih.h("deeplink", String.valueOf(this.f14201b))), "doNotAllow on performSecurityCheck", null);
        } else {
            kotlin.jvm.internal.i.l("crashlyticsManager");
            throw null;
        }
    }

This function receives a PiracyCheckerError object as a parameter, so we can confirm that the application is using the PiracyChecker library to check the conditions of the restrictions. Searching for the use of this function, we found it in the main activity MainActivity, specifically in the function MainActivity.a.C0115a.invokeSuspend. In this function, in particular, we find code that checks if the device is an emulator.️

...
try {
    product = Build.PRODUCT;
} catch (Throwable unused3) {
    product = "";
}
kotlin.jvm.internal.i.e(product, "product");
int i18 = (n.m0(product, "sdk", true) || n.m0(product, "Andy", true) || n.m0(product, "ttVM_Hdragon", true) || n.m0(product, "google_sdk", true) || n.m0(product, "Droid4X", true) || n.m0(product, "nox", true) || n.m0(product, "sdk_x86", true) || n.m0(product, "sdk_google", true) || n.m0(product, "vbox86p", true)) ? 1 : 0;
try {
    manufacturer = Build.MANUFACTURER;
} catch (Throwable unused4) {
    manufacturer = "";
}
...

Or if it is rooted:

...
i12 = 1;
process.destroy();
i11 = i12;
if (i11 == 0) {
    String[] strArr2 = {
        "/system/app/Superuser.apk",
        "/sbin/su",
        "/system/bin/su",
        "/system/xbin/su",
        "/data/local/xbin/su",
        "/data/local/bin/su",
        "/system/sd/xbin/su",
        "/system/bin/failsafe/su",
        "/data/local/su",
        "/su/bin/su"
    };
    int i19 = i10;
    while (true) {
        if (i19 >= 10) {
            i13 = i10;
            break;
        }
        if (new File(strArr2[i19]).exists()) {
            i13 = 1;
            break;
        }
        i19++;
    }
    if (i13 == 0) {}
...

Initially in the function invokeSuspend we find the following code:️

...
int i16 = MainActivity.f8190j;
mainActivity.getClass();
x xVar = new x(mainActivity, this.f8201c, mainActivity);
me.c cVar = new me.c(mainActivity);
xVar.invoke(cVar);
mainActivity.f8195h = cVar;
if (cVar.f16273d) {
...

This code indicates that if the attribute f16273d of the object cVar in class me.c is true, then the code within curly brackets will be executed. The code within these brackets contains all the observed protections as mentioned earlier. If this value could be changed to false, then the code would not be executed and normal application execution would continue. Searching for this variable only shows one assignment in class jd.x stored previously in variable xVar, in its function invoke.️

public final ih.m invoke(me.c cVar) {
    me.c piracyChecker = cVar;
    kotlin.jvm.internal.i.f(piracyChecker, "$this$piracyChecker");
    piracyChecker.f16273d = true;
    piracyChecker.f16274e = (String[]) Arrays.copyOf(new String[] {
        "Gh45tyl10XPMht8jtrhamkRUInI="
    }, 1);
    piracyChecker.f16276g = true;
    piracyChecker.f16275f.addAll(a.a.G(Arrays.copyOf(new ne.a[] {
        ne.a.GOOGLE_PLAY
    }, 1)));
    piracyChecker.f16277h = true;
    MainActivity mainActivity = this.f14202a;
    String str = this.f14203b;
    piracyChecker.f16271b = new v(mainActivity, str, this.f14204c);
    piracyChecker.f16272c = new w(mainActivity, str);
    return ih.m.f13121a;
}

Observing the PiracyChecker library, it can also check if the application has been modified by checking the hashes of the application signature, in this case, Gh45tyl10XPMht8jtrhamkRUInI= or if it has a valid Google Play license in case the application is paid. We found that the variable f16273d has been set to true.️

Deobfuscation and modification of the Smali code

We will use the application apktool to unpack and repack the application, and uber-apk-signer to sign and align the re-packed application. First, we copy the application files to an additional folder.️

mkdir app
cp *.apk app

We unpack the application.️

java -jar apktool_2.9.3.jar d app/base.apk

We found the Smali file of class x in directory base/smali/jd/x.smali.️

.method public final invoke(Ljava/lang/Object;)Ljava/lang/Object;
    .locals 3
    check-cast p1, Lme/c;
    const-string v0, "$this$piracyChecker"
    invoke-static {p1, v0}, Lkotlin/jvm/internal/i;->f(Ljava/lang/Object;Ljava/lang/String;)V
    const-string v0, "Gh45tyl10XPMht8jtrhamkRUInI="
    filled-new-array {v0}, [Ljava/lang/String;
    move-result-object v0
    const/4 v1, 0x1
...

The line const/4 v1, 0x1 is the one that stores the temporary variable, in this case 0x1 = true. Modifying the variable to 0x0 will make the variable f16273d false. We recompile the application with apktool.️

java -jar apktool_2.9.3.jar b base

The application will be packaged in the directory base/dist/base.apk, and we will copy it to the previous directory.️

cp base/dist/base.apk app

From now on we will sign the modified package and any others that may be present.️

java -jar uber-apk-signer-1.3.0.jar --allowResign -a app

From now on, we will have to uninstall the original application package as the signature has been changed. To install the new application, we can use adb install if the application contains a single package or adb install-multiple if it has multiple packages. The packages to be installed end with the suffix -aligned-debugSigned.️

adb install-multiple app/base-aligned-debugSigned.apk app/split_config.es-aligned-debugSigned.apk app/split_config.x86_64-aligned-debugSigned.apk app/split_config.xxhdpi-aligned-debugSigned.apk

The application will start without displaying the restriction shown above.️

Conclusion️

The various personalized protections included in the applications will require an individualized analysis for each one of them. Other protection measures in applications may be symmetric encryption of network communications.️