commit cadcbc958e496e665eef185e936b646a4ec8c118
parent abfbe86a917a51e2fcbf74ac2dfe500ce6892f30
Author: rhunk <101876869+rhunk@users.noreply.github.com>
Date:   Fri, 15 Dec 2023 23:19:04 +0100

fix(core): device spoofer
- add lspatch install package name hook

Diffstat:
Mcommon/src/main/assets/lang/en_US.json | 52+++++++++++++++++++++++-----------------------------
Mcommon/src/main/kotlin/me/rhunk/snapenhance/common/config/impl/Experimental.kt | 2+-
Mcommon/src/main/kotlin/me/rhunk/snapenhance/common/config/impl/Spoof.kt | 20+++++++-------------
Mcore/src/main/kotlin/me/rhunk/snapenhance/core/data/SnapClassCache.kt | 3---
Mcore/src/main/kotlin/me/rhunk/snapenhance/core/features/impl/experiments/DeviceSpooferHook.kt | 127++++++++++++++++++++++++++++++++++++++++---------------------------------------
Mcore/src/main/kotlin/me/rhunk/snapenhance/core/util/LSPatchUpdater.kt | 4++++
6 files changed, 99 insertions(+), 109 deletions(-)

diff --git a/common/src/main/assets/lang/en_US.json b/common/src/main/assets/lang/en_US.json @@ -567,35 +567,29 @@ "name": "Spoof", "description": "Spoof various information about you", "properties": { - "device": { - "name": "Device", - "description": "Spoof your device information", - "properties": { - "fingerprint": { - "name": "Device Fingerprint", - "description": "Spoofs your device Fingerprint" - }, - "android_id": { - "name": "Android ID", - "description": "Spoofs your Android ID to the specified value" - }, - "installer_package_name": { - "name": "Installer Package name", - "description": "Spoofs the installers Package name" - }, - "debug_flag": { - "name": "Debug Flag", - "description": "Makes Snapchat debuggable" - }, - "mock_location": { - "name": "Mock location", - "description": "Spoofs the Mock Location device state" - }, - "split_classloader": { - "name": "Split Classloader", - "description": "Spoofs splitClassloader\nRequested by org.chromium.base.JNIUtils" - } - } + "play_store_installer_package_name": { + "name": "Play Store Installer Package Name", + "description": "Overrides the installer package name to com.android.vending" + }, + "fingerprint": { + "name": "Device Fingerprint", + "description": "Spoofs your device Fingerprint" + }, + "android_id": { + "name": "Android ID", + "description": "Spoofs your Android ID to the specified value" + }, + "remove_vpn_transport_flag": { + "name": "Remove VPN Transport Flag", + "description": "Prevents Snapchat from detecting VPNs" + }, + "remove_mock_location_flag": { + "name": "Remove Mock Location Flag", + "description": "Prevents Snapchat from detecting Mock location" + }, + "randomize_persistent_device_token": { + "name": "Randomize Persistent Device Token", + "description": "Generates a random device token after each login" } } }, diff --git a/common/src/main/kotlin/me/rhunk/snapenhance/common/config/impl/Experimental.kt b/common/src/main/kotlin/me/rhunk/snapenhance/common/config/impl/Experimental.kt @@ -9,7 +9,7 @@ class Experimental : ConfigContainer() { } val nativeHooks = container("native_hooks", NativeHooks()) { icon = "Memory"; requireRestart() } - val spoof = container("spoof", Spoof()) { icon = "Fingerprint" } + val spoof = container("spoof", Spoof()) { icon = "Fingerprint" ; addNotices(FeatureNotice.BAN_RISK); requireRestart() } val convertMessageLocally = boolean("convert_message_locally") { requireRestart() } val appPasscode = string("app_passcode") val appLockOnResume = boolean("app_lock_on_resume") diff --git a/common/src/main/kotlin/me/rhunk/snapenhance/common/config/impl/Spoof.kt b/common/src/main/kotlin/me/rhunk/snapenhance/common/config/impl/Spoof.kt @@ -1,18 +1,12 @@ package me.rhunk.snapenhance.common.config.impl import me.rhunk.snapenhance.common.config.ConfigContainer -import me.rhunk.snapenhance.common.config.FeatureNotice -class Spoof : ConfigContainer() { - inner class Device : ConfigContainer(hasGlobalState = true) { - val fingerprint = string("fingerprint") - val androidId = string("android_id") - val getInstallerPackageName = string("installer_package_name") - val debugFlag = boolean("debug_flag") - val mockLocationState = boolean("mock_location") - val splitClassLoader = string("split_classloader") - val isLowEndDevice = string("low_end_device") - val getDataDirectory = string("get_data_directory") - } - val device = container("device", Device()) { addNotices(FeatureNotice.BAN_RISK) } +class Spoof : ConfigContainer(hasGlobalState = true) { + val overridePlayStoreInstallerPackageName = boolean("play_store_installer_package_name") + val fingerprint = string("fingerprint") + val androidId = string("android_id") + val removeVpnTransportFlag = boolean("remove_vpn_transport_flag") + val removeMockLocationFlag = boolean("remove_mock_location_flag") + val randomizePersistentDeviceToken = boolean("randomize_persistent_device_token") } \ No newline at end of file diff --git a/core/src/main/kotlin/me/rhunk/snapenhance/core/data/SnapClassCache.kt b/core/src/main/kotlin/me/rhunk/snapenhance/core/data/SnapClassCache.kt @@ -17,9 +17,6 @@ class SnapClassCache ( val feedEntry by lazy { findClass("com.snapchat.client.messaging.FeedEntry") } val conversation by lazy { findClass("com.snapchat.client.messaging.Conversation") } val feedManager by lazy { findClass("com.snapchat.client.messaging.FeedManager\$CppProxy") } - val chromiumJNIUtils by lazy { findClass("org.chromium.base.JNIUtils")} - val chromiumBuildInfo by lazy { findClass("org.chromium.base.BuildInfo")} - val chromiumPathUtils by lazy { findClass("org.chromium.base.PathUtils")} private fun findClass(className: String): Class<*> { return try { diff --git a/core/src/main/kotlin/me/rhunk/snapenhance/core/features/impl/experiments/DeviceSpooferHook.kt b/core/src/main/kotlin/me/rhunk/snapenhance/core/features/impl/experiments/DeviceSpooferHook.kt @@ -1,93 +1,94 @@ package me.rhunk.snapenhance.core.features.impl.experiments +import android.annotation.SuppressLint +import android.location.Location +import android.net.ConnectivityManager +import android.net.Network +import android.net.NetworkCapabilities +import android.os.Build import me.rhunk.snapenhance.core.features.Feature import me.rhunk.snapenhance.core.features.FeatureLoadParams +import me.rhunk.snapenhance.core.util.LSPatchUpdater import me.rhunk.snapenhance.core.util.hook.HookStage -import me.rhunk.snapenhance.core.util.hook.Hooker +import me.rhunk.snapenhance.core.util.hook.hook class DeviceSpooferHook: Feature("device_spoofer", loadParams = FeatureLoadParams.INIT_SYNC) { + private fun hookInstallerPackageName() { + context.androidContext.packageManager::class.java.hook("getInstallerPackageName", HookStage.BEFORE) { param -> + param.setResult("com.android.vending") + } + } + + @SuppressLint("MissingPermission") override fun init() { - if (context.config.experimental.spoof.globalState != true) return + // force installer package name for lspatch users + if (LSPatchUpdater.HAS_LSPATCH) { + hookInstallerPackageName() + } - val fingerprint by context.config.experimental.spoof.device.fingerprint - val androidId by context.config.experimental.spoof.device.androidId - val getInstallerPackageName by context.config.experimental.spoof.device.getInstallerPackageName - val debugFlag by context.config.experimental.spoof.device.debugFlag - val mockLocationState by context.config.experimental.spoof.device.mockLocationState - val splitClassLoader by context.config.experimental.spoof.device.splitClassLoader - val isLowEndDevice by context.config.experimental.spoof.device.isLowEndDevice - val getDataDirectory by context.config.experimental.spoof.device.getDataDirectory + if (context.config.experimental.spoof.globalState != true) return - val settingsSecureClass = android.provider.Settings.Secure::class.java - val fingerprintClass = android.os.Build::class.java - val packageManagerClass = android.content.pm.PackageManager::class.java - val applicationInfoClass = android.content.pm.ApplicationInfo::class.java + val fingerprint by context.config.experimental.spoof.fingerprint + val androidId by context.config.experimental.spoof.androidId + val removeMockLocationFlag by context.config.experimental.spoof.removeMockLocationFlag + val overridePlayStoreInstallerPackageName by context.config.experimental.spoof.overridePlayStoreInstallerPackageName + val removeVpnTransportFlag by context.config.experimental.spoof.removeVpnTransportFlag + val randomizePersistentDeviceToken by context.config.experimental.spoof.randomizePersistentDeviceToken - //FINGERPRINT - if (fingerprint.isNotEmpty()) { - Hooker.hook(fingerprintClass, "FINGERPRINT", HookStage.BEFORE) { hookAdapter -> - hookAdapter.setResult(fingerprint) - context.log.verbose("Fingerprint spoofed to $fingerprint") - } - Hooker.hook(fingerprintClass, "deriveFingerprint", HookStage.BEFORE) { hookAdapter -> - hookAdapter.setResult(fingerprint) - context.log.verbose("Fingerprint spoofed to $fingerprint") - } + //Installer package name + if(overridePlayStoreInstallerPackageName) { + hookInstallerPackageName() } - //ANDROID ID - if (androidId.isNotEmpty()) { - Hooker.hook(settingsSecureClass, "getString", HookStage.BEFORE) { hookAdapter -> - if(hookAdapter.args()[1] == "android_id") { - hookAdapter.setResult(androidId) - context.log.verbose("Android ID spoofed to $androidId") + findClass("android.provider.Settings\$NameValueCache").apply { + hook("getStringForUser", HookStage.BEFORE) { hookAdapter -> + val key = hookAdapter.argNullable<String>(1) ?: return@hook + when (key) { + "android_id" -> { + if (androidId.isNotEmpty()) { + hookAdapter.setResult(androidId) + } + } + "ALLOW_MOCK_LOCATION" -> { + if (removeMockLocationFlag) { + hookAdapter.setResult("0") + } + } } } } - //TODO: org.chromium.base.BuildInfo, org.chromium.base.PathUtils getDataDirectory, MushroomDeviceTokenManager(?), TRANSPORT_VPN FLAG, isFromMockProvider, nativeLibraryDir, sourceDir, network capabilities, query all jvm properties - - //INSTALLER PACKAGE NAME - if(getInstallerPackageName.isNotEmpty()) { - Hooker.hook(packageManagerClass, "getInstallerPackageName", HookStage.BEFORE) { hookAdapter -> - hookAdapter.setResult(getInstallerPackageName) + if (removeMockLocationFlag) { + Location::class.java.hook("isMock", HookStage.BEFORE) { param -> + param.setResult(false) } } - //DEBUG FLAG - Hooker.hook(applicationInfoClass, "FLAG_DEBUGGABLE", HookStage.BEFORE) { hookAdapter -> - hookAdapter.setResult(debugFlag) - } - - //MOCK LOCATION - Hooker.hook(settingsSecureClass, "getString", HookStage.BEFORE) { hookAdapter -> - if(hookAdapter.args()[1] == "ALLOW_MOCK_LOCATION") { - hookAdapter.setResult(mockLocationState) - } + if (randomizePersistentDeviceToken) { + context.androidContext.filesDir.resolve("Snapchat").listFiles()?.firstOrNull { + it.name.startsWith("device_token") + }?.delete() } - //GET SPLIT CLASSLOADER - if(splitClassLoader.isNotEmpty()) { - Hooker.hook(context.classCache.chromiumJNIUtils, "getSplitClassLoader", HookStage.BEFORE) { hookAdapter -> - hookAdapter.setResult(splitClassLoader) - } - } + if (removeVpnTransportFlag) { + ConnectivityManager::class.java.hook("getAllNetworks", HookStage.AFTER) { param -> + val instance = param.thisObject() as? ConnectivityManager ?: return@hook + val networks = param.getResult() as? Array<*> ?: return@hook - //ISLOWENDDEVICE - if(isLowEndDevice.isNotEmpty()) { - Hooker.hook(context.classCache.chromiumBuildInfo, "getAll", HookStage.BEFORE) { hookAdapter -> - hookAdapter.setResult(isLowEndDevice) + param.setResult(networks.filterIsInstance<Network>().filter { network -> + val capabilities = instance.getNetworkCapabilities(network) ?: return@filter false + !capabilities.hasTransport(NetworkCapabilities.TRANSPORT_VPN) + }.toTypedArray()) } } - //GETDATADIRECTORY - if(getDataDirectory.isNotEmpty()) { - Hooker.hook(context.classCache.chromiumPathUtils, "getDataDirectory", HookStage.BEFORE) { hookAdapter -> - hookAdapter.setResult(getDataDirectory) + if (fingerprint.isNotEmpty()) { + Build.FINGERPRINT // init fingerprint field + Build::class.java.getField("FINGERPRINT").apply { + isAccessible = true + set(null, fingerprint) + isAccessible = false } } - - //accessibility_enabled - } } \ No newline at end of file diff --git a/core/src/main/kotlin/me/rhunk/snapenhance/core/util/LSPatchUpdater.kt b/core/src/main/kotlin/me/rhunk/snapenhance/core/util/LSPatchUpdater.kt @@ -9,6 +9,9 @@ import java.util.zip.ZipFile object LSPatchUpdater { private const val TAG = "LSPatchUpdater" + var HAS_LSPATCH = false + private set + private fun getModuleUniqueHash(module: ZipFile): String { return module.entries().asSequence() .filter { !it.isDirectory } @@ -38,6 +41,7 @@ object LSPatchUpdater { } ?: return } ?: return + HAS_LSPATCH = true context.log.verbose("Found embedded SE at ${embeddedModule.absolutePath}", TAG) val seAppApk = File(bridgeClient.getApplicationApkPath()).also {