Skip to content

Commit 6a5152a

Browse files
committed
Fix ConnectivityService discovery on APEX-rewritten devices (HyperOS3/Android 16)
1 parent 38bdda4 commit 6a5152a

1 file changed

Lines changed: 73 additions & 8 deletions

File tree

app/src/main/java/io/nekohasekai/sfa/xposed/hooks/hidevpn/ConnectivityServiceHookHelper.kt

Lines changed: 73 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import android.net.Network
66
import android.net.NetworkInfo
77
import android.os.Build
88
import android.os.IBinder
9+
import android.os.Parcel
910
import de.robv.android.xposed.XC_MethodHook
1011
import de.robv.android.xposed.XposedHelpers
1112
import io.nekohasekai.sfa.xposed.HookErrorStore
@@ -26,6 +27,7 @@ class ConnectivityServiceHookHelper(private val classLoader: ClassLoader) : XHoo
2627
private val hooked = AtomicBoolean(false)
2728
private val initializerHooked = AtomicBoolean(false)
2829
private var classLoadUnhook: XC_MethodHook.Unhook? = null
30+
private var onTransactUnhook: XC_MethodHook.Unhook? = null
2931
private val serviceManagerHooked = AtomicBoolean(false)
3032
private var connectivityClassLoader: ClassLoader = classLoader
3133
private val skipLogKeys = ConcurrentHashMap<String, Boolean>()
@@ -53,6 +55,7 @@ class ConnectivityServiceHookHelper(private val classLoader: ClassLoader) : XHoo
5355
}
5456
hookConnectivityServiceInitializer()
5557
hookClassLoaderFallback()
58+
hookOnTransactFallback()
5659
tryHookFromServiceManager()
5760
}
5861

@@ -148,12 +151,39 @@ class ConnectivityServiceHookHelper(private val classLoader: ClassLoader) : XHoo
148151
}
149152
}
150153
HookErrorStore.i(SOURCE, "ConnectivityService class not found in known classloaders")
154+
155+
val initializerNames = listOf(
156+
"com.android.server.ConnectivityServiceInitializer",
157+
"com.android.server.ConnectivityServiceInitializerB",
158+
)
159+
for (name in initializerNames) {
160+
for (loader in loaders) {
161+
val initCls = try {
162+
if (loader != null) Class.forName(name, false, loader) else Class.forName(name)
163+
} catch (_: Throwable) {
164+
null
165+
} ?: continue
166+
try {
167+
val field = initCls.getDeclaredField("mConnectivity")
168+
val fieldType = field.type
169+
if (fieldType.name.endsWith(".ConnectivityService")) {
170+
HookErrorStore.i(
171+
SOURCE,
172+
"ConnectivityService class found via $name.mConnectivity: ${fieldType.name}",
173+
)
174+
return fieldType
175+
}
176+
} catch (_: Throwable) {
177+
}
178+
}
179+
}
180+
151181
return null
152182
}
153183

154184
private fun hookConnectivityServiceInitializer() {
155-
if (sdkInt < 31 || sdkInt >= 33) {
156-
HookErrorStore.d(SOURCE, "Skip ConnectivityServiceInitializer: sdk=$sdkInt (only exists in API 31-32)")
185+
if (sdkInt < 31) {
186+
HookErrorStore.d(SOURCE, "Skip ConnectivityServiceInitializer: sdk=$sdkInt (requires API 31+)")
157187
return
158188
}
159189
val candidates = listOf(
@@ -238,20 +268,20 @@ class ConnectivityServiceHookHelper(private val classLoader: ClassLoader) : XHoo
238268
classLoadUnhook = null
239269
return
240270
}
241-
when (name) {
242-
"com.android.server.ConnectivityService" -> {
271+
when {
272+
name == "com.android.server.ConnectivityService" ||
273+
name.endsWith(".com.android.server.ConnectivityService") -> {
243274
val cls = param.result as? Class<*> ?: return
244275
HookErrorStore.i(
245276
SOURCE,
246-
"ConnectivityService loaded via ${param.thisObject.javaClass.name}",
277+
"ConnectivityService loaded via ${param.thisObject.javaClass.name}: $name",
247278
)
248279
installHooks(cls, "loadClass")
249280
classLoadUnhook?.unhook()
250281
classLoadUnhook = null
251282
}
252-
"com.android.server.ConnectivityServiceInitializer",
253-
"com.android.server.ConnectivityServiceInitializerB",
254-
-> {
283+
name == "com.android.server.ConnectivityServiceInitializer" ||
284+
name == "com.android.server.ConnectivityServiceInitializerB" -> {
255285
if (sdkInt < 31) return
256286
if (initializerHooked.get()) return
257287
val cls = param.result as? Class<*> ?: return
@@ -322,6 +352,41 @@ class ConnectivityServiceHookHelper(private val classLoader: ClassLoader) : XHoo
322352
}
323353
}
324354

355+
private fun hookOnTransactFallback() {
356+
if (onTransactUnhook != null) return
357+
try {
358+
val stub = XposedHelpers.findClass("android.net.IConnectivityManager\$Stub", classLoader)
359+
onTransactUnhook = XposedHelpers.findAndHookMethod(
360+
stub,
361+
"onTransact",
362+
Int::class.javaPrimitiveType,
363+
Parcel::class.java,
364+
Parcel::class.java,
365+
Int::class.javaPrimitiveType,
366+
object : SafeMethodHook(SOURCE) {
367+
override fun beforeHook(param: MethodHookParam) {
368+
if (hooked.get()) {
369+
onTransactUnhook?.unhook()
370+
onTransactUnhook = null
371+
return
372+
}
373+
val serviceClass = param.thisObject.javaClass
374+
HookErrorStore.i(
375+
SOURCE,
376+
"ConnectivityService discovered via onTransact: ${serviceClass.name}",
377+
)
378+
installHooks(serviceClass, "onTransact")
379+
onTransactUnhook?.unhook()
380+
onTransactUnhook = null
381+
}
382+
},
383+
)
384+
HookErrorStore.i(SOURCE, "Hooked IConnectivityManager.Stub.onTransact for discovery")
385+
} catch (e: Throwable) {
386+
HookErrorStore.w(SOURCE, "Hook onTransact fallback failed: ${e.message}", e)
387+
}
388+
}
389+
325390
private fun hookConnectivityServiceInitializerClass(cls: Class<*>) {
326391
if (sdkInt < 31) return
327392
if (initializerHooked.get()) return

0 commit comments

Comments
 (0)