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"
// Versions
const val SNET_EXT_VER = 13
const val SNET_REVISION = "a6c47f86f10b310358afa9dbe837037dd5d561df"
const val SNET_EXT_VER = 14
const val SNET_REVISION = "5e28617412bdad2396eab87fa786094d8242e568"
const val BOOTCTL_REVISION = "a6c47f86f10b310358afa9dbe837037dd5d561df"
// Misc

View File

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

View File

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

View File

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