Cache reflection results using lazy fields
This commit is contained in:
@@ -19,30 +19,67 @@ import java.io.IOException
|
||||
|
||||
object PrivilegedServiceUtils {
|
||||
|
||||
private val iPackageManagerStubClass by lazy { Class.forName("android.content.pm.IPackageManager\$Stub") }
|
||||
private val asInterfaceMethod by lazy { iPackageManagerStubClass.getMethod("asInterface", IBinder::class.java) }
|
||||
private val iPackageManagerClass by lazy { Class.forName("android.content.pm.IPackageManager") }
|
||||
|
||||
private val getInstalledPackagesMethodLong by lazy {
|
||||
iPackageManagerClass.getMethod(
|
||||
"getInstalledPackages",
|
||||
Long::class.javaPrimitiveType,
|
||||
Int::class.javaPrimitiveType
|
||||
)
|
||||
}
|
||||
private val getInstalledPackagesMethodInt by lazy {
|
||||
iPackageManagerClass.getMethod(
|
||||
"getInstalledPackages",
|
||||
Int::class.javaPrimitiveType,
|
||||
Int::class.javaPrimitiveType
|
||||
)
|
||||
}
|
||||
private val getPackageInstallerMethod by lazy { iPackageManagerClass.getMethod("getPackageInstaller") }
|
||||
|
||||
private val packageInstallerCtorS by lazy {
|
||||
PackageInstaller::class.java.getConstructor(
|
||||
IPackageInstaller::class.java,
|
||||
String::class.java,
|
||||
String::class.java,
|
||||
Int::class.javaPrimitiveType
|
||||
)
|
||||
}
|
||||
private val packageInstallerCtorPre by lazy {
|
||||
PackageInstaller::class.java.getConstructor(
|
||||
IPackageInstaller::class.java,
|
||||
String::class.java,
|
||||
Int::class.javaPrimitiveType
|
||||
)
|
||||
}
|
||||
private val sessionCtor by lazy {
|
||||
PackageInstaller.Session::class.java.getConstructor(IPackageInstallerSession::class.java)
|
||||
}
|
||||
private val intentSenderCtor by lazy {
|
||||
IntentSender::class.java.getConstructor(IIntentSender::class.java)
|
||||
}
|
||||
private val installFlagsField by lazy {
|
||||
PackageInstaller.SessionParams::class.java.getDeclaredField("installFlags").apply { isAccessible = true }
|
||||
}
|
||||
private val getListMethod by lazy {
|
||||
Class.forName("android.content.pm.ParceledListSlice").getMethod("getList")
|
||||
}
|
||||
|
||||
private fun getPackageManager(): Any {
|
||||
val binder = SystemServiceHelperCompat.getSystemService("package") ?: throw IllegalStateException("package service not available")
|
||||
val stubClass = Class.forName("android.content.pm.IPackageManager\$Stub")
|
||||
val asInterface = stubClass.getMethod("asInterface", IBinder::class.java)
|
||||
return asInterface.invoke(null, binder) ?: throw IllegalStateException("IPackageManager is null")
|
||||
val binder = SystemServiceHelperCompat.getSystemService("package")
|
||||
?: throw IllegalStateException("package service not available")
|
||||
return asInterfaceMethod.invoke(null, binder)
|
||||
?: throw IllegalStateException("IPackageManager is null")
|
||||
}
|
||||
|
||||
fun getInstalledPackages(flags: Int, userId: Int): List<PackageInfo> {
|
||||
val iPackageManager = getPackageManager()
|
||||
val iPackageManagerClass = Class.forName("android.content.pm.IPackageManager")
|
||||
val result = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
val method = iPackageManagerClass.getMethod(
|
||||
"getInstalledPackages",
|
||||
Long::class.javaPrimitiveType,
|
||||
Int::class.javaPrimitiveType,
|
||||
)
|
||||
method.invoke(iPackageManager, flags.toLong(), userId)
|
||||
getInstalledPackagesMethodLong.invoke(iPackageManager, flags.toLong(), userId)
|
||||
} else {
|
||||
val method = iPackageManagerClass.getMethod(
|
||||
"getInstalledPackages",
|
||||
Int::class.javaPrimitiveType,
|
||||
Int::class.javaPrimitiveType,
|
||||
)
|
||||
method.invoke(iPackageManager, flags, userId)
|
||||
getInstalledPackagesMethodInt.invoke(iPackageManager, flags, userId)
|
||||
}
|
||||
return extractPackageList(result)
|
||||
}
|
||||
@@ -62,9 +99,6 @@ object PrivilegedServiceUtils {
|
||||
|
||||
val params = PackageInstaller.SessionParams(PackageInstaller.SessionParams.MODE_FULL_INSTALL)
|
||||
params.setAppPackageName(BuildConfig.APPLICATION_ID)
|
||||
// Set INSTALL_REPLACE_EXISTING flag (value = 2)
|
||||
val installFlagsField = PackageInstaller.SessionParams::class.java.getDeclaredField("installFlags")
|
||||
installFlagsField.isAccessible = true
|
||||
installFlagsField.setInt(params, installFlagsField.getInt(params) or 2)
|
||||
val sessionId = packageInstaller.createSession(params)
|
||||
|
||||
@@ -106,9 +140,7 @@ object PrivilegedServiceUtils {
|
||||
|
||||
private fun getPackageInstaller(): IPackageInstaller {
|
||||
val iPackageManager = getPackageManager()
|
||||
val iPackageManagerClass = Class.forName("android.content.pm.IPackageManager")
|
||||
val method = iPackageManagerClass.getMethod("getPackageInstaller")
|
||||
val installer = method.invoke(iPackageManager) as IPackageInstaller
|
||||
val installer = getPackageInstallerMethod.invoke(iPackageManager) as IPackageInstaller
|
||||
return IPackageInstaller.Stub.asInterface(installer.asBinder())
|
||||
}
|
||||
|
||||
@@ -119,23 +151,14 @@ object PrivilegedServiceUtils {
|
||||
userId: Int,
|
||||
): PackageInstaller {
|
||||
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||
PackageInstaller::class.java.getConstructor(
|
||||
IPackageInstaller::class.java,
|
||||
String::class.java,
|
||||
String::class.java,
|
||||
Int::class.javaPrimitiveType,
|
||||
).newInstance(installer, installerPackageName, installerAttributionTag, userId)
|
||||
packageInstallerCtorS.newInstance(installer, installerPackageName, installerAttributionTag, userId)
|
||||
} else {
|
||||
PackageInstaller::class.java.getConstructor(
|
||||
IPackageInstaller::class.java,
|
||||
String::class.java,
|
||||
Int::class.javaPrimitiveType,
|
||||
).newInstance(installer, installerPackageName, userId)
|
||||
packageInstallerCtorPre.newInstance(installer, installerPackageName, userId)
|
||||
}
|
||||
}
|
||||
|
||||
private fun createSession(session: IPackageInstallerSession): PackageInstaller.Session {
|
||||
return PackageInstaller.Session::class.java.getConstructor(IPackageInstallerSession::class.java).newInstance(session)
|
||||
return sessionCtor.newInstance(session)
|
||||
}
|
||||
|
||||
private fun createIntentSender(onResult: (Intent) -> Unit): IntentSender {
|
||||
@@ -152,13 +175,12 @@ object PrivilegedServiceUtils {
|
||||
onResult(intent)
|
||||
}
|
||||
}
|
||||
return IntentSender::class.java.getConstructor(IIntentSender::class.java).newInstance(sender)
|
||||
return intentSenderCtor.newInstance(sender)
|
||||
}
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
private fun extractPackageList(parceledListSlice: Any?): List<PackageInfo> {
|
||||
if (parceledListSlice == null) return emptyList()
|
||||
val getListMethod = parceledListSlice.javaClass.getMethod("getList")
|
||||
val list = getListMethod.invoke(parceledListSlice) as? List<*>
|
||||
return list?.filterIsInstance<PackageInfo>() ?: emptyList()
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
package io.nekohasekai.sfa.xposed
|
||||
|
||||
object HookModuleVersion {
|
||||
const val CURRENT = 2
|
||||
const val CURRENT = 3
|
||||
}
|
||||
|
||||
@@ -3,8 +3,7 @@ package io.nekohasekai.sfa.xposed
|
||||
import android.content.pm.ApplicationInfo
|
||||
import android.content.pm.PackageManager
|
||||
import android.os.Process
|
||||
import de.robv.android.xposed.XposedHelpers
|
||||
import io.nekohasekai.sfa.BuildConfig
|
||||
import java.lang.reflect.Method
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
|
||||
object PrivilegeChecker {
|
||||
@@ -21,6 +20,13 @@ object PrivilegeChecker {
|
||||
private val exemptCache = ConcurrentHashMap<Int, Boolean>()
|
||||
private val privilegedCache = ConcurrentHashMap<Int, Boolean>()
|
||||
|
||||
private val appGlobalsClass by lazy { Class.forName("android.app.AppGlobals") }
|
||||
private val getPackageManagerMethod by lazy { appGlobalsClass.getMethod("getPackageManager") }
|
||||
private var getPackagesForUidMethod: Method? = null
|
||||
private var checkUidPermissionMethod: Method? = null
|
||||
private var getApplicationInfoMethodLong: Method? = null
|
||||
private var getApplicationInfoMethodInt: Method? = null
|
||||
|
||||
fun isPrivilegedUid(uid: Int): Boolean {
|
||||
if (uid < Process.FIRST_APPLICATION_UID) {
|
||||
return true
|
||||
@@ -44,9 +50,16 @@ object PrivilegeChecker {
|
||||
return true
|
||||
}
|
||||
}
|
||||
val checkMethod = checkUidPermissionMethod ?: run {
|
||||
pm.javaClass.getMethod(
|
||||
"checkUidPermission",
|
||||
String::class.java,
|
||||
Int::class.javaPrimitiveType
|
||||
).also { checkUidPermissionMethod = it }
|
||||
}
|
||||
for (permission in privilegedPermissions) {
|
||||
val result = try {
|
||||
XposedHelpers.callMethod(pm, "checkUidPermission", permission, uid) as? Int
|
||||
checkMethod.invoke(pm, permission, uid) as? Int
|
||||
} catch (_: Throwable) {
|
||||
null
|
||||
}
|
||||
@@ -83,7 +96,11 @@ object PrivilegeChecker {
|
||||
private fun getPackagesForUid(uid: Int): List<String> {
|
||||
val pm = getPackageManager() ?: return emptyList()
|
||||
return try {
|
||||
val method = pm.javaClass.getMethod("getPackagesForUid", Int::class.javaPrimitiveType)
|
||||
val method = getPackagesForUidMethod ?: run {
|
||||
pm.javaClass.getMethod("getPackagesForUid", Int::class.javaPrimitiveType).also {
|
||||
getPackagesForUidMethod = it
|
||||
}
|
||||
}
|
||||
val result = method.invoke(pm, uid)
|
||||
when (result) {
|
||||
is Array<*> -> result.filterIsInstance<String>()
|
||||
@@ -97,9 +114,7 @@ object PrivilegeChecker {
|
||||
|
||||
private fun getPackageManager(): Any? {
|
||||
return try {
|
||||
val appGlobals = Class.forName("android.app.AppGlobals")
|
||||
val method = appGlobals.getMethod("getPackageManager")
|
||||
method.invoke(null)
|
||||
getPackageManagerMethod.invoke(null)
|
||||
} catch (_: Throwable) {
|
||||
null
|
||||
}
|
||||
@@ -107,10 +122,26 @@ object PrivilegeChecker {
|
||||
|
||||
private fun getApplicationInfo(pm: Any, pkg: String, userId: Int): ApplicationInfo? {
|
||||
return try {
|
||||
XposedHelpers.callMethod(pm, "getApplicationInfo", pkg, 0, userId) as? ApplicationInfo
|
||||
val method = getApplicationInfoMethodInt ?: run {
|
||||
pm.javaClass.getMethod(
|
||||
"getApplicationInfo",
|
||||
String::class.java,
|
||||
Int::class.javaPrimitiveType,
|
||||
Int::class.javaPrimitiveType
|
||||
).also { getApplicationInfoMethodInt = it }
|
||||
}
|
||||
method.invoke(pm, pkg, 0, userId) as? ApplicationInfo
|
||||
} catch (_: Throwable) {
|
||||
try {
|
||||
XposedHelpers.callMethod(pm, "getApplicationInfo", pkg, 0L, userId) as? ApplicationInfo
|
||||
val method = getApplicationInfoMethodLong ?: run {
|
||||
pm.javaClass.getMethod(
|
||||
"getApplicationInfo",
|
||||
String::class.java,
|
||||
Long::class.javaPrimitiveType,
|
||||
Int::class.javaPrimitiveType
|
||||
).also { getApplicationInfoMethodLong = it }
|
||||
}
|
||||
method.invoke(pm, pkg, 0L, userId) as? ApplicationInfo
|
||||
} catch (_: Throwable) {
|
||||
null
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package io.nekohasekai.sfa.xposed
|
||||
|
||||
import java.io.File
|
||||
import java.lang.reflect.Method
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import io.nekohasekai.sfa.xposed.HookErrorStore
|
||||
|
||||
object PrivilegeSettingsStore {
|
||||
private const val SETTINGS_DIR = "/data/system/sing-box"
|
||||
@@ -17,6 +17,10 @@ object PrivilegeSettingsStore {
|
||||
private var interfacePrefix = "en"
|
||||
private val uidCache = ConcurrentHashMap<Int, Boolean>()
|
||||
|
||||
private val appGlobalsClass by lazy { Class.forName("android.app.AppGlobals") }
|
||||
private val getPackageManagerMethod by lazy { appGlobalsClass.getMethod("getPackageManager") }
|
||||
private var getPackagesForUidMethod: Method? = null
|
||||
|
||||
fun update(
|
||||
enabled: Boolean,
|
||||
packages: Set<String>,
|
||||
@@ -110,7 +114,11 @@ object PrivilegeSettingsStore {
|
||||
private fun getPackagesForUid(uid: Int): List<String> {
|
||||
val pm = getPackageManager() ?: return emptyList()
|
||||
return try {
|
||||
val method = pm.javaClass.getMethod("getPackagesForUid", Int::class.javaPrimitiveType)
|
||||
val method = getPackagesForUidMethod ?: run {
|
||||
pm.javaClass.getMethod("getPackagesForUid", Int::class.javaPrimitiveType).also {
|
||||
getPackagesForUidMethod = it
|
||||
}
|
||||
}
|
||||
val result = method.invoke(pm, uid)
|
||||
when (result) {
|
||||
is Array<*> -> result.filterIsInstance<String>()
|
||||
@@ -125,9 +133,7 @@ object PrivilegeSettingsStore {
|
||||
|
||||
private fun getPackageManager(): Any? {
|
||||
return try {
|
||||
val appGlobals = Class.forName("android.app.AppGlobals")
|
||||
val method = appGlobals.getMethod("getPackageManager")
|
||||
method.invoke(null)
|
||||
getPackageManagerMethod.invoke(null)
|
||||
} catch (e: Throwable) {
|
||||
HookErrorStore.e("PrivilegeSettingsStore", "getPackageManager failed", e)
|
||||
null
|
||||
|
||||
@@ -7,6 +7,7 @@ import android.content.pm.PackageManager
|
||||
import android.os.Binder
|
||||
import android.os.SystemClock
|
||||
import io.nekohasekai.sfa.BuildConfig
|
||||
import java.lang.reflect.Method
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
|
||||
object VpnAppStore {
|
||||
@@ -20,6 +21,16 @@ object VpnAppStore {
|
||||
private val uidVpnCache = ConcurrentHashMap<Int, CacheEntry<Boolean>>()
|
||||
private val uidPackagesCache = ConcurrentHashMap<Int, CacheEntry<List<String>>>()
|
||||
|
||||
private val appGlobalsClass by lazy { Class.forName("android.app.AppGlobals") }
|
||||
private val getPackageManagerMethod by lazy { appGlobalsClass.getMethod("getPackageManager") }
|
||||
|
||||
@Volatile
|
||||
private var pmClass: Class<*>? = null
|
||||
private var getPackagesForUidMethod: Method? = null
|
||||
private var getInstalledPackagesMethodLong: Method? = null
|
||||
private var getInstalledPackagesMethodInt: Method? = null
|
||||
private var getListMethod: Method? = null
|
||||
|
||||
fun isVpnUid(uid: Int): Boolean {
|
||||
val now = SystemClock.uptimeMillis()
|
||||
val cached = uidVpnCache[uid]
|
||||
@@ -57,7 +68,11 @@ object VpnAppStore {
|
||||
val result = binderLocalScope {
|
||||
val pm = getPackageManager() ?: return@binderLocalScope emptyList<String>()
|
||||
try {
|
||||
val method = pm.javaClass.getMethod("getPackagesForUid", Int::class.javaPrimitiveType)
|
||||
val method = getPackagesForUidMethod ?: run {
|
||||
pm.javaClass.getMethod("getPackagesForUid", Int::class.javaPrimitiveType).also {
|
||||
getPackagesForUidMethod = it
|
||||
}
|
||||
}
|
||||
when (val raw = method.invoke(pm, uid)) {
|
||||
is Array<*> -> raw.filterIsInstance<String>()
|
||||
is List<*> -> raw.filterIsInstance<String>()
|
||||
@@ -108,19 +123,23 @@ object VpnAppStore {
|
||||
|
||||
private fun getInstalledPackagesCompat(pm: Any, flags: Long, userId: Int): List<PackageInfo> {
|
||||
val result = try {
|
||||
val method = pm.javaClass.getMethod(
|
||||
"getInstalledPackages",
|
||||
Long::class.javaPrimitiveType,
|
||||
Int::class.javaPrimitiveType,
|
||||
)
|
||||
val method = getInstalledPackagesMethodLong ?: run {
|
||||
pm.javaClass.getMethod(
|
||||
"getInstalledPackages",
|
||||
Long::class.javaPrimitiveType,
|
||||
Int::class.javaPrimitiveType,
|
||||
).also { getInstalledPackagesMethodLong = it }
|
||||
}
|
||||
method.invoke(pm, flags, userId)
|
||||
} catch (_: Throwable) {
|
||||
try {
|
||||
val method = pm.javaClass.getMethod(
|
||||
"getInstalledPackages",
|
||||
Int::class.javaPrimitiveType,
|
||||
Int::class.javaPrimitiveType,
|
||||
)
|
||||
val method = getInstalledPackagesMethodInt ?: run {
|
||||
pm.javaClass.getMethod(
|
||||
"getInstalledPackages",
|
||||
Int::class.javaPrimitiveType,
|
||||
Int::class.javaPrimitiveType,
|
||||
).also { getInstalledPackagesMethodInt = it }
|
||||
}
|
||||
method.invoke(pm, flags.toInt(), userId)
|
||||
} catch (e: Throwable) {
|
||||
HookErrorStore.e("VpnAppStore", "getInstalledPackages failed", e)
|
||||
@@ -137,9 +156,7 @@ object VpnAppStore {
|
||||
|
||||
private fun getPackageManager(): Any? {
|
||||
return try {
|
||||
val appGlobals = Class.forName("android.app.AppGlobals")
|
||||
val method = appGlobals.getMethod("getPackageManager")
|
||||
method.invoke(null)
|
||||
getPackageManagerMethod.invoke(null)
|
||||
} catch (e: Throwable) {
|
||||
HookErrorStore.e("VpnAppStore", "getPackageManager failed", e)
|
||||
null
|
||||
@@ -161,7 +178,9 @@ object VpnAppStore {
|
||||
return raw.filterIsInstance<PackageInfo>()
|
||||
}
|
||||
return try {
|
||||
val method = raw.javaClass.getMethod("getList")
|
||||
val method = getListMethod ?: run {
|
||||
raw.javaClass.getMethod("getList").also { getListMethod = it }
|
||||
}
|
||||
val list = method.invoke(raw)
|
||||
if (list is List<*>) {
|
||||
list.filterIsInstance<PackageInfo>()
|
||||
|
||||
@@ -3,6 +3,7 @@ package io.nekohasekai.sfa.xposed
|
||||
import android.net.LinkProperties
|
||||
import android.net.NetworkCapabilities
|
||||
import android.net.NetworkInfo
|
||||
import android.net.ProxyInfo
|
||||
import android.os.Build
|
||||
import android.os.Parcel
|
||||
import android.os.Process
|
||||
@@ -14,6 +15,22 @@ object VpnSanitizer {
|
||||
"tun",
|
||||
)
|
||||
|
||||
private val getStackedLinksMethod by lazy {
|
||||
LinkProperties::class.java.getMethod("getStackedLinks")
|
||||
}
|
||||
private val removeStackedLinkMethod by lazy {
|
||||
LinkProperties::class.java.getMethod("removeStackedLink", String::class.java)
|
||||
}
|
||||
private val setHttpProxyMethod by lazy {
|
||||
LinkProperties::class.java.getMethod("setHttpProxy", ProxyInfo::class.java)
|
||||
}
|
||||
private val removeTransportTypeMethod by lazy {
|
||||
NetworkCapabilities::class.java.getMethod("removeTransportType", Int::class.javaPrimitiveType)
|
||||
}
|
||||
private val addCapabilityMethod by lazy {
|
||||
NetworkCapabilities::class.java.getMethod("addCapability", Int::class.javaPrimitiveType)
|
||||
}
|
||||
|
||||
fun shouldHide(uid: Int): Boolean {
|
||||
if (!PrivilegeSettingsStore.shouldHideUid(uid)) {
|
||||
return false
|
||||
@@ -47,13 +64,13 @@ object VpnSanitizer {
|
||||
lp.setInterfaceName(null)
|
||||
}
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
val stacked = XposedHelpers.callMethod(lp, "getStackedLinks") as? List<LinkProperties>
|
||||
val stacked = getStackedLinksMethod.invoke(lp) as? List<LinkProperties>
|
||||
if (!stacked.isNullOrEmpty()) {
|
||||
for (link in stacked) {
|
||||
clearHttpProxy(link)
|
||||
val name = link.interfaceName
|
||||
if (isVpnInterface(name)) {
|
||||
XposedHelpers.callMethod(lp, "removeStackedLink", name)
|
||||
val iface = link.interfaceName
|
||||
if (iface != null && isVpnInterface(iface)) {
|
||||
removeStackedLinkMethod.invoke(lp, iface)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -65,8 +82,7 @@ object VpnSanitizer {
|
||||
return true
|
||||
}
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
val stacked = XposedHelpers.callMethod(lp, "getStackedLinks") as? List<LinkProperties>
|
||||
?: return false
|
||||
val stacked = getStackedLinksMethod.invoke(lp) as? List<LinkProperties> ?: return false
|
||||
return stacked.any { isVpnInterface(it.interfaceName) }
|
||||
}
|
||||
|
||||
@@ -77,8 +93,8 @@ object VpnSanitizer {
|
||||
}
|
||||
|
||||
private fun sanitizeTransport(caps: NetworkCapabilities) {
|
||||
XposedHelpers.callMethod(caps, "removeTransportType", NetworkCapabilities.TRANSPORT_VPN)
|
||||
XposedHelpers.callMethod(caps, "addCapability", NetworkCapabilities.NET_CAPABILITY_NOT_VPN)
|
||||
removeTransportTypeMethod.invoke(caps, NetworkCapabilities.TRANSPORT_VPN)
|
||||
addCapabilityMethod.invoke(caps, NetworkCapabilities.NET_CAPABILITY_NOT_VPN)
|
||||
}
|
||||
|
||||
private fun clearUnderlyingNetworks(caps: NetworkCapabilities) {
|
||||
@@ -110,7 +126,7 @@ object VpnSanitizer {
|
||||
}
|
||||
|
||||
private fun clearHttpProxy(lp: LinkProperties) {
|
||||
XposedHelpers.callMethod(lp, "setHttpProxy", null)
|
||||
setHttpProxyMethod.invoke(lp, null as ProxyInfo?)
|
||||
}
|
||||
|
||||
fun cloneLinkProperties(source: LinkProperties): LinkProperties {
|
||||
|
||||
@@ -15,6 +15,10 @@ class XposedInit(
|
||||
param: XposedModuleInterface.ModuleLoadedParam,
|
||||
) : XposedModule(base, param) {
|
||||
|
||||
private val activityThreadClass by lazy { Class.forName("android.app.ActivityThread") }
|
||||
private val currentActivityThreadMethod by lazy { activityThreadClass.getMethod("currentActivityThread") }
|
||||
private val getSystemContextMethod by lazy { activityThreadClass.getMethod("getSystemContext") }
|
||||
|
||||
override fun onSystemServerLoaded(param: XposedModuleInterface.SystemServerLoadedParam) {
|
||||
val systemContext = resolveSystemContext()
|
||||
HookErrorStore.i("XposedInit", "handleSystemServerLoaded")
|
||||
@@ -45,9 +49,8 @@ class XposedInit(
|
||||
|
||||
private fun resolveSystemContext(): Context? {
|
||||
return try {
|
||||
val activityThread = Class.forName("android.app.ActivityThread")
|
||||
val currentThread = activityThread.getMethod("currentActivityThread").invoke(null)
|
||||
activityThread.getMethod("getSystemContext").invoke(currentThread) as? Context
|
||||
val currentThread = currentActivityThreadMethod.invoke(null)
|
||||
getSystemContextMethod.invoke(currentThread) as? Context
|
||||
} catch (e: Throwable) {
|
||||
HookErrorStore.e("XposedInit", "resolveSystemContext failed", e)
|
||||
null
|
||||
|
||||
@@ -14,6 +14,7 @@ import io.nekohasekai.sfa.xposed.VpnAppStore
|
||||
import io.nekohasekai.sfa.xposed.VpnSanitizer
|
||||
import io.nekohasekai.sfa.xposed.hooks.SafeMethodHook
|
||||
import io.nekohasekai.sfa.xposed.hooks.XHook
|
||||
import java.lang.reflect.Method
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
|
||||
@@ -33,6 +34,17 @@ class ConnectivityServiceHookHelper(private val classLoader: ClassLoader) : XHoo
|
||||
lateinit var cls: Class<*>
|
||||
private set
|
||||
|
||||
private val serviceManagerClass by lazy { Class.forName("android.os.ServiceManager") }
|
||||
private val checkServiceMethod by lazy { serviceManagerClass.getMethod("checkService", String::class.java) }
|
||||
|
||||
private var getVpnForUidMethod: Method? = null
|
||||
private lateinit var getVpnUnderlyingNetworksMethod: Method
|
||||
private lateinit var getNetworkAgentInfoForNetworkMethod: Method
|
||||
private var getFilteredNetworkInfoMethod: Method? = null
|
||||
private lateinit var getDefaultNetworkMethod: Method
|
||||
private lateinit var isVPNMethod: Method
|
||||
private var networkMethod: Method? = null
|
||||
|
||||
override fun injectHook() {
|
||||
val foundClass = findConnectivityServiceClass()
|
||||
if (foundClass != null) {
|
||||
@@ -50,6 +62,7 @@ class ConnectivityServiceHookHelper(private val classLoader: ClassLoader) : XHoo
|
||||
}
|
||||
this.cls = cls
|
||||
connectivityClassLoader = cls.classLoader ?: classLoader
|
||||
initMethodCache()
|
||||
HookErrorStore.i(
|
||||
SOURCE,
|
||||
"Installing ConnectivityService hooks ($source) cls=${cls.name} loader=${connectivityClassLoader.javaClass.name}",
|
||||
@@ -72,6 +85,38 @@ class ConnectivityServiceHookHelper(private val classLoader: ClassLoader) : XHoo
|
||||
HookErrorStore.i(SOURCE, "Hooked ConnectivityService ($source) cls=${cls.name}")
|
||||
}
|
||||
|
||||
private fun initMethodCache() {
|
||||
val intType = Int::class.javaPrimitiveType!!
|
||||
val booleanType = Boolean::class.javaPrimitiveType!!
|
||||
val naiClass = resolveConnectivityModuleClass("NetworkAgentInfo", "connectivity")
|
||||
if (sdkInt >= 31) {
|
||||
getVpnForUidMethod = findDeclaredMethod(cls, "getVpnForUid", intType)
|
||||
if (getVpnForUidMethod == null) {
|
||||
HookErrorStore.w(SOURCE, "getVpnForUid not found; falling back to underlying networks")
|
||||
}
|
||||
}
|
||||
getVpnUnderlyingNetworksMethod = requireDeclaredMethod(cls, "getVpnUnderlyingNetworks", intType)
|
||||
getNetworkAgentInfoForNetworkMethod = requireDeclaredMethod(cls, "getNetworkAgentInfoForNetwork", Network::class.java)
|
||||
if (sdkInt >= 31) {
|
||||
getFilteredNetworkInfoMethod = findDeclaredMethod(
|
||||
cls,
|
||||
"getFilteredNetworkInfo",
|
||||
naiClass,
|
||||
intType,
|
||||
booleanType
|
||||
)
|
||||
if (getFilteredNetworkInfoMethod == null) {
|
||||
HookErrorStore.w(SOURCE, "getFilteredNetworkInfo not found; network info sanitization disabled")
|
||||
}
|
||||
}
|
||||
getDefaultNetworkMethod = requireDeclaredMethod(cls, "getDefaultNetwork")
|
||||
isVPNMethod = requireDeclaredMethod(naiClass, "isVPN")
|
||||
networkMethod = findDeclaredMethod(naiClass, "network")
|
||||
if (networkMethod == null) {
|
||||
HookErrorStore.w(SOURCE, "NetworkAgentInfo.network() not found; falling back to field access")
|
||||
}
|
||||
}
|
||||
|
||||
// region Service Discovery
|
||||
|
||||
private fun findConnectivityServiceClass(): Class<*>? {
|
||||
@@ -229,9 +274,7 @@ class ConnectivityServiceHookHelper(private val classLoader: ClassLoader) : XHoo
|
||||
private fun tryHookFromServiceManager() {
|
||||
if (hooked.get()) return
|
||||
val binder = try {
|
||||
val serviceManager = Class.forName("android.os.ServiceManager")
|
||||
val checkService = serviceManager.getMethod("checkService", String::class.java)
|
||||
checkService.invoke(null, Context.CONNECTIVITY_SERVICE) as? IBinder
|
||||
checkServiceMethod.invoke(null, Context.CONNECTIVITY_SERVICE) as? IBinder
|
||||
} catch (_: Throwable) {
|
||||
null
|
||||
}
|
||||
@@ -365,56 +408,88 @@ class ConnectivityServiceHookHelper(private val classLoader: ClassLoader) : XHoo
|
||||
|
||||
fun hasVpnForUid(connectivityService: Any, uid: Int): Boolean {
|
||||
if (sdkInt >= 31) {
|
||||
return XposedHelpers.callMethod(connectivityService, "getVpnForUid", uid) != null
|
||||
val vpnForUidMethod = getVpnForUidMethod
|
||||
if (vpnForUidMethod != null) {
|
||||
return vpnForUidMethod.invoke(connectivityService, uid) != null
|
||||
}
|
||||
}
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
val networks = XposedHelpers.callMethod(connectivityService, "getVpnUnderlyingNetworks", uid)
|
||||
as? Array<Network>
|
||||
val networks = getVpnUnderlyingNetworksMethod.invoke(connectivityService, uid) as? Array<Network>
|
||||
return networks != null && networks.isNotEmpty()
|
||||
}
|
||||
|
||||
fun isVpnNetwork(connectivityService: Any, network: Network): Boolean {
|
||||
val nai = XposedHelpers.callMethod(connectivityService, "getNetworkAgentInfoForNetwork", network)
|
||||
?: return false
|
||||
val nai = getNetworkAgentInfoForNetworkMethod.invoke(connectivityService, network) ?: return false
|
||||
return isVpnNai(nai)
|
||||
}
|
||||
|
||||
fun isVpnNai(nai: Any): Boolean {
|
||||
return XposedHelpers.callMethod(nai, "isVPN") as Boolean
|
||||
return isVPNMethod.invoke(nai) as Boolean
|
||||
}
|
||||
|
||||
fun getUnderlyingNetwork(connectivityService: Any, uid: Int): Network? {
|
||||
val nai = getUnderlyingNai(connectivityService, uid) ?: return null
|
||||
return XposedHelpers.callMethod(nai, "network") as Network?
|
||||
val method = networkMethod
|
||||
return if (method != null) {
|
||||
method.invoke(nai) as Network?
|
||||
} else {
|
||||
XposedHelpers.getObjectField(nai, "network") as? Network
|
||||
}
|
||||
}
|
||||
|
||||
fun getUnderlyingLinkProperties(connectivityService: Any, uid: Int): LinkProperties? {
|
||||
val nai = getUnderlyingNai(connectivityService, uid) ?: return null
|
||||
val lp = XposedHelpers.getObjectField(nai, "linkProperties") as LinkProperties?
|
||||
?: return null
|
||||
val lp = XposedHelpers.getObjectField(nai, "linkProperties") as LinkProperties? ?: return null
|
||||
return VpnSanitizer.cloneLinkProperties(lp)
|
||||
}
|
||||
|
||||
fun getUnderlyingNetworkInfo(connectivityService: Any, uid: Int): NetworkInfo? {
|
||||
val nai = getUnderlyingNai(connectivityService, uid) ?: return null
|
||||
return XposedHelpers.callMethod(connectivityService, "getFilteredNetworkInfo", nai, uid, false)
|
||||
as NetworkInfo?
|
||||
val method = getFilteredNetworkInfoMethod
|
||||
if (method != null) {
|
||||
return method.invoke(connectivityService, nai, uid, false) as NetworkInfo?
|
||||
}
|
||||
return XposedHelpers.getObjectField(nai, "networkInfo") as? NetworkInfo
|
||||
}
|
||||
|
||||
fun getUnderlyingNai(connectivityService: Any, uid: Int): Any? {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
val networks = XposedHelpers.callMethod(connectivityService, "getVpnUnderlyingNetworks", uid)
|
||||
as? Array<Network>
|
||||
val networks = getVpnUnderlyingNetworksMethod.invoke(connectivityService, uid) as? Array<Network>
|
||||
if (networks != null && networks.isNotEmpty()) {
|
||||
return XposedHelpers.callMethod(connectivityService, "getNetworkAgentInfoForNetwork", networks[0])
|
||||
return getNetworkAgentInfoForNetworkMethod.invoke(connectivityService, networks[0])
|
||||
}
|
||||
val defaultNai = XposedHelpers.callMethod(connectivityService, "getDefaultNetwork")
|
||||
val defaultNai = getDefaultNetworkMethod.invoke(connectivityService)
|
||||
if (defaultNai != null && !isVpnNai(defaultNai)) {
|
||||
return defaultNai
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
private fun findDeclaredMethod(
|
||||
target: Class<*>,
|
||||
name: String,
|
||||
vararg parameterTypes: Class<*>,
|
||||
): Method? {
|
||||
var current: Class<*>? = target
|
||||
while (current != null) {
|
||||
try {
|
||||
return current.getDeclaredMethod(name, *parameterTypes).apply { isAccessible = true }
|
||||
} catch (_: NoSuchMethodException) {
|
||||
current = current.superclass
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
private fun requireDeclaredMethod(
|
||||
target: Class<*>,
|
||||
name: String,
|
||||
vararg parameterTypes: Class<*>,
|
||||
): Method {
|
||||
return findDeclaredMethod(target, name, *parameterTypes)
|
||||
?: throw NoSuchMethodException("${target.name}#$name")
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves a class from the Connectivity module, handling APEX package rewriting.
|
||||
*
|
||||
|
||||
@@ -17,6 +17,25 @@ class HookNetworkCapabilitiesWriteToParcel : XHook {
|
||||
private const val SOURCE = "HookNCWriteToParcel"
|
||||
}
|
||||
|
||||
private val copyCtor by lazy {
|
||||
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.S) {
|
||||
NetworkCapabilities::class.java.getDeclaredConstructor(
|
||||
NetworkCapabilities::class.java,
|
||||
Long::class.javaPrimitiveType
|
||||
).apply { isAccessible = true }
|
||||
} else {
|
||||
NetworkCapabilities::class.java.getDeclaredConstructor(
|
||||
NetworkCapabilities::class.java
|
||||
).apply { isAccessible = true }
|
||||
}
|
||||
}
|
||||
private val removeTransportTypeMethod by lazy {
|
||||
NetworkCapabilities::class.java.getMethod("removeTransportType", Int::class.javaPrimitiveType)
|
||||
}
|
||||
private val addCapabilityMethod by lazy {
|
||||
NetworkCapabilities::class.java.getMethod("addCapability", Int::class.javaPrimitiveType)
|
||||
}
|
||||
|
||||
private val inWrite = ThreadLocal.withInitial { false }
|
||||
|
||||
override fun injectHook() {
|
||||
@@ -58,22 +77,15 @@ class HookNetworkCapabilitiesWriteToParcel : XHook {
|
||||
|
||||
private fun copyNetworkCapabilities(caps: NetworkCapabilities): NetworkCapabilities {
|
||||
return if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.S) {
|
||||
val ctor = NetworkCapabilities::class.java.getDeclaredConstructor(
|
||||
NetworkCapabilities::class.java,
|
||||
Long::class.javaPrimitiveType!!
|
||||
)
|
||||
ctor.isAccessible = true
|
||||
ctor.newInstance(caps, 0L)
|
||||
copyCtor.newInstance(caps, 0L) as NetworkCapabilities
|
||||
} else {
|
||||
val ctor = NetworkCapabilities::class.java.getDeclaredConstructor(NetworkCapabilities::class.java)
|
||||
ctor.isAccessible = true
|
||||
ctor.newInstance(caps)
|
||||
copyCtor.newInstance(caps) as NetworkCapabilities
|
||||
}
|
||||
}
|
||||
|
||||
private fun sanitizeNetworkCapabilities(caps: NetworkCapabilities) {
|
||||
XposedHelpers.callMethod(caps, "removeTransportType", NetworkCapabilities.TRANSPORT_VPN)
|
||||
XposedHelpers.callMethod(caps, "addCapability", NetworkCapabilities.NET_CAPABILITY_NOT_VPN)
|
||||
removeTransportTypeMethod.invoke(caps, NetworkCapabilities.TRANSPORT_VPN)
|
||||
addCapabilityMethod.invoke(caps, NetworkCapabilities.NET_CAPABILITY_NOT_VPN)
|
||||
clearVpnTransportInfo(caps)
|
||||
clearUnderlyingNetworks(caps)
|
||||
clearOwnerUid(caps)
|
||||
|
||||
@@ -31,6 +31,11 @@ class HookNetworkInterfaceGetName(private val classLoader: ClassLoader) : XHook
|
||||
private const val IFF_UP = 0x1
|
||||
}
|
||||
|
||||
private val netlinkSocketAddressClass by lazy { Class.forName("android.system.NetlinkSocketAddress") }
|
||||
private val netlinkSocketAddressCtor by lazy {
|
||||
netlinkSocketAddressClass.getConstructor(Int::class.javaPrimitiveType, Int::class.javaPrimitiveType)
|
||||
}
|
||||
|
||||
private val seq = AtomicInteger(1)
|
||||
|
||||
override fun injectHook() {
|
||||
@@ -223,9 +228,7 @@ class HookNetworkInterfaceGetName(private val classLoader: ClassLoader) : XHook
|
||||
}
|
||||
|
||||
private fun buildNetlinkAddress(): SocketAddress {
|
||||
val cls = Class.forName("android.system.NetlinkSocketAddress")
|
||||
val ctor = cls.getConstructor(Int::class.javaPrimitiveType, Int::class.javaPrimitiveType)
|
||||
return ctor.newInstance(0, 0) as SocketAddress
|
||||
return netlinkSocketAddressCtor.newInstance(0, 0) as SocketAddress
|
||||
}
|
||||
|
||||
private fun buildLinkMessage(
|
||||
|
||||
@@ -13,6 +13,7 @@ import io.nekohasekai.sfa.xposed.PrivilegeSettingsStore
|
||||
import io.nekohasekai.sfa.xposed.VpnAppStore
|
||||
import io.nekohasekai.sfa.xposed.hooks.SafeMethodHook
|
||||
import io.nekohasekai.sfa.xposed.hooks.XHook
|
||||
import java.lang.reflect.Method
|
||||
|
||||
class HookPackageManagerGetInstalledPackages(private val classLoader: ClassLoader) : XHook {
|
||||
private companion object {
|
||||
@@ -20,6 +21,10 @@ class HookPackageManagerGetInstalledPackages(private val classLoader: ClassLoade
|
||||
private const val PER_USER_RANGE = 100000
|
||||
}
|
||||
|
||||
@Volatile
|
||||
private var lastPackageNameClass: Class<*>? = null
|
||||
private var getPackageNameMethod: Method? = null
|
||||
|
||||
override fun injectHook() {
|
||||
val hooked = ArrayList<String>()
|
||||
val sdk = Build.VERSION.SDK_INT
|
||||
@@ -238,7 +243,15 @@ class HookPackageManagerGetInstalledPackages(private val classLoader: ClassLoade
|
||||
private fun extractPackageName(arg: Any?): String? {
|
||||
if (arg == null) return null
|
||||
try {
|
||||
val method = arg.javaClass.getMethod("getPackageName")
|
||||
val argClass = arg.javaClass
|
||||
val method = if (lastPackageNameClass == argClass && getPackageNameMethod != null) {
|
||||
getPackageNameMethod!!
|
||||
} else {
|
||||
argClass.getMethod("getPackageName").also {
|
||||
lastPackageNameClass = argClass
|
||||
getPackageNameMethod = it
|
||||
}
|
||||
}
|
||||
val result = method.invoke(arg) as String?
|
||||
if (!result.isNullOrEmpty()) {
|
||||
return result
|
||||
|
||||
Reference in New Issue
Block a user