refactor: security features
- remove remote shared library manager - prevent plugin from loading in newer versions Signed-off-by: rhunk <101876869+rhunk@users.noreply.github.com>
This commit is contained in:
@@ -1,123 +0,0 @@
|
||||
package me.rhunk.snapenhance
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Notification
|
||||
import android.app.NotificationChannel
|
||||
import android.app.NotificationManager
|
||||
import android.app.PendingIntent
|
||||
import android.content.Intent
|
||||
import android.os.Build
|
||||
import androidx.core.net.toUri
|
||||
import me.rhunk.snapenhance.common.BuildConfig
|
||||
import me.rhunk.snapenhance.common.bridge.InternalFileHandleType
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
import java.io.File
|
||||
|
||||
class RemoteSharedLibraryManager(
|
||||
private val remoteSideContext: RemoteSideContext
|
||||
) {
|
||||
private val okHttpClient = OkHttpClient()
|
||||
|
||||
private fun getVersion(): String? {
|
||||
return runCatching {
|
||||
okHttpClient.newCall(
|
||||
Request.Builder()
|
||||
.url("${BuildConfig.SIF_ENDPOINT}/version")
|
||||
.build()
|
||||
).execute().use { response ->
|
||||
if (!response.isSuccessful) {
|
||||
return null
|
||||
}
|
||||
response.body.string()
|
||||
}
|
||||
}.getOrNull()
|
||||
}
|
||||
|
||||
private fun downloadLatest(outputFile: File): Boolean {
|
||||
val abi = Build.SUPPORTED_ABIS.firstOrNull() ?: return false
|
||||
val request = Request.Builder()
|
||||
.url("${BuildConfig.SIF_ENDPOINT}/$abi.so")
|
||||
.build()
|
||||
runCatching {
|
||||
okHttpClient.newCall(request).execute().use { response ->
|
||||
if (!response.isSuccessful) {
|
||||
return false
|
||||
}
|
||||
response.body.byteStream().use { input ->
|
||||
outputFile.outputStream().use { output ->
|
||||
input.copyTo(output)
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
}.onFailure {
|
||||
remoteSideContext.log.error("Failed to download latest sif", it)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
@SuppressLint("ApplySharedPref")
|
||||
fun init() {
|
||||
val libraryFile = InternalFileHandleType.SIF.resolve(remoteSideContext.androidContext)
|
||||
val currentVersion = remoteSideContext.sharedPreferences.getString("sif", null)?.trim()
|
||||
if (currentVersion == null || currentVersion == "false") {
|
||||
libraryFile.takeIf { it.exists() }?.delete()
|
||||
remoteSideContext.log.info("sif can't be loaded due to user preference")
|
||||
return
|
||||
}
|
||||
val latestVersion = getVersion()?.trim() ?: run {
|
||||
throw Exception("Failed to get latest sif version")
|
||||
}
|
||||
|
||||
if (currentVersion == latestVersion) {
|
||||
remoteSideContext.log.info("sif is up to date ($currentVersion)")
|
||||
return
|
||||
}
|
||||
|
||||
remoteSideContext.log.info("Updating sif from $currentVersion to $latestVersion")
|
||||
if (downloadLatest(libraryFile)) {
|
||||
remoteSideContext.sharedPreferences.edit().putString("sif", latestVersion).commit()
|
||||
remoteSideContext.shortToast("SIF updated to $latestVersion!")
|
||||
|
||||
if (currentVersion.isNotEmpty()) {
|
||||
val notificationManager = remoteSideContext.androidContext.getSystemService(NotificationManager::class.java)
|
||||
val channelId = "sif_update"
|
||||
|
||||
notificationManager.createNotificationChannel(
|
||||
NotificationChannel(
|
||||
channelId,
|
||||
"SIF Updates",
|
||||
NotificationManager.IMPORTANCE_DEFAULT
|
||||
)
|
||||
)
|
||||
|
||||
notificationManager.notify(
|
||||
System.nanoTime().toInt(),
|
||||
Notification.Builder(remoteSideContext.androidContext, channelId)
|
||||
.setContentTitle("SnapEnhance")
|
||||
.setContentText("Security Features have been updated to version $latestVersion")
|
||||
.setSmallIcon(android.R.drawable.stat_sys_download_done)
|
||||
.setContentIntent(PendingIntent.getActivity(
|
||||
remoteSideContext.androidContext,
|
||||
0,
|
||||
Intent().apply {
|
||||
action = Intent.ACTION_VIEW
|
||||
data = "https://github.com/SnapEnhance/resources".toUri()
|
||||
flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
||||
},
|
||||
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
|
||||
)).build()
|
||||
)
|
||||
}
|
||||
|
||||
// force restart snapchat
|
||||
runCatching {
|
||||
remoteSideContext.config.configStateListener?.takeIf { it.asBinder().pingBinder() }?.onRestartRequired()
|
||||
}
|
||||
} else {
|
||||
remoteSideContext.log.warn("Failed to download latest sif")
|
||||
throw Exception("Failed to download latest sif")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -78,7 +78,6 @@ class RemoteSideContext(
|
||||
val tracker = RemoteTracker(this)
|
||||
val accountStorage = RemoteAccountStorage(this)
|
||||
val locationManager = RemoteLocationManager(this)
|
||||
val remoteSharedLibraryManager = RemoteSharedLibraryManager(this)
|
||||
|
||||
//used to load bitmoji selfies and download previews
|
||||
val imageLoader by lazy {
|
||||
@@ -132,13 +131,6 @@ class RemoteSideContext(
|
||||
messageLogger.purgeTrackerLogs(it)
|
||||
}
|
||||
}
|
||||
coroutineScope.launch {
|
||||
runCatching {
|
||||
remoteSharedLibraryManager.init()
|
||||
}.onFailure {
|
||||
log.error("Failed to init RemoteSharedLibraryManager", it)
|
||||
}
|
||||
}
|
||||
}
|
||||
}.onFailure {
|
||||
log.error("Failed to load RemoteSideContext", it)
|
||||
@@ -220,10 +212,6 @@ class RemoteSideContext(
|
||||
requirements = requirements or Requirements.MAPPINGS
|
||||
}
|
||||
|
||||
if (sharedPreferences.getString("sif", null) == null) {
|
||||
requirements = requirements or Requirements.SIF
|
||||
}
|
||||
|
||||
if (requirements == 0) return false
|
||||
|
||||
val currentContext = activity ?: androidContext
|
||||
|
||||
@@ -154,9 +154,6 @@ class HomeSettings : Routes.Route() {
|
||||
RowAction(key = "change_language") {
|
||||
context.checkForRequirements(Requirements.LANGUAGE)
|
||||
}
|
||||
RowAction(key = "security_features") {
|
||||
context.checkForRequirements(Requirements.SIF)
|
||||
}
|
||||
RowTitle(title = translation["message_logger_title"])
|
||||
ShiftedRow {
|
||||
Column(
|
||||
@@ -285,7 +282,7 @@ class HomeSettings : Routes.Route() {
|
||||
Column(
|
||||
verticalArrangement = Arrangement.spacedBy(4.dp),
|
||||
) {
|
||||
PreferenceToggle(context.sharedPreferences, key = "enable_security_features", text = "Enable Security Features")
|
||||
PreferenceToggle(context.sharedPreferences, key = "test_mode", text = "Test Mode (Debugging)")
|
||||
PreferenceToggle(context.sharedPreferences, key = "disable_feature_loading", text = "Disable Feature Loading")
|
||||
PreferenceToggle(context.sharedPreferences, key = "disable_mapper", text = "Disable Auto Mapper")
|
||||
}
|
||||
|
||||
@@ -29,7 +29,10 @@ import androidx.navigation.compose.rememberNavController
|
||||
import me.rhunk.snapenhance.SharedContextHolder
|
||||
import me.rhunk.snapenhance.common.ui.AppMaterialTheme
|
||||
import me.rhunk.snapenhance.ui.setup.screens.SetupScreen
|
||||
import me.rhunk.snapenhance.ui.setup.screens.impl.*
|
||||
import me.rhunk.snapenhance.ui.setup.screens.impl.MappingsScreen
|
||||
import me.rhunk.snapenhance.ui.setup.screens.impl.PermissionsScreen
|
||||
import me.rhunk.snapenhance.ui.setup.screens.impl.PickLanguageScreen
|
||||
import me.rhunk.snapenhance.ui.setup.screens.impl.SaveFolderScreen
|
||||
|
||||
|
||||
class SetupActivity : ComponentActivity() {
|
||||
@@ -66,9 +69,6 @@ class SetupActivity : ComponentActivity() {
|
||||
if (isFirstRun || hasRequirement(Requirements.MAPPINGS)) {
|
||||
add(MappingsScreen().apply { route = "mappings" })
|
||||
}
|
||||
if (isFirstRun || hasRequirement(Requirements.SIF)) {
|
||||
add(SecurityScreen().apply { route = "security" })
|
||||
}
|
||||
}
|
||||
|
||||
// If there are no required screens, we can just finish the activity
|
||||
|
||||
@@ -1,132 +0,0 @@
|
||||
package me.rhunk.snapenhance.ui.setup.screens.impl
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.WarningAmber
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import me.rhunk.snapenhance.ui.setup.screens.SetupScreen
|
||||
|
||||
class SecurityScreen : SetupScreen() {
|
||||
@SuppressLint("ApplySharedPref")
|
||||
@Composable
|
||||
override fun Content() {
|
||||
Icon(
|
||||
imageVector = Icons.Default.WarningAmber,
|
||||
contentDescription = null,
|
||||
modifier = Modifier
|
||||
.padding(16.dp)
|
||||
.size(30.dp),
|
||||
)
|
||||
|
||||
DialogText(
|
||||
"Since Snapchat has implemented additional security measures against third-party applications such as SnapEnhance, we offer a non-opensource solution that reduces the risk of banning and prevents Snapchat from detecting SnapEnhance. " +
|
||||
"\nPlease note that this solution does not provide a ban bypass or spoofer for anything, and does not take any personal data or communicate with the network." +
|
||||
"\nWe also encourage you to use official signed builds to avoid compromising the security of your account." +
|
||||
"\nIf you're having trouble using the solution, or are experiencing crashes, join the Telegram Group for help: https://t.me/snapenhance_chat"
|
||||
)
|
||||
|
||||
var denyDialog by remember { mutableStateOf(false) }
|
||||
|
||||
if (denyDialog) {
|
||||
AlertDialog(
|
||||
onDismissRequest = {
|
||||
denyDialog = false
|
||||
},
|
||||
text = {
|
||||
Text("Are you sure you don't want to use this solution? You can always change this later in the settings in the SnapEnhance app.")
|
||||
},
|
||||
dismissButton = {
|
||||
Button(onClick = {
|
||||
denyDialog = false
|
||||
}) {
|
||||
Text("Go back")
|
||||
}
|
||||
},
|
||||
confirmButton = {
|
||||
Button(onClick = {
|
||||
context.sharedPreferences.edit().putString("sif", "false").apply()
|
||||
goNext()
|
||||
}) {
|
||||
Text("Yes, I'm sure")
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
var downloadJob by remember { mutableStateOf(null as Job?) }
|
||||
var jobError by remember { mutableStateOf(null as Throwable?) }
|
||||
|
||||
if (downloadJob != null) {
|
||||
AlertDialog(onDismissRequest = {
|
||||
downloadJob?.cancel()
|
||||
downloadJob = null
|
||||
}, confirmButton = {}, text = {
|
||||
Column(
|
||||
modifier = Modifier.verticalScroll(rememberScrollState()).fillMaxWidth(),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.spacedBy(10.dp),
|
||||
) {
|
||||
if (jobError != null) {
|
||||
Text("Failed to download the required files.\n\n${jobError?.message}")
|
||||
} else {
|
||||
Text("Downloading the required files...")
|
||||
CircularProgressIndicator(modifier = Modifier.padding(16.dp))
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fun newDownloadJob() {
|
||||
downloadJob?.cancel()
|
||||
downloadJob = context.coroutineScope.launch {
|
||||
context.sharedPreferences.edit().putString("sif", "").commit()
|
||||
runCatching {
|
||||
context.remoteSharedLibraryManager.init()
|
||||
}.onFailure {
|
||||
jobError = it
|
||||
context.log.error("Failed to download the required files", it)
|
||||
}.onSuccess {
|
||||
downloadJob = null
|
||||
withContext(Dispatchers.Main) {
|
||||
goNext()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Column (
|
||||
modifier = Modifier.padding(16.dp),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.spacedBy(10.dp)
|
||||
) {
|
||||
Button(
|
||||
onClick = {
|
||||
newDownloadJob()
|
||||
}
|
||||
) {
|
||||
Text("Accept and continue", fontSize = 18.sp, fontWeight = FontWeight.Bold)
|
||||
}
|
||||
Button(
|
||||
colors = ButtonDefaults.buttonColors(containerColor = MaterialTheme.colorScheme.error),
|
||||
onClick = {
|
||||
denyDialog = true
|
||||
}
|
||||
) {
|
||||
Text("I don't want to use this solution")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
package me.rhunk.snapenhance.ui.setup.screens.impl
|
||||
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import me.rhunk.snapenhance.ui.setup.screens.SetupScreen
|
||||
|
||||
class WelcomeScreen : SetupScreen() {
|
||||
|
||||
@Composable
|
||||
override fun Content() {
|
||||
Text(text = "Welcome")
|
||||
Button(onClick = { allowNext(true) }) {
|
||||
Text(text = "Next")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -316,10 +316,6 @@
|
||||
"name": "Change Language",
|
||||
"description": "Change the language of SnapEnhance"
|
||||
},
|
||||
"security_features": {
|
||||
"name": "Security Features",
|
||||
"description": "Change security features preferences"
|
||||
},
|
||||
"file_imports": {
|
||||
"name": "File Imports",
|
||||
"description": "Import files for use in Snapchat"
|
||||
|
||||
@@ -34,8 +34,7 @@ enum class InternalFileHandleType(
|
||||
MAPPINGS("mappings", "mappings.json"),
|
||||
MESSAGE_LOGGER("message_logger", "message_logger.db", isDatabase = true),
|
||||
PINNED_BEST_FRIEND("pinned_best_friend", "pinned_best_friend.txt"),
|
||||
NATIVE_SIG_CACHE("native_sig_cache", "native_sig_cache.txt"),
|
||||
SIF("sif", "libsif.so");
|
||||
NATIVE_SIG_CACHE("native_sig_cache", "native_sig_cache.txt");
|
||||
|
||||
fun resolve(context: Context): File = if (isDatabase) {
|
||||
context.getDatabasePath(fileName)
|
||||
|
||||
@@ -75,7 +75,7 @@ class ModContext(
|
||||
val isDeveloper by lazy { config.scripting.developerMode.get() }
|
||||
|
||||
var isMainActivityPaused = true
|
||||
var isSafeMode = false
|
||||
var disablePlugin = false
|
||||
|
||||
fun <T : Feature> feature(featureClass: KClass<T>): T {
|
||||
return features.get(featureClass)!!
|
||||
|
||||
@@ -18,12 +18,13 @@ import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import kotlinx.coroutines.delay
|
||||
import me.rhunk.snapenhance.common.bridge.FileHandleScope
|
||||
import me.rhunk.snapenhance.common.bridge.InternalFileHandleType
|
||||
import me.rhunk.snapenhance.common.bridge.toWrapper
|
||||
import me.rhunk.snapenhance.common.config.MOD_DETECTION_VERSION_CHECK
|
||||
import me.rhunk.snapenhance.common.config.VersionRequirement
|
||||
import me.rhunk.snapenhance.common.ui.createComposeView
|
||||
import me.rhunk.snapenhance.core.event.events.impl.UnaryCallEvent
|
||||
import me.rhunk.snapenhance.core.ui.CustomComposable
|
||||
import me.rhunk.snapenhance.core.util.ktx.getObjectField
|
||||
|
||||
class SecurityFeatures(
|
||||
private val context: ModContext
|
||||
@@ -38,67 +39,55 @@ class SecurityFeatures(
|
||||
transact(this, 0)?.toString(2)?.padStart(32, '0')?.count { it == '1' }
|
||||
}
|
||||
|
||||
private fun isSafeMode(): Boolean {
|
||||
fun init() {
|
||||
val snapchatVersionCode = context.androidContext.packageManager?.getPackageInfo(context.androidContext.packageName, 0)?.longVersionCode ?: throw IllegalStateException("Failed to get version code")
|
||||
val shouldUseSafeMode = MOD_DETECTION_VERSION_CHECK.checkVersion(snapchatVersionCode)?.second == VersionRequirement.OLDER_REQUIRED
|
||||
var shouldDisablePlugin = MOD_DETECTION_VERSION_CHECK.checkVersion(snapchatVersionCode)?.second == VersionRequirement.OLDER_REQUIRED
|
||||
|
||||
// load user shared library
|
||||
context.config.experimental.nativeHooks.customSharedLibrary.get().takeIf { it.isNotEmpty() }?.let {
|
||||
runCatching {
|
||||
context.native.loadSharedLibrary(
|
||||
context.fileHandlerManager.getFileHandle(FileHandleScope.USER_IMPORT.key, it).toWrapper().readBytes()
|
||||
)
|
||||
context.log.verbose("loaded custom shared library")
|
||||
shouldDisablePlugin = false
|
||||
|
||||
lateinit var composable: CustomComposable
|
||||
composable = {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.padding(16.dp)
|
||||
.align(Alignment.TopCenter),
|
||||
) {
|
||||
Icon(Icons.Filled.Check, contentDescription = null, tint = Color(0xFF85A947))
|
||||
}
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
delay(2500)
|
||||
context.inAppOverlay.removeCustomComposable(composable)
|
||||
}
|
||||
}
|
||||
|
||||
context.inAppOverlay.addCustomComposable(composable)
|
||||
}.onFailure {
|
||||
context.log.error("Failed to load custom shared library", it)
|
||||
return true
|
||||
}
|
||||
} ?: context.bridgeClient.getDebugProp("enable_security_features", "false").takeIf { it == "true" }?.runCatching {
|
||||
context.native.loadSharedLibrary(
|
||||
context.fileHandlerManager.getFileHandle(FileHandleScope.INTERNAL.key, InternalFileHandleType.SIF.key)
|
||||
.toWrapper()
|
||||
.readBytes()
|
||||
.takeIf {
|
||||
it.isNotEmpty()
|
||||
} ?: throw IllegalStateException("Binary is empty")
|
||||
)
|
||||
context.log.verbose("loaded sif")
|
||||
}?.onFailure {
|
||||
context.log.error("Failed to load sif: " + it.message)
|
||||
return shouldUseSafeMode
|
||||
} ?: context.log.warn("Security features are disabled")
|
||||
|
||||
token // pre init token
|
||||
|
||||
val status = getStatus()
|
||||
val safeMode = shouldUseSafeMode && (status == null || status < 2)
|
||||
|
||||
if (status != null && status >= 2) {
|
||||
context.log.verbose("status=$status")
|
||||
lateinit var composable: CustomComposable
|
||||
composable = {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.padding(16.dp)
|
||||
.align(Alignment.TopCenter),
|
||||
) {
|
||||
Icon(Icons.Filled.Check, contentDescription = null, tint = Color(0xFF85A947))
|
||||
}
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
delay(2500)
|
||||
context.inAppOverlay.removeCustomComposable(composable)
|
||||
}
|
||||
}
|
||||
context.inAppOverlay.addCustomComposable(composable)
|
||||
}
|
||||
|
||||
return safeMode
|
||||
}
|
||||
if (context.bridgeClient.getDebugProp("test_mode", "false") == "true") {
|
||||
shouldDisablePlugin = false
|
||||
}
|
||||
|
||||
fun init() {
|
||||
context.isSafeMode = isSafeMode()
|
||||
context.log.verbose("isSafeMode=${context.isSafeMode}")
|
||||
if (!context.isSafeMode) return
|
||||
context.disablePlugin = shouldDisablePlugin
|
||||
context.log.verbose("disablePlugin=${context.disablePlugin}")
|
||||
if (!context.disablePlugin) return
|
||||
|
||||
context.event.subscribe(UnaryCallEvent::class) { event ->
|
||||
val callOptions = event.adapter.arg<Any>(2).let { it.javaClass.getMethod("build").invoke(it) } ?: return@subscribe
|
||||
if (callOptions.getObjectField("mAttestation") != null) {
|
||||
event.canceled = true
|
||||
}
|
||||
}
|
||||
|
||||
context.features.addActivityCreateListener { activity ->
|
||||
if (!activity.javaClass.name.endsWith("LoginSignupActivity")) return@addActivityCreateListener
|
||||
|
||||
@@ -18,7 +18,6 @@ import me.rhunk.snapenhance.common.action.EnumAction
|
||||
import me.rhunk.snapenhance.common.bridge.FileHandleScope
|
||||
import me.rhunk.snapenhance.common.bridge.InternalFileHandleType
|
||||
import me.rhunk.snapenhance.common.bridge.toWrapper
|
||||
import me.rhunk.snapenhance.common.config.MOD_DETECTION_VERSION_CHECK
|
||||
import me.rhunk.snapenhance.common.data.FriendStreaks
|
||||
import me.rhunk.snapenhance.common.data.MessagingFriendInfo
|
||||
import me.rhunk.snapenhance.common.data.MessagingGroupInfo
|
||||
@@ -33,6 +32,7 @@ import me.rhunk.snapenhance.core.util.hook.HookAdapter
|
||||
import me.rhunk.snapenhance.core.util.hook.HookStage
|
||||
import me.rhunk.snapenhance.core.util.hook.findRestrictedMethod
|
||||
import me.rhunk.snapenhance.core.util.hook.hook
|
||||
import me.rhunk.snapenhance.mapper.impl.PlatformClientAttestationMapper
|
||||
import kotlin.reflect.KClass
|
||||
import kotlin.system.exitProcess
|
||||
import kotlin.system.measureTimeMillis
|
||||
@@ -231,24 +231,22 @@ class SnapEnhance {
|
||||
else arrayOf(ClassLoader::class.java, String::class.java)
|
||||
)
|
||||
}!!.apply {
|
||||
if (appContext.isSafeMode) {
|
||||
if (appContext.disablePlugin) {
|
||||
hook(HookStage.BEFORE) { param ->
|
||||
if (param.arg<String>(1) != "scplugin") return@hook
|
||||
param.setResult(null)
|
||||
appContext.runOnUiThread {
|
||||
appContext.inAppOverlay.showStatusToast(
|
||||
Icons.Outlined.Cancel,
|
||||
"SnapEnhance is not compatible with this version of Snapchat and will result in a ban.\nUse Snapchat ${MOD_DETECTION_VERSION_CHECK.maxVersion?.first ?: "0.0.0"} or older to avoid detections.",
|
||||
durationMs = 7000,
|
||||
maxLines = 6
|
||||
)
|
||||
appContext.log.verbose("skipped scplugin load")
|
||||
|
||||
appContext.mappings.useMapper(PlatformClientAttestationMapper::class) {
|
||||
pluginNativeClass.getAsClass()?.methods?.filter {
|
||||
it.declaringClass == pluginNativeClass.getAsClass()
|
||||
}?.forEach { method ->
|
||||
method.hook(HookStage.BEFORE) {
|
||||
appContext.log.verbose("called $method")
|
||||
it.setResult(null)
|
||||
}
|
||||
} ?: error("Failed to get pluginNativeClass class")
|
||||
}
|
||||
runCatching {
|
||||
Thread.sleep(Long.MAX_VALUE)
|
||||
}.onFailure {
|
||||
appContext.log.error(it)
|
||||
}
|
||||
exitProcess(1)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -39,6 +39,7 @@ class ClassMapper(
|
||||
StreaksExpirationMapper(),
|
||||
COFObservableMapper(),
|
||||
FoldingLayoutMapper(),
|
||||
PlatformClientAttestationMapper(),
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
package me.rhunk.snapenhance.mapper.impl
|
||||
|
||||
import com.android.tools.smali.dexlib2.Opcode
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.formats.Instruction35c
|
||||
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
|
||||
import me.rhunk.snapenhance.mapper.AbstractClassMapper
|
||||
import me.rhunk.snapenhance.mapper.ext.getSuperClassName
|
||||
|
||||
class PlatformClientAttestationMapper: AbstractClassMapper("PlatformClientAttestationMapper") {
|
||||
val pluginNativeClass = classReference("pluginNativeClass")
|
||||
|
||||
init {
|
||||
mapper {
|
||||
for (clazz in classes) {
|
||||
if (clazz.getSuperClassName()?.endsWith("PlatformClientAttestation") != true) continue
|
||||
val getSignatureMethod = clazz.methods.firstOrNull { it.name == "getSignature" } ?: continue
|
||||
|
||||
getSignatureMethod.implementation?.instructions?.firstOrNull { instruction ->
|
||||
instruction.opcode == Opcode.INVOKE_STATIC && instruction is Instruction35c && (instruction.reference as? MethodReference)?.takeIf { it.definingClass.count { it == '/' } == 1 }?.returnType == "[B"
|
||||
}?.let { instruction ->
|
||||
val method = (instruction as Instruction35c).reference as MethodReference
|
||||
pluginNativeClass.set(method.definingClass.replaceFirst("L", "").replaceFirst(";", "").replace("/", "."))
|
||||
}
|
||||
return@mapper
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user