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:
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 {