BLCK

Decompiling the Android Developer Verifier app

F-Droid blog has published an article titled What We Talk About When We Talk About Malware. The article is rather critical and claims that a pre-loaded Android app called Android Developer Verifier behaves like a trojan horse. The article contains little evidence for its claims. But I would like to give it credit for making me aware of the app. This post is my analysis of the Android developer verification timeline, policy, and of the decompiled source code.

The Android Developer Verifier app’s version 1.0 was released in July 2012. Since October 2025, it has started receiving more updates. This coincides with the post to an Android Developers Blog titled A new layer of security for certified Android devices. The blog states: “Starting next year, Android will require all apps to be registered by verified developers in order to be installed by users on certified Android devices.”

The timeline of the rollout is reportedly:

AnnouncementAug 2025Google announces developer verification
Early accessOct 2025Developer verification early access begins. Invitations will be sent out gradually.
Verification opensMar 2026Verification opens for all developers.
Enforcement beginsSep 2026Requirements go into effect in Brazil, Indonesia, Singapore, and Thailand. At this point, any app installed on a certified Android device in these regions must be registered by a verified developer.
Global rollout2027 and beyondRequirements roll out globally.

Android 16, released in June 2025, introduces developer verification requirements. The release compatibility documentation contains useful info. I encourage you to read the original text. This is my summary of the relevant points:

  • Android 16 devices that configure a developer verifier in config.xml must invoke DeveloperVerifierService for every package installation and update.
  • The verifier service must prevent the installation of a package if the developer identity verification fails. Failure means the app is unverified.
  • The verifier policy doesn’t apply to installation via ADB. Or if developer verification policy is set to FAIL_WARN or FAIL_OPEN, verification is incomplete, and user clicks install anyway.

DeveloperVerifierService was introduced in Android 16, coinciding with the update of the Android Developer Verifier app.

Code analysis

APK: com.android.google.verifier version 1.0.866414232 from March 30

I decompiled the APK using Dex to Java decompiler that produces Java source code where most of the business logic is obfuscated. For a better look into the business logic, Apktool decompiles the apk to smali code (type of assembly language) which is not as easily readable.

 java -jar apktool_3.0.2.jar d com.google.android.verifier.apk

I tasked DeepSeek with analysing the decompiled code. Then, I verified the code execution path manually. I also referenced my findings to this comprehensive analysis someone already did.

Findings

The com.google.android.verifier app is invoked before a package installation. It’s a pre-installed, system-privileged app that acts as the platform’s developer verification service. It hosts the DeveloperVerifierService.

Once invoked, the verifier reads the policy. [1] Then, it extracts SHA-256 hashes of APK signing certificates. [2] It also collects installer identity, device serial number, device owner info and other basic information. It reports the result back to the PackageInstaller. [3]

The verification is local. The app pulls flags that originate from Google’s server. [4] It validates the signing certificate fingerprints and reports them to PackageInstaller, a separate service, and it decides what to do based on the policy value:

  • DEVELOPER_VERIFICATION_POLICY_NONE = 0: Do not block install.
  • DEVELOPER_VERIFICATION_POLICY_BLOCK_FAIL_OPEN = 1: Block install when verification fails. If verifier can’t be reached, let it through.
  • DEVELOPER_VERIFICATION_POLICY_BLOCK_FAIL_WARN = 2: Warn user if verification fails, let them install anyway.
  • DEVELOPER_VERIFICATION_POLICY_BLOCK_FAIL_CLOSED = 3: Block install when verification fails.

The app has two verification paths. The original enforcement approach works by sending the verificator result via gRPC to the Play Store, which applies its own policy. The new enforcement works by pulling policy flags and trusted fingerprints from Google’s servers to a local cached store. The enforcement is delegated to PackageInstallerSession.

Conclusion

The app verifier is a system app and can’t be uninstalled. The PackageInstaller is invoked during install and if app verifier is not present, it may or may not block the install. In any case, existing apps are not removed. However, updates to installed apps are checked. The new verification path is enabled by a flag pulled from Google server.

No network calls are made in the verification logic. App verifier compares the app’s signing certificate and fingerprint to trusted fingeprints pulled from Google’s server. For more context and ADB shell commands one can use to debug this functionality, I recommend reading the Android Developer Verification Discourse.


Reference

Android Developer Verifier permissions
DEVELOPER_VERIFICATION_AGENT
INTERACT_ACROSS_USERS
GET_ROLE_HOLDERS
MANAGE_USERS
READ_PRIVILEGED_PHONE_STATE
ACCESS_NETWORK_STATE
INTERNET + ACCESS_NETWORK_STATE
QUERY_ALL_PACKAGES
POST_NOTIFICATIONS

[4] Flags (Phenotypes)

NumberTypeDefaultIndexFileDescription
45681539long01bgg.javaPlatform policy - 0=NONE, 1=OPEN, 2=WARN, 3=CLOSED
45681540long00bea.javaBackport policy - same values, gRPC
45711407boolfalse3wf.javaFingerprint matching on/off
45711408bytes""2ave.javaDeveloper registry - the allowlist
45749715boolfalse4wf.javaForced backport manager - short-circuit verification

[1], [3]

// helpers/verification/impl/common/platform/PlatformVerificationService.java
public final class PlatformVerificationService extends bgg {  
    public static final bny a = bny.h("com/google/android/verifier/helpers/verification/impl/common/platform/PlatformVerificationService");  
    public bgz b;  
    public bsx c;  
    public xv f;  
    public final Object d = new Object();  
    private final Map g = new LinkedHashMap();  
    public final Map e = new LinkedHashMap();  
  
    private final void c(final DeveloperVerificationSession developerVerificationSession, boolean z) {  
        Object objB;  
        Object objB2;  
        bgy bgyVar;  
        if (this.c == null) {  
            czl.a("timeSource");  
        }  
        Instant instantNow = Instant.now();  
        instantNow.getClass();  
        synchronized (this.d) {  
            bgz bgzVar = this.b;  
            if (bgzVar == null) {  
                czl.a("policyManager");  
                bgzVar = null;  
            }  
            int policy = developerVerificationSession.getPolicy();  
            long j = bgzVar.d;  
            int i = (int) j;  
            if (!bgz.b.contains(Integer.valueOf(i))) {  
                ((bnw) bgz.a.c().h("com/google/android/verifier/helpers/verification/impl/common/platform/PlatformVerificationPolicyManager$Companion", "parsePhenotypePolicy", 126, "PlatformVerificationPolicyManager.kt")).p("Invalid verification policy: %d.", j);  
                i = 0;  
            }  
            if (i == policy) {  
                bgyVar = new bgy(policy, i, i, i);  
            } else {  
                ((bnw) bgz.a.d().h("com/google/android/verifier/helpers/verification/impl/common/platform/PlatformVerificationPolicyManager", "updatePolicyIfNecessary", 49, "PlatformVerificationPolicyManager.kt")).w(policy, i);  
                PackageInstaller packageInstaller = bgzVar.c.getPackageManager().getPackageInstaller();  
                packageInstaller.getClass();  
                try {  
                    objB = Boolean.valueOf(packageInstaller.setDeveloperVerificationPolicy(i));  
                } catch (Throwable th) {  
                    objB = cvl.b(th);  
                }  
                boolean zBooleanValue = ((Boolean) (true != (objB instanceof cvu) ? objB : false)).booleanValue();  
                if (!zBooleanValue) {  
                    ((bnw) bgz.a.c().g(cvv.a(objB)).h("com/google/android/verifier/helpers/verification/impl/common/platform/PlatformVerificationPolicyManager", "updatePolicyIfNecessary", 56, "PlatformVerificationPolicyManager.kt")).n("Failed to update global policy.");  
                }  
                try {  
                    objB2 = Boolean.valueOf(developerVerificationSession.setPolicy(i));  
                } catch (Throwable th2) {  
                    objB2 = cvl.b(th2);  
                }  
                boolean zBooleanValue2 = ((Boolean) (true != (objB2 instanceof cvu) ? objB2 : false)).booleanValue();  
                if (!zBooleanValue2) {  
                    ((bnw) bgz.a.c().g(cvv.a(objB2)).h("com/google/android/verifier/helpers/verification/impl/common/platform/PlatformVerificationPolicyManager", "updatePolicyIfNecessary", 63, "PlatformVerificationPolicyManager.kt")).n("Failed to update session policy.");  
                }  
                bgyVar = new bgy(policy, i, true != zBooleanValue2 ? policy : i, true != zBooleanValue ? policy : i);  
            }  
            String packageName = developerVerificationSession.getPackageName();  
            if (packageName == null || czl.m(packageName)) {  
                ((bnw) a.c().h("com/google/android/verifier/helpers/verification/impl/common/platform/PlatformVerificationService", "onVerificationRequiredOrRetry", 116, "PlatformVerificationService.kt")).n("`onVerificationRequiredOrRetry` missing package name.");  
                try {  
                    developerVerificationSession.reportVerificationIncomplete(0);  
                } catch (Throwable th3) {  
                    cvl.b(th3);  
                }  
            } else {  
                Map map = this.e;  
                if (map.containsKey(Integer.valueOf(developerVerificationSession.getId()))) {  
                    ((bnw) a.d().h("com/google/android/verifier/helpers/verification/impl/common/platform/PlatformVerificationService", "onVerificationRequiredOrRetry", 126, "PlatformVerificationService.kt")).n("Multiple `onVerificationRequiredOrRetry` calls.");  
                    bgo bgoVar = (bgo) map.remove(Integer.valueOf(developerVerificationSession.getId()));  
                    if (bgoVar != null) {  
                        bgoVar.b("Multiple `onVerificationRequiredOrRetry` calls");  
                    }  
                }  
                final bgo bgoVarAl = (bgo) this.g.remove(developerVerificationSession.getPackageName());  
                if (bgoVarAl == null) {  
                    xv xvVarB = b();  
                    String packageName2 = developerVerificationSession.getPackageName();  
                    packageName2.getClass();  
                    bgoVarAl = xvVarB.al(packageName2);  
                }  
                cyr cyrVar = new cyr() { // from class: bha  
                    @Override // defpackage.cyr  
                    public final Object a(Object obj) {  
                        bgk bgkVar = (bgk) obj;  
                        bgkVar.getClass();  
                        DeveloperVerificationSession developerVerificationSession2 = developerVerificationSession;  
                        developerVerificationSession2.getPackageName();  
                        developerVerificationSession2.getId();  
                        bgo bgoVar2 = bgoVarAl;  
                        PlatformVerificationService platformVerificationService = this.a;  
                        synchronized (platformVerificationService.d) {  
                            Map map2 = platformVerificationService.e;  
                            bgo bgoVar3 = (bgo) map2.get(Integer.valueOf(developerVerificationSession2.getId()));  
                            if (czl.b(bgoVar3, bgoVar2)) {  
                                map2.remove(Integer.valueOf(developerVerificationSession2.getId()));  
                            } else if (bgoVar3 == null) {  
                                ((bnw) PlatformVerificationService.a.c().h("com/google/android/verifier/helpers/verification/impl/common/platform/PlatformVerificationService", "onVerificationResult", 184, "PlatformVerificationService.kt")).n("`onVerificationResult` for untracked verification");  
                            } else {  
                                ((bnw) PlatformVerificationService.a.c().h("com/google/android/verifier/helpers/verification/impl/common/platform/PlatformVerificationService", "onVerificationResult", 189, "PlatformVerificationService.kt")).n("`onVerificationResult` for unexpected verification");  
                            }  
                            // [3]
                            try {  
                                if (bgkVar instanceof bgi) {  
                                    DeveloperVerificationStatus developerVerificationStatus = ((bgi) bgkVar).a;  
                                    developerVerificationStatus.isVerified();  
                                    developerVerificationStatus.getFailureMessage();  
                                    developerVerificationSession2.reportVerificationComplete(developerVerificationStatus);  
                                } else if (bgkVar instanceof bgj) {  
                                    developerVerificationSession2.reportVerificationIncomplete(((bgj) bgkVar).a);  
                                } else {  
                                    if (!(bgkVar instanceof bgh)) {  
                                        throw new cvs();  
                                    }  
                                    developerVerificationSession2.reportVerificationBypassed(1);  
                                }  
                            } catch (Exception e) {  
                                ((bnw) PlatformVerificationService.a.c().g(e).h("com/google/android/verifier/helpers/verification/impl/common/platform/PlatformVerificationService", "onVerificationResult", 216, "PlatformVerificationService.kt")).n("Unexpect exception while reporting result");  
                            }  
                        }  
                        return cvz.a;  
                    }  
                };  
                bgoVarAl.e.M(developerVerificationSession);  
                bgoVarAl.f.M(Boolean.valueOf(z));  
                bgoVarAl.g.M(instantNow);  
                bgoVarAl.h.M(bgyVar);  
                bgoVarAl.i.M(cyrVar);  
                map.put(Integer.valueOf(developerVerificationSession.getId()), bgoVarAl);  
            }  
        }  
    }

[2]

// defpackage/bgw.java
public static final List c(SigningInfo signingInfo) {  
    Signature[] apkContentsSigners;  
    int length;  
    try {  
        if (signingInfo == null) {  
            throw new IllegalArgumentException("`signingInfo` is null");  
        }  
        if (signingInfo.hasMultipleSigners()) {  
            apkContentsSigners = signingInfo.getApkContentsSigners();  
            if (apkContentsSigners == null) {  
                throw new IllegalArgumentException("`apkContentsSigners` is null");  
            }  
        } else {  
            Signature[] signingCertificateHistory = signingInfo.getSigningCertificateHistory();  
            Signature signature = null;  
            if (signingCertificateHistory != null && (length = signingCertificateHistory.length) != 0) {  
                signature = signingCertificateHistory[length - 1];  
            }  
            if (signature == null) {  
                throw new IllegalArgumentException("`signingCertificateHistory` is null or empty");  
            }  
            apkContentsSigners = new Signature[]{signature};  
        }  
        MessageDigest messageDigest = MessageDigest.getInstance("SHA-256");  
        messageDigest.getClass();  
        ArrayList arrayList = new ArrayList(apkContentsSigners.length);  
        for (Signature signature2 : apkContentsSigners) {  
            byte[] bArrDigest = messageDigest.digest(signature2.toByteArray());  
            bArrDigest.getClass();  
            arrayList.add(new bej(bArrDigest));  
        }  
        return arrayList;  
    } catch (Exception e) {  
        ((bnw) a.d().g(e).h("com/google/android/verifier/helpers/verification/impl/common/platform/PlatformVerificationMapping", "toCertFingerprints", 220, "PlatformVerificationMapping.kt")).n("Unable to parse certs. Defaulting to empty list.");  
        return cwk.a;  
    }  
}