Skip to content

Commit 5f1e8cf

Browse files
committed
Spoof a build identity that holds together
UpdateBuildFields resolved a field on android.os.Build, fell back to android.os.Build$VERSION when that failed, and then handed buildClass to SetStaticObjectField either way. ART used to decode a jfieldID without consulting the class argument, so the mismatch went unseen for years. It no longer does: it checks the field against the class it was given and aborts the process when they disagree. Abort message: 'Direct field pointer 0x0080000070... not found in hierarchy of class java.lang.Class<android.os.Build>' #06 _JNIEnv::SetStaticObjectField jni.h:809 #07 PlayIntegrityFix::UpdateBuildFields main.cpp #08 PlayIntegrityFix::postAppSpecialize main.cpp json.items() walks the keys in order, so BRAND, DEVICE, FINGERPRINT and ID were written, and INCREMENTAL, the first field declared on Build$VERSION, took com.google.android.gms.unstable down with it. RELEASE and SECURITY_PATCH would have done the same. GMS then crash-looped, and no integrity verdict could be obtained at all while the module was enabled. Remember the class a field resolved on and write through that one, do not write a field that already holds the value we mean to give it, and stand down entirely when none of them needed changing: a device running the very build we picked is asked for nothing, gets no property hook, and offers a detection nothing to compare. The comparison reads the field rather than ro.build.fingerprint, which PROP_VALUE_MAX truncates at 92 bytes. Every write is announced before it is attempted, with the field, the class it resolved on, the id handed to JNI and the value already there, so an abort here names its own cause in the log instead of leaving a tombstone to be guessed at. The properties themselves were matched on suffixes, which cannot express what a stock device actually reports. A Pixel 6 on the Android 17 canary shows both ways that breaks: ro.build.fingerprint google/oriole_beta/oriole:CANARY/ZP11.260717.006/... ro.system.build.fingerprint google/generic_system_google/generic:CANARY/ZP11... The system partition holds a generic system image and names it as such, in its fingerprint and in ro.product.system.{name,device,model}, while odm, product, system_ext, vendor and vendor_dlkm all name the device. Rewriting every *.build.fingerprint alike described a Pixel that has never shipped. ro.product.first_api_level 31 ro.vendor.api_level 31 ro.board.api_level 202704 ro.llndk.api_level 202704 All four end in api_level, only the first is the launch SDK that Build.VERSION.DEVICE_INITIAL_SDK_INT reads, and the last two are not even counted on the same scale. So build the exact set of properties once, from the configuration, and look each name up in it. Asked for the very build that Pixel 6 runs, the map reproduces all 86 of the properties it reports, byte for byte. The suffix rules contradicted five of them and left another 68 telling the truth: ro.build.flavor, the incremental on seven partitions, and every ro.product.*.{brand,device,manufacturer,model,name} down to name_for_attestation, which drops the _beta a beta product carries. The fourth field of a fingerprint is release_or_codename, not release: it reads CANARY where ro.build.version.release reads 17, so a codename no longer lands in Build.VERSION.RELEASE. RELEASE_OR_CODENAME is not a value of its own either, it is CODENAME, or RELEASE once CODENAME reads REL, so codename, all_codenames and release_or_preview_display are decided together with it. The release number itself is not in the fingerprint at all and comes from action.sh, which looked it up to find the build. A value the user spelled out wins over the one read off the fingerprint, and a fingerprint that does not split into eight fields is dropped rather than written into Build.FINGERPRINT whole. A json that is not an object at all, which a malformed file parses to, is normalised away before anyone tries to iterate it. DEVICE_INITIAL_SDK_INT becomes an override rather than a default of 21. It is the legacy claim of having shipped before hardware attestation existed, and no fingerprint says when the device it names first shipped; left unset, first_api_level answers what the device already answers, which for the Pixel 6 the fingerprint claims is 31, not 21. A hardcoded fingerprint is stale the day it is packaged and is shared by every user of the release, so the module now ships none: pif.json is a placeholder documenting the keys, and customize.sh keeps /data/adb/pif.json where it is. Moving it aside on an update would silently turn spoofing off with nothing to replace it.
1 parent 124d61f commit 5f1e8cf

3 files changed

Lines changed: 330 additions & 48 deletions

File tree

0 commit comments

Comments
 (0)