Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added Vending IntegrityService #2599

Merged
merged 6 commits into from
Dec 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ buildscript {
ext.slf4jVersion = '1.7.36'
ext.volleyVersion = '1.2.1'
ext.wireVersion = '4.8.0'
ext.tinkVersion = '1.13.0'

ext.androidBuildGradleVersion = '8.2.2'

Expand Down
5 changes: 5 additions & 0 deletions play-services-droidguard/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

apply plugin: 'com.android.library'
apply plugin: 'maven-publish'
apply plugin: 'kotlin-android'
apply plugin: 'signing'

android {
Expand All @@ -31,6 +32,10 @@ android {
sourceCompatibility = 1.8
targetCompatibility = 1.8
}

kotlinOptions {
jvmTarget = 1.8
}
}

apply from: '../gradle/publish-android.gradle'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@

import org.microg.gms.droidguard.core.DroidGuardServiceBroker;
import org.microg.gms.droidguard.GuardCallback;
import org.microg.gms.droidguard.core.HandleProxyFactory;
import org.microg.gms.droidguard.core.NetworkHandleProxyFactory;
import org.microg.gms.droidguard.PingData;
import org.microg.gms.droidguard.Request;

Expand All @@ -30,7 +30,7 @@
public class DroidGuardChimeraService extends TracingIntentService {
public static final Object a = new Object();
// factory
public HandleProxyFactory b;
public NetworkHandleProxyFactory b;
// widevine
public Object c;
// executor
Expand All @@ -51,7 +51,7 @@ public DroidGuardChimeraService() {
setIntentRedelivery(true);
}

public DroidGuardChimeraService(HandleProxyFactory factory, Object ping, Object database) {
public DroidGuardChimeraService(NetworkHandleProxyFactory factory, Object ping, Object database) {
super("DG");
setIntentRedelivery(true);
this.b = factory;
Expand Down Expand Up @@ -120,7 +120,7 @@ public final IBinder onBind(Intent intent) {
@Override
public void onCreate() {
this.e = new Object();
this.b = new HandleProxyFactory(this);
this.b = new NetworkHandleProxyFactory(this);
this.g = new Object();
this.h = new Handler();
this.c = new Object();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,12 @@ import android.util.Log
import com.google.android.gms.droidguard.internal.DroidGuardInitReply
import com.google.android.gms.droidguard.internal.DroidGuardResultsRequest
import com.google.android.gms.droidguard.internal.IDroidGuardHandle
import org.microg.gms.droidguard.BytesException
import org.microg.gms.droidguard.GuardCallback
import org.microg.gms.droidguard.HandleProxy
import java.io.FileNotFoundException

class DroidGuardHandleImpl(private val context: Context, private val packageName: String, private val factory: HandleProxyFactory, private val callback: GuardCallback) : IDroidGuardHandle.Stub() {
class DroidGuardHandleImpl(private val context: Context, private val packageName: String, private val factory: NetworkHandleProxyFactory, private val callback: GuardCallback) : IDroidGuardHandle.Stub() {
private val condition = ConditionVariable()

private var flow: String? = null
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ object DroidGuardPreferences {
fun isAvailable(context: Context): Boolean = isEnabled(context) && (!isForcedLocalDisabled(context) || getMode(context) != Mode.Embedded)

@JvmStatic
fun isLocalAvailable(context: Context): Boolean = isEnabled(context) && !isForcedLocalDisabled(context)
fun isLocalAvailable(context: Context): Boolean = isEnabled(context) && !isForcedLocalDisabled(context) && getMode(context) == Mode.Embedded

@JvmStatic
fun setEnabled(context: Context, enabled: Boolean) = setSettings(context) { put(ENABLED, enabled) }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ private class NetworkDroidGuardResultCreator(private val context: Context) : Dro
get() = DroidGuardPreferences.getNetworkServerUrl(context) ?: throw IllegalStateException("Network URL required")

override suspend fun getResults(flow: String, data: Map<String, String>): String = suspendCoroutine { continuation ->
queue.add(PostParamsStringRequest("$url?flow=$flow", data, {
queue.add(PostParamsStringRequest("$url?flow=$flow&source=${context.packageName}", data, {
continuation.resume(it)
}, {
continuation.resumeWithException(it.cause ?: it)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,30 +6,23 @@
package org.microg.gms.droidguard.core

import android.content.Context
import androidx.annotation.GuardedBy
import com.android.volley.NetworkResponse
import com.android.volley.VolleyError
import com.android.volley.toolbox.RequestFuture
import com.android.volley.toolbox.Volley
import com.google.android.gms.droidguard.internal.DroidGuardResultsRequest
import dalvik.system.DexClassLoader
import okio.ByteString.Companion.decodeHex
import okio.ByteString.Companion.of
import org.microg.gms.droidguard.*
import org.microg.gms.profile.Build
import org.microg.gms.profile.ProfileManager
import org.microg.gms.utils.singleInstanceOf
import java.io.File
import java.io.IOException
import java.security.MessageDigest
import java.security.cert.Certificate
import java.util.*
import com.android.volley.Request as VolleyRequest
import com.android.volley.Response as VolleyResponse

class HandleProxyFactory(private val context: Context) {
@GuardedBy("CLASS_LOCK")
private val classMap = hashMapOf<String, Class<*>>()
class NetworkHandleProxyFactory(private val context: Context) : HandleProxyFactory(context) {
private val dgDb: DgDatabaseHelper = DgDatabaseHelper(context)
private val version = VersionUtil(context)
private val queue = singleInstanceOf { Volley.newRequestQueue(context.applicationContext) }
Expand Down Expand Up @@ -136,7 +129,7 @@ class HandleProxyFactory(private val context: Context) {

override fun getHeaders(): Map<String, String> {
return mapOf(
"User-Agent" to "DroidGuard/${version.versionCode}"
"User-Agent" to "DroidGuard/${version.versionCode}"
)
}
})
Expand Down Expand Up @@ -178,68 +171,7 @@ class HandleProxyFactory(private val context: Context) {
return HandleProxy(clazz, context, flow, byteCode, callback, vmKey, extra, request?.bundle)
}

fun getTheApkFile(vmKey: String) = File(getCacheDir(vmKey), "the.apk")
private fun getCacheDir() = context.getDir(CACHE_FOLDER_NAME, Context.MODE_PRIVATE)
private fun getCacheDir(vmKey: String) = File(getCacheDir(), vmKey)
private fun getOptDir(vmKey: String) = File(getCacheDir(vmKey), "opt")
private fun isValidCache(vmKey: String) = getTheApkFile(vmKey).isFile && getOptDir(vmKey).isDirectory

private fun updateCacheTimestamp(vmKey: String) {
try {
val timestampFile = File(getCacheDir(vmKey), "t")
if (!timestampFile.exists() && !timestampFile.createNewFile()) {
throw Exception("Failed to touch last-used file for $vmKey.")
}
if (!timestampFile.setLastModified(System.currentTimeMillis())) {
throw Exception("Failed to update last-used timestamp for $vmKey.")
}
} catch (e: IOException) {
throw Exception("Failed to touch last-used file for $vmKey.")
}
}

private fun verifyApkSignature(apk: File): Boolean {
return true
val certificates: Array<Certificate> = TODO()
if (certificates.size != 1) return false
return Arrays.equals(MessageDigest.getInstance("SHA-256").digest(certificates[0].encoded), PROD_CERT_HASH)
}

private fun loadClass(vmKey: String, bytes: ByteArray): Class<*> {
synchronized(CLASS_LOCK) {
val cachedClass = classMap[vmKey]
if (cachedClass != null) {
updateCacheTimestamp(vmKey)
return cachedClass
}
val weakClass = weakClassMap[vmKey]
if (weakClass != null) {
classMap[vmKey] = weakClass
updateCacheTimestamp(vmKey)
return weakClass
}
if (!isValidCache(vmKey)) {
throw BytesException(bytes, "VM key $vmKey not found in cache")
}
if (!verifyApkSignature(getTheApkFile(vmKey))) {
getCacheDir(vmKey).deleteRecursively()
throw ClassNotFoundException("APK signature verification failed")
}
val loader = DexClassLoader(getTheApkFile(vmKey).absolutePath, getOptDir(vmKey).absolutePath, null, context.classLoader)
val clazz = loader.loadClass(CLASS_NAME)
classMap[vmKey] = clazz
weakClassMap[vmKey] = clazz
return clazz
}
}

companion object {
const val CLASS_NAME = "com.google.ccc.abuse.droidguard.DroidGuard"
const val SERVER_URL = "https://www.googleapis.com/androidantiabuse/v1/x/create?alt=PROTO&key=AIzaSyBofcZsgLSS7BOnBjZPEkk4rYwzOIz-lTI"
const val CACHE_FOLDER_NAME = "cache_dg"
val CLASS_LOCK = Object()
@GuardedBy("CLASS_LOCK")
val weakClassMap = WeakHashMap<String, Class<*>>()
val PROD_CERT_HASH = byteArrayOf(61, 122, 18, 35, 1, -102, -93, -99, -98, -96, -29, 67, 106, -73, -64, -119, 107, -5, 79, -74, 121, -12, -34, 95, -25, -62, 63, 50, 108, -113, -103, 74)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.Looper;
import android.os.ParcelFileDescriptor;
import android.util.Log;

import com.google.android.gms.droidguard.DroidGuardHandle;
Expand All @@ -29,6 +30,7 @@ public class DroidGuardApiClient extends GmsClient<IDroidGuardService> {
private final Context context;
private int openHandles = 0;
private Handler handler;
private HandleProxyFactory factory;

public DroidGuardApiClient(Context context, ConnectionCallbacks callbacks, OnConnectionFailedListener connectionFailedListener) {
super(context, callbacks, connectionFailedListener, GmsService.DROIDGUARD.ACTION);
Expand All @@ -38,6 +40,8 @@ public DroidGuardApiClient(Context context, ConnectionCallbacks callbacks, OnCon
HandlerThread thread = new HandlerThread("DG");
thread.start();
handler = new Handler(thread.getLooper());

factory = new HandleProxyFactory(context);
}

public void setPackageName(String packageName) {
Expand All @@ -60,6 +64,7 @@ public DroidGuardHandle openHandle(String flow, DroidGuardResultsRequest request
for (String key : bundle.keySet()) {
Log.d(TAG, "reply.object[" + key + "] = " + bundle.get(key));
}
handleDroidGuardData(reply.pfd, (Bundle) reply.object);
}
}
}
Expand All @@ -70,6 +75,16 @@ public DroidGuardHandle openHandle(String flow, DroidGuardResultsRequest request
}
}

private void handleDroidGuardData(ParcelFileDescriptor pfd, Bundle bundle) {
String vmKey = bundle.getString("h");
if (vmKey == null) {
throw new RuntimeException("Missing vmKey");
}
HandleProxy proxy = factory.createHandle(vmKey, pfd, bundle);
proxy.init();
proxy.close();
}

public void markHandleClosed() {
if (openHandles == 0) {
Log.w(TAG, "Can't mark handle closed if none is open");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* SPDX-License-Identifier: Apache-2.0
*/

package org.microg.gms.droidguard.core
package org.microg.gms.droidguard

class BytesException : Exception {
val bytes: ByteArray
Expand Down
Original file line number Diff line number Diff line change
@@ -1,30 +1,30 @@
/*
* SPDX-FileCopyrightText: 2021 microG Project Team
* SPDX-FileCopyrightText: 2022 microG Project Team
* SPDX-License-Identifier: Apache-2.0
*/

package org.microg.gms.droidguard.core
package org.microg.gms.droidguard

import android.content.Context
import android.os.Bundle
import android.os.Parcelable

class HandleProxy(val handle: Any, val vmKey: String, val extra: ByteArray = ByteArray(0)) {
constructor(clazz: Class<*>, context: Context, vmKey: String, data: Parcelable) : this(
kotlin.runCatching {
clazz.getDeclaredConstructor(Context::class.java, Parcelable::class.java).newInstance(context, data)
}.getOrElse {
throw BytesException(ByteArray(0), it)
},
vmKey
kotlin.runCatching {
clazz.getDeclaredConstructor(Context::class.java, Parcelable::class.java).newInstance(context, data)
}.getOrElse {
throw BytesException(ByteArray(0), it)
},
vmKey
)

constructor(clazz: Class<*>, context: Context, flow: String?, byteCode: ByteArray, callback: Any, vmKey: String, extra: ByteArray, bundle: Bundle?) : this(
kotlin.runCatching {
clazz.getDeclaredConstructor(Context::class.java, String::class.java, ByteArray::class.java, Object::class.java, Bundle::class.java).newInstance(context, flow, byteCode, callback, bundle)
}.getOrElse {
throw BytesException(extra, it)
}, vmKey, extra)
kotlin.runCatching {
clazz.getDeclaredConstructor(Context::class.java, String::class.java, ByteArray::class.java, Object::class.java, Bundle::class.java).newInstance(context, flow, byteCode, callback, bundle)
}.getOrElse {
throw BytesException(extra, it)
}, vmKey, extra)

fun run(data: Map<Any, Any>): ByteArray {
try {
Expand Down
Loading
Loading