Repository navigation
Commit 5f1e8cf
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
0 commit comments