Add vpn hide xposed module

This commit is contained in:
世界
2026-01-07 20:31:27 +08:00
parent 8a8686b3df
commit cd83cbfe9e
152 changed files with 12994 additions and 2782 deletions

View File

@@ -1,34 +1,27 @@
package io.nekohasekai.sfa.vendor
import android.Manifest
import android.content.pm.PackageInfo
import android.content.pm.PackageManager
import android.os.Build
import android.util.Log
import io.nekohasekai.sfa.Application
import io.nekohasekai.sfa.BuildConfig
import io.nekohasekai.sfa.bg.RootClient
import io.nekohasekai.sfa.database.Settings
import io.nekohasekai.sfa.utils.HookStatusClient
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
object PackageQueryManager {
private const val TAG = "PackageQueryManager"
val needsPrivilegedQuery: Boolean by lazy {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
// Check if QUERY_ALL_PACKAGES is declared in manifest
val packageInfo = Application.packageManager.getPackageInfo(
Application.application.packageName,
PackageManager.GET_PERMISSIONS
)
val hasPermission = packageInfo.requestedPermissions?.contains(
Manifest.permission.QUERY_ALL_PACKAGES
) == true
!hasPermission
} else {
false
val strategy: PackageQueryStrategy
get() = when {
HookStatusClient.status.value?.active == true -> PackageQueryStrategy.ForcedRoot
BuildConfig.FLAVOR == "play" -> PackageQueryStrategy.UserSelected(queryMode.value)
else -> PackageQueryStrategy.Direct
}
}
val showModeSelector: Boolean
get() = strategy is PackageQueryStrategy.UserSelected
private val _queryMode = MutableStateFlow(Settings.perAppProxyPackageQueryMode)
val queryMode: StateFlow<String> = _queryMode
@@ -36,8 +29,8 @@ object PackageQueryManager {
val shizukuInstalled: StateFlow<Boolean> get() = ShizukuPackageManager.shizukuInstalled
val shizukuBinderReady: StateFlow<Boolean> get() = ShizukuPackageManager.binderReady
val shizukuPermissionGranted: StateFlow<Boolean> get() = ShizukuPackageManager.permissionGranted
val rootAvailable: StateFlow<Boolean?> get() = RootPackageManager.rootAvailable
val rootServiceConnected: StateFlow<Boolean> get() = RootPackageManager.serviceConnected
val rootAvailable: StateFlow<Boolean?> get() = RootClient.rootAvailable
val rootServiceConnected: StateFlow<Boolean> get() = RootClient.serviceConnected
fun isShizukuAvailable(): Boolean =
ShizukuPackageManager.isAvailable() && ShizukuPackageManager.checkPermission()
@@ -55,8 +48,12 @@ object PackageQueryManager {
ShizukuPackageManager.requestPermission()
}
fun refreshShizukuState() {
ShizukuPackageManager.refresh()
}
suspend fun checkRootAvailable(): Boolean {
return RootPackageManager.checkRootAvailable()
return RootClient.checkRootAvailable()
}
fun setQueryMode(mode: String) {
@@ -64,26 +61,14 @@ object PackageQueryManager {
}
suspend fun getInstalledPackages(flags: Int): List<PackageInfo> {
if (!needsPrivilegedQuery) {
return getPackagesViaPackageManager(flags)
}
val mode = _queryMode.value
if (mode == Settings.PACKAGE_QUERY_MODE_ROOT) {
if (rootAvailable.value != true) {
val isAvailable = RootPackageManager.checkRootAvailable()
if (!isAvailable) {
throw PrivilegedAccessRequiredException("ROOT access required")
}
return when (val s = strategy) {
is PackageQueryStrategy.ForcedRoot -> RootClient.getInstalledPackages(flags)
is PackageQueryStrategy.UserSelected -> when (s.mode) {
Settings.PACKAGE_QUERY_MODE_ROOT -> RootClient.getInstalledPackages(flags)
else -> ShizukuPackageManager.getInstalledPackages(flags)
}
return RootPackageManager.getInstalledPackages(flags)
is PackageQueryStrategy.Direct -> getPackagesViaPackageManager(flags)
}
if (!isShizukuAvailable()) {
throw PrivilegedAccessRequiredException("Shizuku access required")
}
return ShizukuPackageManager.getInstalledPackages(flags)
}
private fun getPackagesViaPackageManager(flags: Int): List<PackageInfo> {

View File

@@ -1,108 +0,0 @@
package io.nekohasekai.sfa.vendor
import android.content.ComponentName
import android.content.Intent
import android.content.ServiceConnection
import android.content.pm.PackageInfo
import android.os.IBinder
import com.topjohnwu.superuser.Shell
import com.topjohnwu.superuser.ipc.RootService
import io.nekohasekai.sfa.Application
import io.nekohasekai.sfa.BuildConfig
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.withContext
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException
object RootPackageManager {
init {
Shell.enableVerboseLogging = BuildConfig.DEBUG
Shell.setDefaultBuilder(
Shell.Builder.create()
.setFlags(Shell.FLAG_MOUNT_MASTER)
.setTimeout(10)
)
}
private val _rootAvailable = MutableStateFlow<Boolean?>(null)
val rootAvailable: StateFlow<Boolean?> = _rootAvailable
private val _serviceConnected = MutableStateFlow(false)
val serviceConnected: StateFlow<Boolean> = _serviceConnected
private var service: IRootPackageManager? = null
private var connection: ServiceConnection? = null
private val connectionMutex = Mutex()
suspend fun checkRootAvailable(): Boolean {
Shell.getCachedShell()?.close()
return suspendCancellableCoroutine { continuation ->
Shell.getShell { shell ->
val available = shell.isRoot
_rootAvailable.value = available
continuation.resume(available)
}
}
}
suspend fun bindService(): IRootPackageManager = connectionMutex.withLock {
service?.let { return it }
return withContext(Dispatchers.Main) {
suspendCancellableCoroutine { continuation ->
val conn = object : ServiceConnection {
override fun onServiceConnected(name: ComponentName?, binder: IBinder?) {
val svc = IRootPackageManager.Stub.asInterface(binder)
service = svc
connection = this
_serviceConnected.value = true
continuation.resume(svc)
}
override fun onServiceDisconnected(name: ComponentName?) {
service = null
connection = null
_serviceConnected.value = false
}
}
val intent = Intent(Application.application, RootPackageManagerService::class.java)
RootService.bind(intent, conn)
continuation.invokeOnCancellation {
RootService.unbind(conn)
}
}
}
}
fun unbindService() {
connection?.let {
RootService.unbind(it)
connection = null
service = null
_serviceConnected.value = false
}
}
private const val CHUNK_SIZE = 50
suspend fun getInstalledPackages(flags: Int): List<PackageInfo> {
val svc = bindService()
val result = mutableListOf<PackageInfo>()
var offset = 0
while (true) {
val chunk = svc.getInstalledPackages(flags, offset, CHUNK_SIZE)
if (chunk.isEmpty()) break
result.addAll(chunk)
offset += chunk.size
}
return result
}
}

View File

@@ -1,33 +0,0 @@
package io.nekohasekai.sfa.vendor
import android.content.Intent
import android.content.pm.PackageInfo
import android.content.pm.PackageManager
import android.os.Build
import android.os.IBinder
import com.topjohnwu.superuser.ipc.RootService
class RootPackageManagerService : RootService() {
private val binder = object : IRootPackageManager.Stub() {
override fun getInstalledPackages(flags: Int, offset: Int, limit: Int): List<PackageInfo> {
val allPackages = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
packageManager.getInstalledPackages(
PackageManager.PackageInfoFlags.of(flags.toLong())
)
} else {
@Suppress("DEPRECATION")
packageManager.getInstalledPackages(flags)
}
val endIndex = minOf(offset + limit, allPackages.size)
if (offset >= allPackages.size) {
return emptyList()
}
return allPackages.subList(offset, endIndex)
}
}
override fun onBind(intent: Intent): IBinder {
return binder
}
}

View File

@@ -1,25 +1,12 @@
package io.nekohasekai.sfa.vendor
import android.content.Intent
import android.content.IntentSender
import android.content.pm.IPackageInstaller
import android.content.pm.IPackageInstallerSession
import android.content.pm.PackageInstaller
import android.content.pm.PackageManager
import android.os.Build
import android.os.ParcelFileDescriptor
import android.os.Process
import io.nekohasekai.sfa.vendor.hidden.IPackageManager
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.lsposed.hiddenapibypass.HiddenApiBypass
import rikka.shizuku.Shizuku
import rikka.shizuku.ShizukuBinderWrapper
import rikka.shizuku.SystemServiceHelper
import java.io.File
import java.io.FileInputStream
import java.util.concurrent.CountDownLatch
import java.util.concurrent.TimeUnit
import android.content.IIntentSender
object ShizukuInstaller {
@@ -59,127 +46,11 @@ object ShizukuInstaller {
}
}
private fun getPackageInstaller(): IPackageInstaller {
val packageManagerBinder = SystemServiceHelper.getSystemService("package")
val packageManager = IPackageManager.Stub.asInterface(ShizukuBinderWrapper(packageManagerBinder))
val installerBinder = packageManager.packageInstaller.asBinder()
return IPackageInstaller.Stub.asInterface(ShizukuBinderWrapper(installerBinder))
}
private fun createPackageInstaller(
installer: IPackageInstaller,
installerPackageName: String,
installerAttributionTag: String?,
userId: Int
): PackageInstaller {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
return PackageInstaller::class.java
.getConstructor(
IPackageInstaller::class.java,
String::class.java,
String::class.java,
Int::class.javaPrimitiveType
)
.newInstance(installer, installerPackageName, installerAttributionTag, userId)
} else {
return PackageInstaller::class.java
.getConstructor(
IPackageInstaller::class.java,
String::class.java,
Int::class.javaPrimitiveType
)
.newInstance(installer, installerPackageName, userId)
}
}
private fun createSession(session: IPackageInstallerSession): PackageInstaller.Session {
return PackageInstaller.Session::class.java
.getConstructor(IPackageInstallerSession::class.java)
.newInstance(session)
}
private fun createIntentSender(onResult: (Intent) -> Unit): IntentSender {
val sender = object : IIntentSender.Stub() {
override fun send(
code: Int,
intent: Intent,
resolvedType: String?,
whitelistToken: android.os.IBinder?,
finishedReceiver: android.content.IIntentReceiver?,
requiredPermission: String?,
options: android.os.Bundle?
) {
onResult(intent)
}
}
return IntentSender::class.java
.getConstructor(IIntentSender::class.java)
.newInstance(sender)
}
suspend fun install(apkFile: File): Result<Unit> = withContext(Dispatchers.IO) {
try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
HiddenApiBypass.addHiddenApiExemptions("")
}
val iPackageInstaller = getPackageInstaller()
val isRoot = isRunningAsRoot()
val installerPackageName = if (isRoot) "io.nekohasekai.sfa" else "com.android.shell"
val installerAttributionTag: String? = null
val userId = if (isRoot) Process.myUserHandle().hashCode() else 0
val packageInstaller = createPackageInstaller(
iPackageInstaller,
installerPackageName,
installerAttributionTag,
userId
)
val params = PackageInstaller.SessionParams(PackageInstaller.SessionParams.MODE_FULL_INSTALL)
val sessionId = packageInstaller.createSession(params)
val iSession = IPackageInstallerSession.Stub.asInterface(
ShizukuBinderWrapper(iPackageInstaller.openSession(sessionId).asBinder())
)
val session = createSession(iSession)
try {
FileInputStream(apkFile).use { inputStream ->
session.openWrite("base.apk", 0, apkFile.length()).use { outputStream ->
inputStream.copyTo(outputStream)
session.fsync(outputStream)
}
}
val resultIntent = arrayOfNulls<Intent>(1)
val latch = CountDownLatch(1)
val intentSender = createIntentSender { intent ->
resultIntent[0] = intent
latch.countDown()
}
session.commit(intentSender)
latch.await(60, TimeUnit.SECONDS)
val intent = resultIntent[0]
?: return@withContext Result.failure(Exception("Installation timed out"))
val status = intent.getIntExtra(PackageInstaller.EXTRA_STATUS, PackageInstaller.STATUS_FAILURE)
val message = intent.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE)
if (status == PackageInstaller.STATUS_SUCCESS) {
Result.success(Unit)
} else {
Result.failure(Exception("Installation failed: $status - $message"))
}
} finally {
session.close()
}
} catch (e: Exception) {
Result.failure(e)
suspend fun install(apkFile: File) = withContext(Dispatchers.IO) {
val service = ShizukuPrivilegedServiceClient.getService()
val userId = if (isRunningAsRoot()) Process.myUserHandle().hashCode() else 0
ParcelFileDescriptor.open(apkFile, ParcelFileDescriptor.MODE_READ_ONLY).use { pfd ->
service.installPackage(pfd, apkFile.length(), userId)
}
}
}

View File

@@ -2,15 +2,13 @@ package io.nekohasekai.sfa.vendor
import android.content.pm.PackageInfo
import android.content.pm.PackageManager
import android.os.Build
import android.os.IBinder
import android.os.Process
import io.nekohasekai.sfa.Application
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import org.lsposed.hiddenapibypass.HiddenApiBypass
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import rikka.shizuku.Shizuku
import rikka.shizuku.ShizukuBinderWrapper
import rikka.shizuku.SystemServiceHelper
object ShizukuPackageManager {
@@ -33,19 +31,19 @@ object ShizukuPackageManager {
private val binderDeadListener = Shizuku.OnBinderDeadListener {
_binderReady.value = false
_permissionGranted.value = false
ShizukuPrivilegedServiceClient.reset()
}
private val permissionResultListener = Shizuku.OnRequestPermissionResultListener { _, grantResult ->
_permissionGranted.value = grantResult == PackageManager.PERMISSION_GRANTED
_binderReady.value = isAvailable()
}
fun registerListeners() {
Shizuku.addBinderReceivedListenerSticky(binderReceivedListener)
Shizuku.addBinderDeadListener(binderDeadListener)
Shizuku.addRequestPermissionResultListener(permissionResultListener)
_shizukuInstalled.value = isShizukuInstalled()
_binderReady.value = isAvailable()
_permissionGranted.value = checkPermission()
refresh()
}
fun isShizukuInstalled(): Boolean {
@@ -69,46 +67,17 @@ object ShizukuPackageManager {
fun requestPermission() = ShizukuInstaller.requestPermission()
fun getInstalledPackages(flags: Int): List<PackageInfo> {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
HiddenApiBypass.addHiddenApiExemptions("")
}
val packageManagerBinder = SystemServiceHelper.getSystemService("package")
val wrappedBinder = ShizukuBinderWrapper(packageManagerBinder)
val iPackageManagerClass = Class.forName("android.content.pm.IPackageManager")
val stubClass = Class.forName("android.content.pm.IPackageManager\$Stub")
val asInterfaceMethod = stubClass.getMethod("asInterface", IBinder::class.java)
val iPackageManager = asInterfaceMethod.invoke(null, wrappedBinder)
val userId = android.os.Process.myUserHandle().hashCode()
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)
} else {
val method = iPackageManagerClass.getMethod(
"getInstalledPackages",
Int::class.javaPrimitiveType,
Int::class.javaPrimitiveType
)
method.invoke(iPackageManager, flags, userId)
}
return extractPackageList(result)
fun refresh() {
_shizukuInstalled.value = isShizukuInstalled()
_binderReady.value = isAvailable()
_permissionGranted.value = checkPermission()
}
@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()
suspend fun getInstalledPackages(flags: Int): List<PackageInfo> = withContext(Dispatchers.IO) {
val service = ShizukuPrivilegedServiceClient.getService()
val userId = Process.myUserHandle().hashCode()
val slice = service.getInstalledPackages(flags, userId)
@Suppress("UNCHECKED_CAST")
slice.list as List<PackageInfo>
}
}

View File

@@ -0,0 +1,24 @@
package io.nekohasekai.sfa.vendor
import android.content.pm.PackageInfo
import android.os.ParcelFileDescriptor
import io.nekohasekai.sfa.bg.IShizukuService
import io.nekohasekai.sfa.bg.ParceledListSlice
import java.io.IOException
class ShizukuPrivilegedService : IShizukuService.Stub() {
override fun destroy() {
System.exit(0)
}
override fun getInstalledPackages(flags: Int, userId: Int): ParceledListSlice<PackageInfo> {
val allPackages = PrivilegedServiceUtils.getInstalledPackages(flags, userId)
return ParceledListSlice(allPackages)
}
override fun installPackage(apk: ParcelFileDescriptor?, size: Long, userId: Int) {
if (apk == null) throw IOException("APK file descriptor is null")
PrivilegedServiceUtils.installPackage(apk, size, userId)
}
}

View File

@@ -0,0 +1,86 @@
package io.nekohasekai.sfa.vendor
import android.content.ComponentName
import android.content.ServiceConnection
import android.os.IBinder
import io.nekohasekai.sfa.Application
import io.nekohasekai.sfa.BuildConfig
import io.nekohasekai.sfa.bg.IShizukuService
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.withContext
import rikka.shizuku.Shizuku
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException
object ShizukuPrivilegedServiceClient {
private val serviceMutex = Mutex()
private var service: IShizukuService? = null
private var connection: ServiceConnection? = null
private val args = Shizuku.UserServiceArgs(
ComponentName(Application.application, ShizukuPrivilegedService::class.java)
)
.tag("sfa-privileged")
.processNameSuffix("privileged")
.version(BuildConfig.VERSION_CODE)
.debuggable(BuildConfig.DEBUG)
suspend fun getService(): IShizukuService = serviceMutex.withLock {
service?.let { return it }
return withContext(Dispatchers.Main) {
suspendCancellableCoroutine { continuation ->
val conn = object : ServiceConnection {
override fun onServiceConnected(name: ComponentName?, binder: IBinder?) {
val svc = if (binder != null && binder.pingBinder()) {
IShizukuService.Stub.asInterface(binder)
} else {
null
}
if (svc == null) {
continuation.resumeWithException(IllegalStateException("Invalid Shizuku service binder"))
return
}
service = svc
connection = this
continuation.resume(svc)
}
override fun onServiceDisconnected(name: ComponentName?) {
service = null
connection = null
}
}
try {
Shizuku.bindUserService(args, conn)
} catch (e: Throwable) {
continuation.resumeWithException(e)
return@suspendCancellableCoroutine
}
continuation.invokeOnCancellation {
try {
Shizuku.unbindUserService(args, conn, false)
} catch (_: Throwable) {
// Ignore
}
}
}
}
}
fun reset() {
val conn = connection ?: return
service = null
connection = null
try {
Shizuku.unbindUserService(args, conn, false)
} catch (_: Throwable) {
// Ignore
}
}
}

View File

@@ -1,19 +0,0 @@
package io.nekohasekai.sfa.vendor.hidden;
import android.os.Binder;
import android.os.IBinder;
import android.os.IInterface;
import android.os.RemoteException;
import android.content.pm.IPackageInstaller;
public interface IPackageManager extends IInterface {
IPackageInstaller getPackageInstaller() throws RemoteException;
abstract class Stub extends Binder implements IPackageManager {
public static IPackageManager asInterface(IBinder binder) {
throw new UnsupportedOperationException();
}
}
}