Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save mmtrt/ba3da05b4a26dde1b5f99cdfe51d8769 to your computer and use it in GitHub Desktop.
Save mmtrt/ba3da05b4a26dde1b5f99cdfe51d8769 to your computer and use it in GitHub Desktop.
From 4c6ae32cb264e38a61ca57e7177e17bd13d7d1dc Mon Sep 17 00:00:00 2001
From: Alberto Ponces <ponces26@gmail.com>
Date: Thu, 9 Nov 2023 12:33:55 +0100
Subject: [PATCH 1/9] gmscompat: Change attestation and instrumentation to pass
SafetyNet and Play Integrity API.
Original work by @kdrag0n.
Updated by many people like @dereference23, @Stallix, @dyneteve, @neobuddy89 and @jhenrique09.
Adapted by @iceows for his own AOSP A13 GSI.
Adapted by @ponces based on the work of @chiteroman to pass newest Play Integrity API.
---
core/java/android/app/Instrumentation.java | 4 +
.../internal/gmscompat/AttestationHooks.java | 173 ++++++++++++++++++
core/res/res/values/arrays.xml | 15 ++
core/res/res/values/symbols.xml | 3 +
.../keystore2/AndroidKeyStoreSpi.java | 3 +
5 files changed, 198 insertions(+)
create mode 100644 core/java/com/android/internal/gmscompat/AttestationHooks.java
diff --git a/core/java/android/app/Instrumentation.java b/core/java/android/app/Instrumentation.java
index e31486f18dbf..a8136bcf346a 100644
--- a/core/java/android/app/Instrumentation.java
+++ b/core/java/android/app/Instrumentation.java
@@ -57,6 +57,8 @@ import android.view.WindowManagerGlobal;
import com.android.internal.content.ReferrerIntent;
+import com.android.internal.gmscompat.AttestationHooks;
+
import java.io.File;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -1242,6 +1244,7 @@ public class Instrumentation {
Application app = getFactory(context.getPackageName())
.instantiateApplication(cl, className);
app.attach(context);
+ AttestationHooks.initApplicationBeforeOnCreate(context, app);
return app;
}
@@ -1259,6 +1262,7 @@ public class Instrumentation {
ClassNotFoundException {
Application app = (Application)clazz.newInstance();
app.attach(context);
+ AttestationHooks.initApplicationBeforeOnCreate(context, app);
return app;
}
diff --git a/core/java/com/android/internal/gmscompat/AttestationHooks.java b/core/java/com/android/internal/gmscompat/AttestationHooks.java
new file mode 100644
index 000000000000..7dc7d6a237c8
--- /dev/null
+++ b/core/java/com/android/internal/gmscompat/AttestationHooks.java
@@ -0,0 +1,173 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.gmscompat;
+
+import android.app.Application;
+import android.content.Context;
+import android.content.res.Resources;
+import android.os.Build;
+import android.os.Build.VERSION;
+import android.os.FileUtils;
+import android.os.SystemProperties;
+import android.util.JsonReader;
+import android.util.Log;
+
+import com.android.internal.R;
+
+import java.io.DataInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.IOException;
+import java.lang.reflect.Field;
+import java.net.URL;
+import java.net.URLConnection;
+import java.nio.file.Files;
+import java.nio.file.StandardCopyOption;
+import java.util.Arrays;
+
+/** @hide */
+public final class AttestationHooks {
+ private static final String TAG = "GmsCompat/Attestation";
+ private static final boolean DEBUG = true;
+
+ private static final String PACKAGE_GMS = "com.google.android.gms";
+ private static final String PACKAGE_FINSKY = "com.android.vending";
+ private static final String PROCESS_UNSTABLE = "com.google.android.gms.unstable";
+
+ private static final String GMSSPOOF_PATH = SystemProperties.get("ro.system.gms.spoof_file");
+
+ private static final String[] sCertifiedProps =
+ Resources.getSystem().getStringArray(R.array.config_certifiedBuildProperties);
+
+ private static volatile boolean sIsGms = false;
+ private static volatile boolean sIsFinsky = false;
+
+ private AttestationHooks() { }
+
+ private static void setBuildField(String key, String value) {
+ try {
+ Log.i(TAG, "Spoofing Build." + key + " with value \"" + value + "\"");
+ Field field = Build.class.getDeclaredField(key);
+ field.setAccessible(true);
+ field.set(null, value);
+ field.setAccessible(false);
+ } catch (NoSuchFieldException | IllegalAccessException e) {
+ Log.e(TAG, "Failed to spoof Build." + key, e);
+ }
+ }
+
+ private static void setVersionField(String key, Object value) {
+ try {
+ Log.i(TAG, "Spoofing Build.VERSION." + key + " with value \"" + value + "\"");
+ Field field = Build.VERSION.class.getDeclaredField(key);
+ field.setAccessible(true);
+ field.set(null, value);
+ field.setAccessible(false);
+ } catch (NoSuchFieldException | IllegalAccessException e) {
+ Log.e(TAG, "Failed to spoof Build." + key, e);
+ }
+ }
+
+ private static void spoofBuildGms(Context context) {
+ new Thread(new Runnable() {
+ @Override
+ public void run() {
+ File spoofFile = new File("/data/local/tmp", "spoof.json");
+ File cachedFile = new File(context.getCacheDir(), "spoof.json");
+ try {
+ spoofBuildGmsFile(spoofFile);
+ } catch (Exception e) {
+ Log.e(TAG, "Failed to spoof GMS using a local file, trying an OTA file");
+ try {
+ spoofBuildGmsOta(cachedFile);
+ } catch (Exception e2) {
+ Log.e(TAG, "Failed to spoof GMS using an OTA file, trying a cached file");
+ try {
+ spoofBuildGmsFile(cachedFile);
+ } catch (Exception e3) {
+ Log.e(TAG, "Failed to spoof GMS using a cached file, aborting");
+ }
+ }
+ }
+ }
+ }).start();
+ }
+
+ private static void spoofBuildGmsFile(File spoofFile) throws Exception {
+ Log.i(TAG, "Getting spoof file from " + spoofFile.getAbsolutePath());
+ if (spoofFile.exists()) {
+ parseSpoofFile(spoofFile);
+ } else {
+ throw new FileNotFoundException();
+ }
+ }
+
+ private static void spoofBuildGmsOta(File cachedFile) throws Exception {
+ Log.i(TAG, "Getting spoof file from " + GMSSPOOF_PATH);
+ URLConnection con = new URL(GMSSPOOF_PATH).openConnection();
+ con.setUseCaches(false);
+ InputStream stream = con.getInputStream();
+ Log.i(TAG, "Saving cached spoof file at " + cachedFile.getAbsolutePath());
+ Files.copy(stream, cachedFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
+ parseSpoofFile(cachedFile);
+ }
+
+ private static void parseSpoofFile(File spoofFile) throws Exception {
+ InputStream stream = new DataInputStream(new FileInputStream(spoofFile));
+ JsonReader reader = new JsonReader(new InputStreamReader(stream, "UTF-8"));
+ reader.beginObject();
+ while (reader.hasNext()) {
+ String token = reader.nextName();
+ String value = reader.nextString();
+ if (token.equals("SECURITY_PATCH")) {
+ setVersionField(token, value);
+ } else if (token.equals("DEVICE_INITIAL_SDK_INT") || token.equals("FIRST_API_LEVEL")) {
+ setVersionField("DEVICE_INITIAL_SDK_INT", Integer.parseInt(value));
+ } else {
+ setBuildField(token, value);
+ }
+ }
+ reader.endObject();
+ }
+
+ public static void initApplicationBeforeOnCreate(Context context, Application app) {
+ if (PACKAGE_GMS.equals(app.getPackageName()) &&
+ PROCESS_UNSTABLE.equals(Application.getProcessName())) {
+ sIsGms = true;
+ spoofBuildGms(context);
+ }
+
+ if (PACKAGE_FINSKY.equals(app.getPackageName())) {
+ sIsFinsky = true;
+ }
+ }
+
+ private static boolean isCallerSafetyNet() {
+ return sIsGms && Arrays.stream(Thread.currentThread().getStackTrace())
+ .anyMatch(elem -> elem.getClassName().contains("DroidGuard"));
+ }
+
+ public static void onEngineGetCertificateChain() {
+ if (isCallerSafetyNet() || sIsFinsky) {
+ Log.i(TAG, "Blocked key attestation sIsGms=" + sIsGms + " sIsFinsky=" + sIsFinsky);
+ throw new UnsupportedOperationException();
+ }
+ }
+}
diff --git a/core/res/res/values/arrays.xml b/core/res/res/values/arrays.xml
index 97e753e2bdeb..2320a0356e15 100644
--- a/core/res/res/values/arrays.xml
+++ b/core/res/res/values/arrays.xml
@@ -207,4 +207,18 @@
be set by OEMs for devices which use eUICCs. -->
<integer-array name="non_removable_euicc_slots"></integer-array>
+ <!-- Build properties from a GMS certified device -->
+ <string-array name="config_certifiedBuildProperties" translatable="false">
+ <!--
+ <item>Build.BRAND</item>
+ <item>Build.MANUFACTURER</item>
+ <item>Build.ID</item>
+ <item>Build.DEVICE</item>
+ <item>Build.PRODUCT</item>
+ <item>Build.MODEL</item>
+ <item>Build.FINGERPRINT</item>
+ <item>Build.TYPE</item>
+ <item>Build.TAGS</item>
+ -->
+ </string-array>
</resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index bee5799938f2..ed1af0c43181 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -4916,4 +4916,7 @@
<!-- Whether to show weather on the lockscreen by default. -->
<java-symbol type="bool" name="config_lockscreenWeatherEnabledByDefault" />
+
+ <!-- Build properties from a GMS certified device -->
+ <java-symbol type="array" name="config_certifiedBuildProperties" />
</resources>
diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java
index 25f5dec9de40..b34351585c4b 100644
--- a/keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java
+++ b/keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java
@@ -42,6 +42,7 @@ import android.system.keystore2.ResponseCode;
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.gmscompat.AttestationHooks;
import java.io.ByteArrayInputStream;
import java.io.IOException;
@@ -164,6 +165,8 @@ public class AndroidKeyStoreSpi extends KeyStoreSpi {
@Override
public Certificate[] engineGetCertificateChain(String alias) {
+ AttestationHooks.onEngineGetCertificateChain();
+
KeyEntryResponse response = getKeyMetadata(alias);
if (response == null || response.metadata.certificate == null) {
--
2.34.1
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment