RikkaApps / Shizuku-API

The API and the developer guide for Shizuku and Sui.
MIT License
1.02k stars 245 forks source link

`UnsatisfiedLinkError: No implementation found` when attempting to use native code loaded via a DexClassLoader #13

Closed KieronQuinn closed 2 years ago

KieronQuinn commented 2 years ago

I'm attempting to load an APK using DexClassLoader within the Shizuku service. It all seems to work well and I can call methods (and have also got LSPlant working via Aliucord's Hook library too), but when trying to call JNI from within the APK, it seems to stop working.

My intentions are to try to run the recognition code for Ambient Music within Shell, which in theory would work - I've already got a PoC working as a system app (no Xposed or model loading needed as LSPlant can stop the framework calls and instead trigger it manually), but the ultimate goal is to get it working with just Shizuku. Shell has access to record audio using the hotword mic, but simply recording the audio and sending it back to a client isn't sufficient as there's also something in the recognition (not this crash) that requires being a system app.

Minimal setup:

val context = Class.forName("android.app.ActivityThread")
    .getMethod("currentApplication")
    .invoke(null) as Context

val apkPath = File("/data/local/tmp", "aiai.apk").absolutePath
val sourceDir = context.packageManager.getApplicationInfo(BuildConfig.APPLICATION_ID, 0).sourceDir
var librarySearchPath: String = sourceDir.toString() + "!/lib/" + Build.SUPPORTED_ABIS[0]
val systemLibrarySearchPath = System.getProperty("java.library.path")
if (!TextUtils.isEmpty(systemLibrarySearchPath)) {
    librarySearchPath += File.pathSeparatorChar.toString() + systemLibrarySearchPath
}
val classLoader = DexClassLoader(apkPath, ".", librarySearchPath, ClassLoader.getSystemClassLoader())

System.loadLibrary("sense")

val lfr = classLoader.loadClass("lfr").getDeclaredConstructor().apply {
    isAccessible = true
}.newInstance()

val nnfpRecognizer = classLoader.loadClass("com.google.audio.ambientmusic.NnfpRecognizer")
    .getConstructor(lfr::class.java, Array<String>::class.java, Array<String>::class.java)
    .newInstance(lfr, emptyArray<String>(), emptyArray<String>())
04-22 01:46:26.851 22051 22074 E com.kieronquinn.app.shizukutest:shizuku: No implementation found for long com.google.audio.ambientmusic.NnfpRecognizer.init(java.lang.String[], java.lang.String[], byte[]) (tried Java_com_google_audio_ambientmusic_NnfpRecognizer_init and Java_com_google_audio_ambientmusic_NnfpRecognizer_init___3Ljava_lang_String_2_3Ljava_lang_String_2_3B)
04-22 01:46:26.851 22051 22074 E JavaBinder: *** Uncaught remote exception!  (Exceptions are not yet supported across processes.)
04-22 01:46:26.851 22051 22074 E JavaBinder: java.lang.reflect.InvocationTargetException
04-22 01:46:26.851 22051 22074 E JavaBinder:    at java.lang.reflect.Constructor.newInstance0(Native Method)
04-22 01:46:26.851 22051 22074 E JavaBinder:    at java.lang.reflect.Constructor.newInstance(Constructor.java:343)
04-22 01:46:26.851 22051 22074 E JavaBinder:    at com.kieronquinn.app.shizukutest.ShizukuService.start(ShizukuService.kt:77)
04-22 01:46:26.851 22051 22074 E JavaBinder:    at com.kieronquinn.app.shizukutest.IShizukuService$Stub.onTransact(IShizukuService.java:59)
04-22 01:46:26.851 22051 22074 E JavaBinder:    at android.os.Binder.execTransactInternal(Binder.java:1179)
04-22 01:46:26.851 22051 22074 E JavaBinder:    at android.os.Binder.execTransact(Binder.java:1143)
04-22 01:46:26.851 22051 22074 E JavaBinder: Caused by: java.lang.UnsatisfiedLinkError: No implementation found for long com.google.audio.ambientmusic.NnfpRecognizer.init(java.lang.String[], java.lang.String[], byte[]) (tried Java_com_google_audio_ambientmusic_NnfpRecognizer_init and Java_com_google_audio_ambientmusic_NnfpRecognizer_init___3Ljava_lang_String_2_3Ljava_lang_String_2_3B)
04-22 01:46:26.851 22051 22074 E JavaBinder:    at com.google.audio.ambientmusic.NnfpRecognizer.init(Native Method)
04-22 01:46:26.851 22051 22074 E JavaBinder:    at com.google.audio.ambientmusic.NnfpRecognizer.<init>(PG:2)
04-22 01:46:26.851 22051 22074 E JavaBinder:    ... 6 more

I've made the code identical to how Shizuku starts the Java code from a shell service, other than loading the source directory using PackageManager.

I've also tried:

None of these make any difference, the exception is identical.

I've also verified that the function in the library is exported:

image

There's no other relevant logcat output either.

What's really strange is that using Hook does work, even though it also uses native libraries in exactly the same way. Those are loaded fine, and methods are hooked without a problem.

Obviously this is quite a specific edge case, so if there's something that prevents this working in the system that simply isn't surfacing in the logcat, I'll happily move on and make the mod require a Magisk module to make it a system app.

Thanks!

RikkaW commented 2 years ago

Load so directly from apk ("!") requires the so file "uncompressed and aligned". I found that android:extractNativeLibs of com.google.android.as is not set (default value is false), so maybe so files are not "uncompressed and aligned". You can try extract the so from the apk to somewhere like /data/local/tmp and set librarySearchPath to that location.

KieronQuinn commented 2 years ago

I had tried that with System.load() and the extracted .so (which was in /data/local/tmp, also tried from the sdcard as Shell has access), but it didn't seem to work either, very odd. What did work was copying the native methods into Java classes in my own code, then running it from that - with the same System.loadLibrary("sense") call.

Unfortunately for me while this solved the "no such implementation" problem, it didn't actually work with the specific call I wanted as it seems to require some setup that's deep within obfuscated code. Instead I've managed to get it to recognise without being a system app, and can feed it audio recorded as Shell via Shizuku, so it will work without root!

Thanks for your help, I'll close this as it's very unlikely anyone else will hit it anyway.