Skip to content

Commit 5a54bfd

Browse files
committed
core: Report fake model name to GMS using reflection
SafetyNet currently enforces hardware attestation for specific devices (mostly Pixels at the moment) by checking the product model name using Build.MODEL. We could alter the product model name globally, but that's ugly and can break apps that check the model name for features or to just report information to the user. Instead of modifying the model name globally, detect when Google Play Services is launching and modify the Build.MODEL field in its post-fork process instead. This is done early during app start before the app's code starts to run. To be more stealthy, we also use reflection to edit the final field directly and restore the final modifier once we're done rather than removing final from the field declaration. This works because even though part of SafetyNet runs in the context of client apps (snet.jar), that's just a thin client — the real attestation logic takes place in Play Services. Instead of blatantly changing the model name to a completely different device or dummy name, we only append an invisible U+200B ZERO-WIDTH SPACE character to it so the user doesn't see a weird device logged into their Google account. The invisible character is enough to defy SafetyNet and make it revert back to basic (software) attestation. This is hidden behind the ro.product.needs_model_edit property to avoid unnecessary hacks on devices that don't need it. Change-Id: I09495a7aa1e59b3bd59f3dbd4c9cdd701b00df5d
1 parent f62feee commit 5a54bfd

File tree

1 file changed

+37
-0
lines changed

1 file changed

+37
-0
lines changed

core/java/com/android/internal/os/Zygote.java

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import android.net.LocalServerSocket;
2626
import android.net.LocalSocket;
2727
import android.net.NetworkUtils;
28+
import android.os.Build;
2829
import android.os.FactoryTest;
2930
import android.os.IVold;
3031
import android.os.Process;
@@ -46,6 +47,7 @@
4647
import java.io.FileDescriptor;
4748
import java.io.IOException;
4849
import java.io.InputStreamReader;
50+
import java.lang.reflect.Field;
4951

5052
/** @hide */
5153
public final class Zygote {
@@ -285,6 +287,9 @@ public final class Zygote {
285287
*/
286288
public static final String USAP_POOL_SECONDARY_SOCKET_NAME = "usap_pool_secondary";
287289

290+
private static final boolean PRODUCT_NEEDS_MODEL_EDIT =
291+
SystemProperties.getBoolean("ro.product.needs_model_edit", false);
292+
288293
private Zygote() {}
289294

290295
private static boolean containsInetGid(int[] gids) {
@@ -786,6 +791,35 @@ private static void boostUsapPriority() {
786791

787792
private static native void nativeBoostUsapPriority();
788793

794+
private static void maybeSetGmsModel(String packageName, String loggingTag) {
795+
if (PRODUCT_NEEDS_MODEL_EDIT &&
796+
packageName != null &&
797+
packageName.startsWith("com.google.android.gms")) {
798+
/*
799+
* This would be much prettier if we just removed "final" from the MODEL field in Build,
800+
* but that's easy to detect should Google ever catch onto this. Inspecting bytecode
801+
* is much harder so this is more future-proof.
802+
*
803+
* While it's an awful hack, it's technically safe because the field was populated at
804+
* runtime (in pre-fork Zygote) and it's not a primitive.
805+
*/
806+
try {
807+
// Unlock
808+
Field field = Build.class.getDeclaredField("MODEL");
809+
field.setAccessible(true);
810+
811+
// Edit
812+
String newModel = Build.MODEL + "\u200b";
813+
field.set(null, newModel);
814+
815+
// Lock
816+
field.setAccessible(false);
817+
} catch (NoSuchFieldException | IllegalAccessException e) {
818+
Log.w(loggingTag, "Failed to set fake model name for GMS", e);
819+
}
820+
}
821+
}
822+
789823
static void setAppProcessName(ZygoteArguments args, String loggingTag) {
790824
if (args.mNiceName != null) {
791825
Process.setArgV0(args.mNiceName);
@@ -794,6 +828,9 @@ static void setAppProcessName(ZygoteArguments args, String loggingTag) {
794828
} else {
795829
Log.w(loggingTag, "Unable to set package name.");
796830
}
831+
832+
// Modify model to defy SafetyNet hardware attestation in GMS
833+
maybeSetGmsModel(args.mPackageName, loggingTag);
797834
}
798835

799836
private static final String USAP_ERROR_PREFIX = "Invalid command to USAP: ";

0 commit comments

Comments
 (0)