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(
|
Column(
|
||||||
verticalArrangement = Arrangement.spacedBy(4.dp),
|
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_feature_loading", text = "Disable Feature Loading")
|
||||||
PreferenceToggle(context.sharedPreferences, key = "disable_mapper", text = "Disable Auto Mapper")
|
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))
|
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 {
|
val safeMode = SecurityFeatures(appContext).init()
|
||||||
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")
|
|
||||||
}
|
|
||||||
|
|
||||||
Runtime::class.java.findRestrictedMethod {
|
Runtime::class.java.findRestrictedMethod {
|
||||||
it.name == "loadLibrary0" && it.parameterTypes.contentEquals(
|
it.name == "loadLibrary0" && it.parameterTypes.contentEquals(
|
||||||
|
@ -28,6 +28,10 @@ class FeatureManager(
|
|||||||
private val features = mutableMapOf<KClass<out Feature>, Feature>()
|
private val features = mutableMapOf<KClass<out Feature>, Feature>()
|
||||||
private val onActivityCreateListeners = mutableListOf<(Activity) -> Unit>()
|
private val onActivityCreateListeners = mutableListOf<(Activity) -> Unit>()
|
||||||
|
|
||||||
|
fun addActivityCreateListener(block: (Activity) -> Unit) {
|
||||||
|
onActivityCreateListeners.add(block)
|
||||||
|
}
|
||||||
|
|
||||||
private fun register(vararg featureList: Feature) {
|
private fun register(vararg featureList: Feature) {
|
||||||
if (context.bridgeClient.getDebugProp("disable_feature_loading") == "true") {
|
if (context.bridgeClient.getDebugProp("disable_feature_loading") == "true") {
|
||||||
context.log.warn("Feature loading is disabled")
|
context.log.warn("Feature loading is disabled")
|
||||||
@ -61,7 +65,6 @@ class FeatureManager(
|
|||||||
fun init() {
|
fun init() {
|
||||||
register(
|
register(
|
||||||
Debug(),
|
Debug(),
|
||||||
SecurityFeatures(),
|
|
||||||
EndToEndEncryption(),
|
EndToEndEncryption(),
|
||||||
ScopeSync(),
|
ScopeSync(),
|
||||||
PreventMessageListAutoScroll(),
|
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