mirror of
https://github.com/rhunk/SnapEnhance.git
synced 2025-05-23 18:16:15 +02:00
refactor: security features
Signed-off-by: rhunk <101876869+rhunk@users.noreply.github.com>
This commit is contained in:
parent
23666e5528
commit
8e8220a55e
@ -284,10 +284,9 @@ class HomeSettings : Routes.Route() {
|
||||
Column(
|
||||
verticalArrangement = Arrangement.spacedBy(4.dp),
|
||||
) {
|
||||
PreferenceToggle(context.sharedPreferences, key = "disable_sif_prod", text = "Disable Snap Integrity Fix")
|
||||
PreferenceToggle(context.sharedPreferences, key = "disable_feature_loading", text = "Disable Feature Loading")
|
||||
PreferenceToggle(context.sharedPreferences, key = "disable_mapper", text = "Disable Auto Mapper")
|
||||
PreferenceToggle(context.sharedPreferences, key = "disable_sif", text = "Disable Security Features")
|
||||
PreferenceToggle(context.sharedPreferences, key = "disable_mod_detection_version_check", text = "Disable Mod Detection Version Check")
|
||||
}
|
||||
}
|
||||
Spacer(modifier = Modifier.height(50.dp))
|
||||
|
@ -0,0 +1,126 @@
|
||||
package me.rhunk.snapenhance.core
|
||||
|
||||
import android.system.Os
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.NotInterested
|
||||
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.util.protobuf.ProtoEditor
|
||||
import me.rhunk.snapenhance.common.util.protobuf.ProtoReader
|
||||
import me.rhunk.snapenhance.core.event.events.impl.UnaryCallEvent
|
||||
import me.rhunk.snapenhance.core.util.ktx.setObjectField
|
||||
import java.io.FileDescriptor
|
||||
import kotlin.text.isNotEmpty
|
||||
|
||||
class SecurityFeatures(
|
||||
private val context: ModContext
|
||||
) {
|
||||
private fun transact(option: Int, option2: Long) = runCatching { Os.prctl(option, option2, 0, 0, 0) }.getOrNull()
|
||||
|
||||
private val token by lazy {
|
||||
transact(0, 0)
|
||||
}
|
||||
|
||||
private fun getStatus() = token?.run {
|
||||
transact(this, 0)?.toString(2)?.padStart(32, '0')?.count { it == '1' }
|
||||
}
|
||||
|
||||
fun init(): Boolean {
|
||||
val snapchatVersionCode = context.androidContext.packageManager?.getPackageInfo(context.androidContext.packageName, 0)?.longVersionCode ?: throw IllegalStateException("Failed to get version code")
|
||||
val mustUseSafeMode = MOD_DETECTION_VERSION_CHECK.checkVersion(snapchatVersionCode)?.second == VersionRequirement.OLDER_REQUIRED
|
||||
val debugDisable = context.bridgeClient.getDebugProp("disable_sif_prod", "false") != "true"
|
||||
|
||||
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")
|
||||
}.onFailure {
|
||||
context.log.error("Failed to load custom shared library", it)
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
if (debugDisable && !mustUseSafeMode) {
|
||||
runCatching {
|
||||
context.native.loadSharedLibrary(
|
||||
context.fileHandlerManager.getFileHandle(FileHandleScope.INTERNAL.key, InternalFileHandleType.SIF.key)
|
||||
.toWrapper()
|
||||
.readBytes()
|
||||
.takeIf {
|
||||
it.isNotEmpty()
|
||||
} ?: throw IllegalStateException("buffer is empty")
|
||||
)
|
||||
context.log.verbose("loaded sif")
|
||||
}.onFailure {
|
||||
context.log.error("Failed to load sif", it)
|
||||
return true
|
||||
}
|
||||
} else {
|
||||
context.log.warn("sif is disabled")
|
||||
}
|
||||
|
||||
token // pre init token
|
||||
|
||||
context.event.subscribe(UnaryCallEvent::class) { event ->
|
||||
if (!event.uri.contains("/Login")) return@subscribe
|
||||
|
||||
// intercept login response
|
||||
event.addResponseCallback {
|
||||
val response = ProtoReader(buffer)
|
||||
val isBlocked = when {
|
||||
event.uri.contains("TLv") -> response.getVarInt(1) == 14L
|
||||
else -> response.getVarInt(1) == 16L
|
||||
}
|
||||
|
||||
val errorDataIndex = when {
|
||||
response.contains(11) -> 11
|
||||
response.contains(10) -> 10
|
||||
response.contains(8) -> 8
|
||||
else -> return@addResponseCallback
|
||||
}
|
||||
|
||||
if (isBlocked) {
|
||||
val status = transact(token ?: return@addResponseCallback, 1)
|
||||
?.takeIf { it != 0 }
|
||||
?.let {
|
||||
val buffer = ByteArray(8192)
|
||||
val fd = FileDescriptor().apply {
|
||||
setObjectField("descriptor", it)
|
||||
}
|
||||
val read = Os.read(fd, buffer, 0, buffer.size)
|
||||
Os.close(fd)
|
||||
buffer.copyOfRange(0, read).decodeToString()
|
||||
} ?: return@addResponseCallback
|
||||
|
||||
buffer = ProtoEditor(buffer).apply {
|
||||
edit(errorDataIndex) {
|
||||
remove(1)
|
||||
addString(1, status)
|
||||
}
|
||||
}.toByteArray()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val status = getStatus()
|
||||
val safeMode = mustUseSafeMode || (status == null || status < 2)
|
||||
|
||||
if (safeMode && !debugDisable) {
|
||||
context.features.addActivityCreateListener {
|
||||
context.inAppOverlay.showStatusToast(
|
||||
icon = Icons.Filled.NotInterested,
|
||||
text = "SnapEnhance is not compatible with this version of Snapchat without SIF and will result in a ban.\nUse Snapchat ${MOD_DETECTION_VERSION_CHECK.maxVersion?.first ?: "0.0.0"} or older to avoid detections or use test accounts.",
|
||||
durationMs = 10000,
|
||||
maxLines = 6
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return safeMode
|
||||
}
|
||||
}
|
@ -223,35 +223,7 @@ class SnapEnhance {
|
||||
}
|
||||
}
|
||||
|
||||
appContext.config.experimental.nativeHooks.customSharedLibrary.get().takeIf { it.isNotEmpty() }?.let {
|
||||
runCatching {
|
||||
appContext.native.loadSharedLibrary(
|
||||
appContext.fileHandlerManager.getFileHandle(FileHandleScope.USER_IMPORT.key, it).toWrapper().readBytes()
|
||||
)
|
||||
appContext.log.verbose("loaded custom shared library")
|
||||
}.onFailure {
|
||||
appContext.log.error("Failed to load custom shared library", it)
|
||||
}
|
||||
}
|
||||
|
||||
if (appContext.bridgeClient.getDebugProp("disable_sif", "false") != "true") {
|
||||
runCatching {
|
||||
appContext.native.loadSharedLibrary(
|
||||
appContext.fileHandlerManager.getFileHandle(FileHandleScope.INTERNAL.key, InternalFileHandleType.SIF.key)
|
||||
.toWrapper()
|
||||
.readBytes()
|
||||
.takeIf {
|
||||
it.isNotEmpty()
|
||||
} ?: throw IllegalStateException("buffer is empty")
|
||||
)
|
||||
appContext.log.verbose("loaded sif")
|
||||
}.onFailure {
|
||||
safeMode = true
|
||||
appContext.log.error("Failed to load sif", it)
|
||||
}
|
||||
} else {
|
||||
appContext.log.warn("sif is disabled")
|
||||
}
|
||||
val safeMode = SecurityFeatures(appContext).init()
|
||||
|
||||
Runtime::class.java.findRestrictedMethod {
|
||||
it.name == "loadLibrary0" && it.parameterTypes.contentEquals(
|
||||
|
@ -28,6 +28,10 @@ class FeatureManager(
|
||||
private val features = mutableMapOf<KClass<out Feature>, Feature>()
|
||||
private val onActivityCreateListeners = mutableListOf<(Activity) -> Unit>()
|
||||
|
||||
fun addActivityCreateListener(block: (Activity) -> Unit) {
|
||||
onActivityCreateListeners.add(block)
|
||||
}
|
||||
|
||||
private fun register(vararg featureList: Feature) {
|
||||
if (context.bridgeClient.getDebugProp("disable_feature_loading") == "true") {
|
||||
context.log.warn("Feature loading is disabled")
|
||||
@ -61,7 +65,6 @@ class FeatureManager(
|
||||
fun init() {
|
||||
register(
|
||||
Debug(),
|
||||
SecurityFeatures(),
|
||||
EndToEndEncryption(),
|
||||
ScopeSync(),
|
||||
PreventMessageListAutoScroll(),
|
||||
|
@ -1,85 +0,0 @@
|
||||
package me.rhunk.snapenhance.core.features.impl
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.system.Os
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.NotInterested
|
||||
import me.rhunk.snapenhance.common.config.MOD_DETECTION_VERSION_CHECK
|
||||
import me.rhunk.snapenhance.common.config.VersionRequirement
|
||||
import me.rhunk.snapenhance.common.util.protobuf.ProtoEditor
|
||||
import me.rhunk.snapenhance.common.util.protobuf.ProtoReader
|
||||
import me.rhunk.snapenhance.core.event.events.impl.UnaryCallEvent
|
||||
import me.rhunk.snapenhance.core.features.Feature
|
||||
import me.rhunk.snapenhance.core.util.ktx.setObjectField
|
||||
import java.io.FileDescriptor
|
||||
|
||||
class SecurityFeatures : Feature("Security Features") {
|
||||
private fun transact(option: Int, option2: Long) = runCatching { Os.prctl(option, option2, 0, 0, 0) }.getOrNull()
|
||||
|
||||
private val token by lazy {
|
||||
transact(0, 0)
|
||||
}
|
||||
|
||||
private fun getStatus() = token?.run {
|
||||
transact(this, 0)?.toString(2)?.padStart(32, '0')?.count { it == '1' }
|
||||
}
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
override fun init() {
|
||||
token // pre init token
|
||||
|
||||
context.event.subscribe(UnaryCallEvent::class) { event ->
|
||||
if (!event.uri.contains("/Login")) return@subscribe
|
||||
|
||||
// intercept login response
|
||||
event.addResponseCallback {
|
||||
val response = ProtoReader(buffer)
|
||||
val isBlocked = when {
|
||||
event.uri.contains("TLv") -> response.getVarInt(1) == 14L
|
||||
else -> response.getVarInt(1) == 16L
|
||||
}
|
||||
|
||||
val errorDataIndex = when {
|
||||
response.contains(11) -> 11
|
||||
response.contains(10) -> 10
|
||||
response.contains(8) -> 8
|
||||
else -> return@addResponseCallback
|
||||
}
|
||||
|
||||
if (isBlocked) {
|
||||
val status = transact(token ?: return@addResponseCallback, 1)?.let {
|
||||
val buffer = ByteArray(8192)
|
||||
val fd = FileDescriptor().apply {
|
||||
setObjectField("descriptor", it)
|
||||
}
|
||||
val read = Os.read(fd, buffer, 0, buffer.size)
|
||||
Os.close(fd)
|
||||
buffer.copyOfRange(0, read).decodeToString()
|
||||
}!!
|
||||
|
||||
buffer = ProtoEditor(buffer).apply {
|
||||
edit(errorDataIndex) {
|
||||
remove(1)
|
||||
addString(1, status)
|
||||
}
|
||||
}.toByteArray()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val status = getStatus()
|
||||
val canCheckVersion = context.bridgeClient.getDebugProp("disable_mod_detection_version_check", "false") != "true"
|
||||
val snapchatVersionCode = context.androidContext.packageManager.getPackageInfo(context.androidContext.packageName, 0).longVersionCode
|
||||
|
||||
if (canCheckVersion && MOD_DETECTION_VERSION_CHECK.checkVersion(snapchatVersionCode)?.second == VersionRequirement.OLDER_REQUIRED && (status == null || status < 2)) {
|
||||
onNextActivityCreate {
|
||||
context.inAppOverlay.showStatusToast(
|
||||
icon = Icons.Filled.NotInterested,
|
||||
text = "SnapEnhance is not compatible with this version of Snapchat without SIF and will result in a ban.\nUse Snapchat ${MOD_DETECTION_VERSION_CHECK.maxVersion?.first ?: "0.0.0"} or older to avoid detections or use test accounts.",
|
||||
durationMs = 10000,
|
||||
maxLines = 6
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user