diff --git a/app/src/main/java/hat/holo/token/LoaderActivity.kt b/app/src/main/java/hat/holo/token/LoaderActivity.kt index bb21d33..2567eeb 100644 --- a/app/src/main/java/hat/holo/token/LoaderActivity.kt +++ b/app/src/main/java/hat/holo/token/LoaderActivity.kt @@ -24,6 +24,7 @@ class LoaderActivity : Activity() { val zActivity = loader.loadClass("hat.holo.token.TokenActivity") val actIntent = Intent(this, zActivity) actIntent.putExtra("accountInfo", intent.getSerializableExtra("accountInfo")) + actIntent.putExtra("deviceInfo", intent.getSerializableExtra("deviceInfo")) startActivityForResult(actIntent, 1234) } diff --git a/app/src/main/java/hat/holo/token/ModuleMain.kt b/app/src/main/java/hat/holo/token/ModuleMain.kt index 2d44c1a..8587f30 100644 --- a/app/src/main/java/hat/holo/token/ModuleMain.kt +++ b/app/src/main/java/hat/holo/token/ModuleMain.kt @@ -46,9 +46,10 @@ class ModuleMain : IXposedHookLoadPackage, IXposedHookZygoteInit { val c0 = loadClass("com.mihoyo.hyperion.app.HyperionApplicationHelper") findAndHookMethod(c0, "initOnMainProcess", Application::class.java, object : XC_MethodHook() { override fun afterHookedMethod(p: MethodHookParam) { - AppUtils.init(classLoader) - AccountManager.init(classLoader) val app = p.args[0] as Application + AppUtils.init(classLoader) + DeviceManager.init(classLoader, app.applicationContext) + AccountManager.init(classLoader) appendToClassPath(app.applicationContext) } }) @@ -70,12 +71,14 @@ class ModuleMain : IXposedHookLoadPackage, IXposedHookZygoteInit { if (isPatch) { val intent = Intent(ctx, LoaderActivity::class.java) intent.putExtra("accountInfo", AccountManager.accountInfo) + intent.putExtra("deviceInfo", DeviceManager.deviceInfo) intent.putExtra("dexPath", modulePath) ctx.startActivity(intent) } else { val intent = Intent() intent.setClassName("hat.holo.token", "hat.holo.token.TokenActivity") intent.putExtra("accountInfo", AccountManager.accountInfo) + intent.putExtra("deviceInfo", DeviceManager.deviceInfo) ctx.startActivity(intent) } } else { diff --git a/app/src/main/java/hat/holo/token/TokenActivity.kt b/app/src/main/java/hat/holo/token/TokenActivity.kt index 6905dfc..569670b 100644 --- a/app/src/main/java/hat/holo/token/TokenActivity.kt +++ b/app/src/main/java/hat/holo/token/TokenActivity.kt @@ -31,6 +31,7 @@ import androidx.constraintlayout.compose.ConstraintLayout import androidx.core.content.getSystemService import com.google.accompanist.systemuicontroller.rememberSystemUiController import hat.holo.token.models.AccountInfo +import hat.holo.token.models.DeviceInfo import kotlinx.coroutines.delay val textColor = Color(0xFF424242) @@ -44,6 +45,7 @@ class TokenActivity : ComponentActivity() { super.onCreate(savedInstanceState) requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT setTheme(androidx.appcompat.R.style.Theme_AppCompat_Light_NoActionBar) + val deviceInfo = intent.getSerializableExtra("deviceInfo") as DeviceInfo val accountInfo = intent.getSerializableExtra("accountInfo") as AccountInfo setContent { rememberSystemUiController().setStatusBarColor(Color.White) @@ -53,7 +55,7 @@ class TokenActivity : ComponentActivity() { ), content = { Surface { - Content(accountInfo) + Content(deviceInfo, accountInfo) } } ) @@ -70,7 +72,7 @@ private fun TokenActivity.showDialog(msg: String) = runOnUiThread { } @Composable -private fun TokenActivity.Content(accountInfo: AccountInfo) = Column( +private fun TokenActivity.Content(deviceInfo: DeviceInfo, accountInfo: AccountInfo) = Column( modifier = Modifier.fillMaxSize() ) { TopAppBar() @@ -79,6 +81,14 @@ private fun TokenActivity.Content(accountInfo: AccountInfo) = Column( ) { var grantSToken by remember { mutableStateOf(false) } var showDoneIcon by remember { mutableStateOf(false) } + CustomCheckBox( + checked = true, + onCheckedChange = {}, + name = "DeviceInfo", + permissions = buildAnnotatedString { + appendLine("绕过米游社风控所需的设备ID与指纹") + } + ) CustomCheckBox( checked = true, onCheckedChange = {}, @@ -116,6 +126,8 @@ private fun TokenActivity.Content(accountInfo: AccountInfo) = Column( put("mid", accountInfo.mid) put("stoken", accountInfo.sToken) } + put("device_id", deviceInfo.id) + put("device_fp", deviceInfo.fingerprint) }.map { (k, v) -> "$k=$v" }.joinToString(";") val clip = ClipData.newPlainText(null, authStr) getSystemService()!!.setPrimaryClip(clip) @@ -133,7 +145,7 @@ private fun TokenActivity.Content(accountInfo: AccountInfo) = Column( colorFilter = ColorFilter.tint(Color(0xFF4CAF50)) ) } - Text("复制登录信息") + Text("复制到剪贴板") } } LaunchedEffect(showDoneIcon) { diff --git a/app/src/main/java/hat/holo/token/models/DeviceInfo.kt b/app/src/main/java/hat/holo/token/models/DeviceInfo.kt new file mode 100644 index 0000000..ffc8bf5 --- /dev/null +++ b/app/src/main/java/hat/holo/token/models/DeviceInfo.kt @@ -0,0 +1,6 @@ +package hat.holo.token.models + +data class DeviceInfo( + val id: String, + val fingerprint: String, +) : java.io.Serializable diff --git a/app/src/main/java/hat/holo/token/utils/AppUtils.kt b/app/src/main/java/hat/holo/token/utils/AppUtils.kt index 700f62b..92a4a6a 100644 --- a/app/src/main/java/hat/holo/token/utils/AppUtils.kt +++ b/app/src/main/java/hat/holo/token/utils/AppUtils.kt @@ -1,19 +1,13 @@ package hat.holo.token.utils -import java.lang.reflect.Method - object AppUtils { - private lateinit var instance: Any - private val methodTable = arrayListOf() + private lateinit var inst: Any fun init(cl: ClassLoader) { - val clz = cl.loadClass("com.mihoyo.hyperion.utils.AppUtils") - instance = clz.getDeclaredField("INSTANCE").get(null)!! - methodTable.add(clz.getDeclaredMethod("showToast", String::class.java)) + inst = cl.loadClass("com.mihoyo.hyperion.utils.AppUtils").visitStaticField("INSTANCE") } - fun showToast(str: String) { - methodTable[0].invoke(instance, str) - } + fun showToast(str: String) = inst.invokeMethod("showToast", str) + } diff --git a/app/src/main/java/hat/holo/token/utils/DeviceManager.kt b/app/src/main/java/hat/holo/token/utils/DeviceManager.kt new file mode 100644 index 0000000..c0d03fc --- /dev/null +++ b/app/src/main/java/hat/holo/token/utils/DeviceManager.kt @@ -0,0 +1,31 @@ +package hat.holo.token.utils + +import android.annotation.SuppressLint +import android.content.Context +import hat.holo.token.models.DeviceInfo + +@Suppress("MemberVisibilityCanBePrivate") +@SuppressLint("StaticFieldLeak") +object DeviceManager { + + private lateinit var context: Context + private lateinit var riskManager: Any + private lateinit var deviceUtils: Any + + fun init(cl: ClassLoader, ctx: Context) { + context = ctx + riskManager = cl + .loadClass("com.mihoyo.platform.account.sdk.risk.RiskManager") + .visitStaticField("INSTANCE") + deviceUtils = cl + .loadClass("com.mihoyo.platform.account.sdk.utils.DeviceUtils") + .visitStaticField("INSTANCE") + } + + val deviceFp get() = riskManager.invokeMethod("getDeviceFp") + + val deviceId get() = deviceUtils.invokeMethod("getDeviceID", context) + + val deviceInfo get() = DeviceInfo(deviceId, deviceFp) + +} diff --git a/app/src/main/java/hat/holo/token/utils/ReflectUtils.kt b/app/src/main/java/hat/holo/token/utils/ReflectUtils.kt index cc13034..20cab7e 100644 --- a/app/src/main/java/hat/holo/token/utils/ReflectUtils.kt +++ b/app/src/main/java/hat/holo/token/utils/ReflectUtils.kt @@ -6,11 +6,22 @@ fun T.setAccess() = apply { isAccessible = true } -@Suppress("UNCHECKED_CAST") -fun Any.invokeMethod(methodName: String, vararg args: Any?) : R { +inline fun Any.invokeMethod(methodName: String) : R { val method = this.javaClass.getDeclaredMethod(methodName) method.isAccessible = true - return method.invoke(this, *args) as R + return method.invoke(this) as R +} + +inline fun Any.invokeMethod(methodName: String, a1: T) : R { + val method = this.javaClass.getDeclaredMethod(methodName, T::class.java) + method.isAccessible = true + return method.invoke(this, a1) as R +} + +inline fun Any.invokeMethod(methodName: String, a1: T1, a2: T2) : R { + val method = this.javaClass.getDeclaredMethod(methodName, T1::class.java, T2::class.java) + method.isAccessible = true + return method.invoke(this, a1, a2) as R } inline fun Any.visitField(fieldName: String) : T { @@ -19,6 +30,12 @@ inline fun Any.visitField(fieldName: String) : T { return field.get(this) as T } +inline fun Class<*>.visitStaticField(fieldName: String) : T { + val field = getDeclaredField(fieldName) + field.isAccessible = true + return field.get(null) as T +} + inline fun T.visitParentField(fieldName: String) : R { val field = T::class.java.getDeclaredField(fieldName) field.isAccessible = true