/* * Copyright (C) 2022 Paranoid Android * * 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.util; import android.app.Application; import android.content.Context; import android.content.res.Resources; import android.os.Build; import android.text.TextUtils; import android.util.Log; import com.android.internal.R; import java.lang.reflect.Field; import java.util.Arrays; public class PropImitationHooks { private static final String TAG = "PropImitationHooks"; private static final boolean DEBUG = false; private static final String[] sCertifiedProps = Resources.getSystem().getStringArray(R.array.config_certifiedBuildProperties); private static final String sStockFp = Resources.getSystem().getString(R.string.config_stockFingerprint); private static final Boolean sEnablePropImitation = Resources.getSystem().getBoolean(R.bool.config_enablePropImitation); private static final String PACKAGE_ARCORE = "com.google.ar.core"; private static final String PACKAGE_FINSKY = "com.android.vending"; private static final String PACKAGE_GMS = "com.google.android.gms"; private static final String PROCESS_GMS_UNSTABLE = PACKAGE_GMS + ".unstable"; private static volatile boolean sIsGms = false; private static volatile boolean sIsFinsky = false; public static void setProps(Context context) { if (!sEnablePropImitation) { dlog("Prop imitation is disabled by config"); return; } final String packageName = context.getPackageName(); final String processName = Application.getProcessName(); if (TextUtils.isEmpty(packageName) || processName == null) { return; } sIsGms = packageName.equals(PACKAGE_GMS) && processName.equals(PROCESS_GMS_UNSTABLE); sIsFinsky = packageName.equals(PACKAGE_FINSKY); /* Set Certified Properties for GMSCore * Set Stock Fingerprint for ARCore */ if (sCertifiedProps.length == 4 && sIsGms) { dlog("Spoofing build for GMS"); setPropValue("DEVICE", sCertifiedProps[0]); setPropValue("PRODUCT", sCertifiedProps[1]); setPropValue("MODEL", sCertifiedProps[2]); setPropValue("FINGERPRINT", sCertifiedProps[3]); } else if (!sStockFp.isEmpty() && packageName.equals(PACKAGE_ARCORE)) { dlog("Setting stock fingerprint for: " + packageName); setPropValue("FINGERPRINT", sStockFp); } } private static void setPropValue(String key, Object value){ try { dlog("Setting prop " + key + " to " + value.toString()); 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 set prop " + key, e); } } private static boolean isCallerSafetyNet() { return sIsGms && Arrays.stream(Thread.currentThread().getStackTrace()) .anyMatch(elem -> elem.getClassName().contains("DroidGuard")); } public static void onEngineGetCertificateChain() { // Check stack for SafetyNet or Play Integrity if (isCallerSafetyNet() || sIsFinsky) { dlog("Blocked key attestation sIsGms=" + sIsGms + " sIsFinsky=" + sIsFinsky); throw new UnsupportedOperationException(); } } public static void dlog(String msg) { if (DEBUG) Log.d(TAG, msg); } }