Update snet extension

Receive full snet payload from extension
This commit is contained in:
topjohnwu 2020-06-30 02:24:58 -07:00
parent a0b47f3ca3
commit 4bbd7989dd
6 changed files with 86 additions and 70 deletions

View File

@ -12,8 +12,8 @@ object Const {
const val MAGISK_LOG = "/cache/magisk.log" const val MAGISK_LOG = "/cache/magisk.log"
// Versions // Versions
const val SNET_EXT_VER = 13 const val SNET_EXT_VER = 14
const val SNET_REVISION = "a6c47f86f10b310358afa9dbe837037dd5d561df" const val SNET_REVISION = "5e28617412bdad2396eab87fa786094d8242e568"
const val BOOTCTL_REVISION = "a6c47f86f10b310358afa9dbe837037dd5d561df" const val BOOTCTL_REVISION = "a6c47f86f10b310358afa9dbe837037dd5d561df"
// Misc // Misc

View File

@ -1,5 +1,7 @@
package com.topjohnwu.magisk.core.utils package com.topjohnwu.magisk.core.utils
import org.json.JSONObject
interface SafetyNetHelper { interface SafetyNetHelper {
val version: Int val version: Int
@ -7,15 +9,6 @@ interface SafetyNetHelper {
fun attest() fun attest()
interface Callback { interface Callback {
fun onResponse(responseCode: Int) fun onResponse(response: JSONObject?)
}
companion object {
const val RESPONSE_ERR = 0x01
const val CONNECTION_FAIL = 0x02
const val BASIC_PASS = 0x10
const val CTS_PASS = 0x20
} }
} }

View File

@ -2,10 +2,14 @@ package com.topjohnwu.magisk.model.events
import com.topjohnwu.magisk.core.model.MagiskPolicy import com.topjohnwu.magisk.core.model.MagiskPolicy
import com.topjohnwu.magisk.utils.RxBus import com.topjohnwu.magisk.utils.RxBus
import org.json.JSONObject
sealed class PolicyUpdateEvent(val item: MagiskPolicy) : RxBus.Event { sealed class PolicyUpdateEvent(val item: MagiskPolicy) : RxBus.Event {
class Notification(item: MagiskPolicy) : PolicyUpdateEvent(item) class Notification(item: MagiskPolicy) : PolicyUpdateEvent(item)
class Log(item: MagiskPolicy) : PolicyUpdateEvent(item) class Log(item: MagiskPolicy) : PolicyUpdateEvent(item)
} }
data class SafetyNetResult(val responseCode: Int) : RxBus.Event data class SafetyNetResult(
val response: JSONObject? = null,
val dismiss: Boolean = false
) : RxBus.Event

View File

@ -10,6 +10,7 @@ import com.topjohnwu.magisk.core.model.module.Repo
import com.topjohnwu.magisk.core.utils.SafetyNetHelper import com.topjohnwu.magisk.core.utils.SafetyNetHelper
import com.topjohnwu.magisk.data.repository.MagiskRepository import com.topjohnwu.magisk.data.repository.MagiskRepository
import com.topjohnwu.magisk.extensions.DynamicClassLoader import com.topjohnwu.magisk.extensions.DynamicClassLoader
import com.topjohnwu.magisk.extensions.OnErrorListener
import com.topjohnwu.magisk.extensions.subscribeK import com.topjohnwu.magisk.extensions.subscribeK
import com.topjohnwu.magisk.extensions.writeTo import com.topjohnwu.magisk.extensions.writeTo
import com.topjohnwu.magisk.utils.RxBus import com.topjohnwu.magisk.utils.RxBus
@ -19,8 +20,10 @@ import com.topjohnwu.superuser.Shell
import dalvik.system.DexFile import dalvik.system.DexFile
import io.reactivex.Completable import io.reactivex.Completable
import io.reactivex.subjects.PublishSubject import io.reactivex.subjects.PublishSubject
import org.json.JSONObject
import org.koin.core.KoinComponent import org.koin.core.KoinComponent
import org.koin.core.inject import org.koin.core.inject
import timber.log.Timber
import java.io.File import java.io.File
import java.lang.reflect.InvocationHandler import java.lang.reflect.InvocationHandler
@ -40,18 +43,25 @@ class UpdateSafetyNetEvent : ViewEvent(), ContextExecutor, KoinComponent, Safety
private val magiskRepo by inject<MagiskRepository>() private val magiskRepo by inject<MagiskRepository>()
private val rxBus by inject<RxBus>() private val rxBus by inject<RxBus>()
private lateinit var EXT_APK: File private lateinit var apk: File
private lateinit var EXT_DEX: File private lateinit var dex: File
override fun invoke(context: Context) { override fun invoke(context: Context) {
val die = ::EXT_APK.isInitialized apk = File("${context.filesDir.parent}/snet", "snet.jar")
dex = File(apk.parent, "snet.dex")
EXT_APK = File("${context.filesDir.parent}/snet", "snet.jar") attest(context) {
EXT_DEX = File(EXT_APK.parent, "snet.dex") // Download and retry
Shell.sh("rm -rf " + apk.parent).exec()
apk.parentFile?.mkdir()
download(context, true)
}
}
private fun attest(context: Context, onError: OnErrorListener) {
Completable.fromAction { Completable.fromAction {
val loader = DynamicClassLoader(EXT_APK) val loader = DynamicClassLoader(apk)
val dex = DexFile.loadDex(EXT_APK.path, EXT_DEX.path, 0) val dex = DexFile.loadDex(apk.path, dex.path, 0)
// Scan through the dex and find our helper class // Scan through the dex and find our helper class
var helperClass: Class<*>? = null var helperClass: Class<*>? = null
@ -66,32 +76,25 @@ class UpdateSafetyNetEvent : ViewEvent(), ContextExecutor, KoinComponent, Safety
} }
helperClass ?: throw Exception() helperClass ?: throw Exception()
val helper = helperClass.getMethod( val helper = helperClass
"get", .getMethod("get", Class::class.java, Context::class.java, Any::class.java)
Class::class.java, Context::class.java, Any::class.java
)
.invoke(null, SafetyNetHelper::class.java, context, this) as SafetyNetHelper .invoke(null, SafetyNetHelper::class.java, context, this) as SafetyNetHelper
if (helper.version < Const.SNET_EXT_VER) if (helper.version < Const.SNET_EXT_VER)
throw Exception() throw Exception()
helper.attest() helper.attest()
}.subscribeK(onError = { }.subscribeK(onError = onError)
if (die) {
rxBus.post(SafetyNetResult(-1))
} else {
Shell.sh("rm -rf " + EXT_APK.parent).exec()
EXT_APK.parentFile?.mkdir()
download(context, true)
}
})
} }
@Suppress("SameParameterValue") @Suppress("SameParameterValue")
private fun download(context: Context, askUser: Boolean) { private fun download(context: Context, askUser: Boolean) {
fun downloadInternal() = magiskRepo.fetchSafetynet() fun downloadInternal() = magiskRepo.fetchSafetynet()
.map { it.byteStream().writeTo(EXT_APK) } .map { it.byteStream().writeTo(apk) }
.subscribeK { invoke(context) } .subscribeK { attest(context) {
Timber.e(it)
rxBus.post(SafetyNetResult())
} }
if (!askUser) { if (!askUser) {
downloadInternal() downloadInternal()
@ -107,14 +110,14 @@ class UpdateSafetyNetEvent : ViewEvent(), ContextExecutor, KoinComponent, Safety
onClick { downloadInternal() } onClick { downloadInternal() }
} }
.applyButton(MagiskDialog.ButtonType.NEGATIVE) { .applyButton(MagiskDialog.ButtonType.NEGATIVE) {
titleRes = android.R.string.no titleRes = android.R.string.cancel
onClick { rxBus.post(SafetyNetResult(-2)) } onClick { rxBus.post(SafetyNetResult(dismiss = true)) }
} }
.reveal() .reveal()
} }
override fun onResponse(responseCode: Int) { override fun onResponse(response: JSONObject?) {
rxBus.post(SafetyNetResult(responseCode)) rxBus.post(SafetyNetResult(response))
} }
} }

View File

@ -3,7 +3,6 @@ package com.topjohnwu.magisk.ui.safetynet
import androidx.databinding.Bindable import androidx.databinding.Bindable
import com.topjohnwu.magisk.BR import com.topjohnwu.magisk.BR
import com.topjohnwu.magisk.R import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.core.utils.SafetyNetHelper
import com.topjohnwu.magisk.extensions.subscribeK import com.topjohnwu.magisk.extensions.subscribeK
import com.topjohnwu.magisk.model.events.SafetyNetResult import com.topjohnwu.magisk.model.events.SafetyNetResult
import com.topjohnwu.magisk.model.events.UpdateSafetyNetEvent import com.topjohnwu.magisk.model.events.UpdateSafetyNetEvent
@ -11,6 +10,7 @@ import com.topjohnwu.magisk.ui.base.BaseViewModel
import com.topjohnwu.magisk.ui.safetynet.SafetyNetState.* import com.topjohnwu.magisk.ui.safetynet.SafetyNetState.*
import com.topjohnwu.magisk.utils.KObservableField import com.topjohnwu.magisk.utils.KObservableField
import com.topjohnwu.magisk.utils.RxBus import com.topjohnwu.magisk.utils.RxBus
import org.json.JSONObject
enum class SafetyNetState { enum class SafetyNetState {
LOADING, PASS, FAILED, IDLE LOADING, PASS, FAILED, IDLE
@ -35,14 +35,12 @@ class SafetynetViewModel(
init { init {
rxBus.register<SafetyNetResult>() rxBus.register<SafetyNetResult>()
.subscribeK { resolveResponse(it.responseCode) } .subscribeK { resolveResponse(it) }
.add() .add()
if (safetyNetResult >= 0) { cachedResult?.also {
resolveResponse(safetyNetResult) resolveResponse(SafetyNetResult(it))
} else { } ?: attest()
attest()
}
} }
override fun notifyStateChanged() { override fun notifyStateChanged() {
@ -59,38 +57,40 @@ class SafetynetViewModel(
fun reset() = attest() fun reset() = attest()
private fun resolveResponse(response: Int) = when { private fun resolveResponse(response: SafetyNetResult) {
response and 0x0F == 0 -> { if (response.dismiss) {
val hasCtsPassed = response and SafetyNetHelper.CTS_PASS != 0 back()
val hasBasicIntegrityPassed = response and SafetyNetHelper.BASIC_PASS != 0 return
val result = hasCtsPassed && hasBasicIntegrityPassed }
safetyNetResult = response
ctsState.value = hasCtsPassed response.response?.apply {
basicIntegrityState.value = hasBasicIntegrityPassed runCatching {
val cts = getBoolean("ctsProfileMatch")
val basic = getBoolean("basicIntegrity")
val result = cts && basic
cachedResult = this
ctsState.value = cts
basicIntegrityState.value = basic
currentState = if (result) PASS else FAILED currentState = if (result) PASS else FAILED
safetyNetTitle.value = safetyNetTitle.value =
if (result) R.string.safetynet_attest_success if (result) R.string.safetynet_attest_success
else R.string.safetynet_attest_failure else R.string.safetynet_attest_failure
} }.onFailure {
response == -2 -> {
currentState = FAILED currentState = FAILED
ctsState.value = false ctsState.value = false
basicIntegrityState.value = false basicIntegrityState.value = false
back() safetyNetTitle.value = R.string.safetynet_res_invalid
} }
else -> { } ?: {
currentState = FAILED currentState = FAILED
ctsState.value = false ctsState.value = false
basicIntegrityState.value = false basicIntegrityState.value = false
safetyNetTitle.value = when (response) { safetyNetTitle.value = R.string.safetynet_api_error
SafetyNetHelper.RESPONSE_ERR -> R.string.safetynet_res_invalid }()
else -> R.string.safetynet_api_error
}
}
} }
companion object { companion object {
private var safetyNetResult = -1 private var cachedResult: JSONObject? = null
} }
} }

View File

@ -398,6 +398,22 @@ def build_stub(args):
header('* Building Magisk Manager stub') header('* Building Magisk Manager stub')
build_apk(args, 'stub') build_apk(args, 'stub')
# Bind mount snet package on top of the stub folder
def build_snet(args):
header('* Building snet extension')
proc = execv([gradlew, 'stub:assembleRelease'])
if proc.returncode != 0:
error('Build snet extention failed!')
source = op.join('stub', 'build', 'outputs', 'apk',
'release', 'stub-release.apk')
target = op.join(config['outdir'], 'snet.jar')
# Extract classes.dex
with zipfile.ZipFile(target, 'w', compression=zipfile.ZIP_DEFLATED, allowZip64=False) as zout:
with zipfile.ZipFile(source) as zin:
zout.writestr('classes.dex', zin.read('classes.dex'))
rm(source)
header('Output: ' + target)
def zip_main(args): def zip_main(args):
header('* Packing Flashable Zip') header('* Packing Flashable Zip')