Statement: This article is a technical discussion and should not be used for illegal activities.

We know that the signature is an effective identification of Android software, because the signature secret key file is used by our unique, and when we after the app is to repackage, the signature of the app information is bound to be tampered with, all we can according to the release of the software runtime signature and signature of the same or not to decide whether to need to app to suspend operation. The common Java layer signature verification methods are as follows:

Signature verification

The Android SDK provides a method to detect software signatures. We can use the hashCode() method of the signature object to get a Hash value and compare its values in the code. Here is the code to get the signature information of the current run time:

    public static int getSignature(Context context) {
        PackageManager pm = context.getPackageManager();
        PackageInfo pi;
        StringBuilder sb = new StringBuilder();
        // Obtain the signature information
        try {
            pi = pm.getPackageInfo(context.getPackageName(), PackageManager.GET_SIGNATURES);
            Signature[] signatures = pi.signatures;
            for(Signature signature : signatures) { sb.append(signature.toCharsString()); }}catch (PackageManager.NameNotFoundException e) {
            e.printStackTrace();
        }
        return sb.toString().hashCode();
    }
Copy the code

Next we need to compare it to the signature we published, which has the Hash value MD5 encrypted here:

    int signature = getSignature(getApplicationContext());
    if(! MD5Util.getMD5(String.valueOf(signature)).equals("Signature value at publication")) {// May have been recompiled and need to exit
        android.os.Process.killProcess(android.os.Process.myPid());
    }
Copy the code

Crc32 verification of classes.dex

Recompiling apK usually means recompiling the classes file, and the Hash value of the generated classes.dex file will change after the code is recompiled, so we can check the Hash value of the classes.dex file after the program is installed to determine whether the software has been repackaged. The CRC algorithm is used to obtain the crC32 value of the currently running app.

    public static long getApkCRC(Context context) {
        ZipFile zf;
        try {
            zf = new ZipFile(context.getPackageCodePath());
            // Obtain the apK installation path
            ZipEntry ze = zf.getEntry("classes.dex");
            return ze.getCrc();
        }catch (Exception e){
            return 0; }}Copy the code

Now that we have the current value of crc32, all we need to do is compare it to the original value of crc32 when our app was released. This is the value of our Java logic, r.sing.classes_txt. Then AndroidStudio started packaging:

    String srcStr = MD5Util.getMD5(String.valueOf(CommentUtils.getApkCRC(getApplicationContext())));
    if(! srcStr.equals(getString(R.string.classes_txt))){// May have been recompiled and need to exit
        android.os.Process.killProcess(android.os.Process.myPid());
    }
Copy the code

After the package is complete, we get the crc32 value of apK’s classes.dex and assign the crc32 value to r.sing.classes_txt. Finally, you can repackage it with AndroidStudio (since changing the resource file doesn’t change the crc32 value of classe.dex, changing the code does). To obtain the crc32 value of classes.dex, use the Windows crc32 command tool as follows:

Java-level validation methods are vulnerable because the attacker can change our logic directly to bypass the validation, so we can only achieve a little bit of anti-hacking by increasing the amount of work it does.

You are advised to encrypt the CRc32 value or the signature hash value with MD5 encryption and compare the encrypted value in the code to prevent global search after decompilation. You are advised to combine signature verification with classes.dex verification. Verify the signature first. After the verification is successful, use the correct signature hash value as a parameter to request the crc32 value of classes.dex in the background, and then compare it with the current value of crc32. In order to increase the workload of cracking, it is recommended to carry out multiple checks and use deformation judgment statements at each place and combine them with other judgment conditions.

Decompile and secondary packaging

Before I talk about how to bypass Java code signature validation, I’ll briefly show you how to use apkTool to decompile apK and double-pack it. First, you need to download the tool. Here, you use apktool.jar and apktool.bat:

Use apktool to obtain the APK resource file and smali file

Put the apktool.jar file and apktool.bat file together, and copy the apk file to the following directory:

Next, in the corresponding file directory, run the following command: Apktool d test.apk, apktool d test.apk, apktool d test.apk, apktool d test.

Use apktool to repackage apK files

After the decompile operation is complete, we can find the. Smali files in the smali folder, which are written in the Smali language, the register language of Davlik. Smali has its own syntax and can be modified, which can be packaged twice as APK. Apk can not be installed directly after secondary packaging, but must be signed before installation.

Now we need to repackage the compiled test file into an APK file. As I said before, smali has its own syntax and can be modified, so we can change the smali file as we want, but I’ll just show you how to package it.

First we open the CMD command and type the command: Apktool b test, after the command is executed, the dist folder will be generated in the test folder. In this folder, the APK file generated after the second packaging is saved. However, this APK file cannot be installed and run because it has not been signed.

The auto-sign command is used to sign the apK file after secondary packaging

First we need to download the auto-sign tool and put it in the apktool directory (recommended) :

Then copy the apk file we want to sign into the auto-sign directory and change the name to update.zip:

You can see why you want to change to update.zip by looking at the sign.bat file:

Finally, double-click the sign.bat file and change the update_signed.zip file generated in the same directory to the test.apk file. This file is the second package of the signed file we need. In this example, if the user app does not do the signature verification, Then the repackaged APK functions exactly like the original APK:

From the above, we can see that if we do not perform signature verification, the rogue can simply use the apkTool and auto-sign tools to crack our app and repackage it into a new APK.

Bypass Java code signature verification

Here I will only use a simple demo as an example, first we will compile apkto generate smali files we need through apktool, these files will be based on the package hierarchy to generate the corresponding directory, all classes in the program will be generated in the corresponding directory to generate separate smali files:

Then we use dex2JAR and JD-GUI to get the decomcompiled Java code (which is often confused). By looking at the Java code, we can quickly search out the required Android API methods, and then locate the approximate location of the corresponding Smali file by the location of the API methods:

Signatures in the Java layer usually require signatures signatures (signatures) which can be found in the JD-GUI and find a method based on signatures (signatures). If the app has a special Toast prompt or Log message before the verification fails, it is even easier:

We then open the signatures code which we found and which normally enable the app to perform multiple checks:

We can use the androidmanifest.xml file to find the Application and the main Activity. We can use the androidmanifest.xml file to find the Application and the main Activity. We can use the androidmanifest.xml file to find the Application and the main Activity. After a change, by running the app, and then according to the app exit or stuck location to locate the next verification code, until successful operation.

By the above statement we can know, this is just a simple equals (), then we open the corresponding smali files, search “F010AF8CFE611E1CC74845F80266”, position the signature verifier decompiled code:

invoke-static {v0}, Ljava/lang/String;->valueOf(I)Ljava/lang/String;

    move-result-object v0

    invoke-static {v0}, Lcom/lcmhy/c/a/d;->a(Ljava/lang/String;)Ljava/lang/String;

    move-result-object v0

    const-string v1, "F010AF8CFE611E1CC74845F80266"

    invoke-virtual {v0, v1}, Ljava/lang/String;->equals(Ljava/lang/Object;)Z

    move-result v0

    if-nez v0, :cond_1
Copy the code

If -nezv0, :cond_1; if-eqz v0, :cond_1; if-eqz v0, :cond_1;

Hook bypass system signature

Although the above method is orthodox, but in the actual operation process, if the object code is large, and the verification logic is scattered around, it is difficult to modify all the bypass. Therefore, at present, we try to start from the most fundamental interface and achieve the purpose of bypassing signature verification by hook system interface. From the above, we know that the API for obtaining the system signature is as follows:

    public static int getSignature(Context context) { PackageManager pm = context.getPackageManager(); .// Obtain the signature informationpi = pm.getPackageInfo(context.getPackageName(), PackageManager.GET_SIGNATURES); . }Copy the code

As you can see, the core method to get the signature: getPackageInfo, let’s look at the getPackageManager source:

As you can see, the static method of ActivityThread is then called. Let’s go to the ActivityThread source code and take a look:

There’s a static variable, sPackageManager, and it’s an interface. What do you think of here? The first idea, of course, is dynamic proxies. For those of you who don’t know, check out my previous post: Android practice series (1), write a straightforward dynamic proxy explanation, the idea is to replace the sPackageManager object with our proxy object by reflection, and in the proxy object for the getPackageInfo method can be redefined.

The code is also not difficult, directly through reflection sPackageManager, and injected into our proxy object, hook code is as follows:

    public void hookGetPackageInfo(Application app) throws Exception {
        Class clzActivityThread = Class.forName("android.app.ActivityThread");
        Method methodGetPackageManager = clzActivityThread.getDeclaredMethod("getPackageManager");
        methodGetPackageManager.setAccessible(true);
        Object sPackageManager = methodGetPackageManager.invoke(null);
        // Dynamic proxy
        Class clzIPackageManager = Class.forName("android.content.pm.IPackageManager");
        Object proxy = Proxy.newProxyInstance(clzActivityThread.getClassLoader()
                , new Class[]{clzIPackageManager}
                , new PackageManagerProxy(app, sPackageManager));
        // Replace the original sPackageManager
        Field filedIPackageManager = clzActivityThread.getDeclaredField("sPackageManager");
        filedIPackageManager.setAccessible(true);
        filedIPackageManager.set(null, proxy);
    }
Copy the code

This is our proxy object code, if you are not clear, you can read the original article:

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if (method.getName().equals("getPackageInfo")) {
            String packageName = "";
            if (null! = args && args.length >0 && args[0] instanceof String) {
                packageName = (String) args[0];
            }
            final PackageInfo packageInfo;
            if (mApplication.getPackageName().equals(packageName)
                    && null! = (packageInfo = (PackageInfo) method.invoke(mRealPackageManager, args))) {final byte[] b = null; // The original APK signature
                Signature signature = new Signature(b);
                if (null == packageInfo.signatures) {
                    packageInfo.signatures = new Signature[1];
                }
                packageInfo.signatures[0] = signature;
                returnpackageInfo; }}return method.invoke(mRealPackageManager, args);
    }
Copy the code

Ok, this article ends, about the signature verification introduction. If this article is useful to you, give it a thumbs up, everyone’s affirmation is also the motivation of dumb I to keep writing.