frida / frida-java-bridge

Java runtime interop from Frida
323 stars 119 forks source link

Java.use() failed for class with return type or parameter type not in class path #131

Open gebing opened 5 years ago

gebing commented 5 years ago

i found Java.use() will throw ClassNotFoundException when some of the class's constructors or methods have undefined return type or parameter type.

The sample class is like below:

class NewApiClass {
  public NewApiClass() {
  }
  public NewApiClass(java.time.Duration duration) {
  }

  public int getDuration() {
  }

  public Java.time.Duration getDuration() {
  }
}

And the exception is like below:

08-06 16:05:48.828 25429-25431/? A/art: art/runtime/java_vm_ext.cc:410] JNI DETECTED ERROR IN APPLICATION: JNI GetArrayLength called with pending exception java.lang.NoClassDefFoundError: java.time.Duration
08-06 16:05:48.828 25429-25431/? A/art: art/runtime/java_vm_ext.cc:410]   at java.lang.Class libcore.reflect.InternalNames.getClass(java.lang.ClassLoader, java.lang.String) (InternalNames.java:55)
08-06 16:05:48.828 25429-25431/? A/art: art/runtime/java_vm_ext.cc:410]   at java.lang.Class java.lang.Class.getDexCacheType(com.android.dex.Dex, int) (Class.java:476)
08-06 16:05:48.828 25429-25431/? A/art: art/runtime/java_vm_ext.cc:410]   at java.lang.Class java.lang.reflect.Method.getReturnType() (Method.java:183)
08-06 16:05:48.828 25429-25431/? A/art: art/runtime/java_vm_ext.cc:410]   at java.lang.reflect.Method[] java.lang.Class.getDeclaredMethods() (Class.java:672)
08-06 16:05:48.828 25429-25431/? A/art: art/runtime/java_vm_ext.cc:410] Caused by: java.lang.ClassNotFoundException: Didn't find class "java.time.Duration" on path: DexPathList[[dex file "/data/local/tmp/frida-java-tests/tests.dex"],nativeLibraryDirectories=[/vendor/lib64, /system/lib64]]
08-06 16:05:48.828 25429-25431/? A/art: art/runtime/java_vm_ext.cc:410]   at java.lang.Class dalvik.system.BaseDexClassLoader.findClass(java.lang.String) (BaseDexClassLoader.java:56)
08-06 16:05:48.828 25429-25431/? A/art: art/runtime/java_vm_ext.cc:410]   at java.lang.Class java.lang.ClassLoader.loadClass(java.lang.String, boolean) (ClassLoader.java:511)
08-06 16:05:48.828 25429-25431/? A/art: art/runtime/java_vm_ext.cc:410]   at java.lang.Class java.lang.ClassLoader.loadClass(java.lang.String) (ClassLoader.java:469)
08-06 16:05:48.828 25429-25431/? A/art: art/runtime/java_vm_ext.cc:410]   at java.lang.Class libcore.reflect.InternalNames.getClass(java.lang.ClassLoader, java.lang.String) (InternalNames.java:53)
08-06 16:05:48.828 25429-25431/? A/art: art/runtime/java_vm_ext.cc:410]   at java.lang.Class java.lang.Class.getDexCacheType(com.android.dex.Dex, int) (Class.java:476)
08-06 16:05:48.828 25429-25431/? A/art: art/runtime/java_vm_ext.cc:410]   at java.lang.Class java.lang.reflect.Method.getReturnType() (Method.java:183)
08-06 16:05:48.828 25429-25431/? A/art: art/runtime/java_vm_ext.cc:410]   at java.lang.reflect.Method[] java.lang.Class.getDeclaredMethods() (Class.java:672)

This is very common in Android because of the difference of SDK version, and can work fine under different SDK version, but these classes can not manipulate under frida.

So my suggestion is:

  1. Add an option parameter for Java.use to supply optional ignored classes array, such like: Java.use('re.frida.NewApiClass', ['java.time.Duration'])

  2. In implement of factory.use(), when calling getDeclaredConstructors/getDeclaredMethods, hook the loadClass function call and return java.lang.Object when loadClass throws ClassNotFoundException and class name is in ignore classes array.

@oleavr If you agree this suggestion, i can make a pull request includes implementation and unit test.

rotk19 commented 4 years ago

Can you share the patch? I'm facing same problem. Thanks!

gebing commented 4 years ago

Sorry for reply later, because i am on vacation. Below is may patch for this problem and also including use user-defined class loader:

function javaUse(className, classLoader, ignoreClasses) {
  ignoreClasses = Array.isArray(ignoreClasses) ? ignoreClasses : [].slice.call(arguments, 2);
  const classObject = Java.use("java.lang.Object").class;
  const classOverload = !ignoreClasses.length ? null : (classLoader ? classLoader : Java.use("java.lang.ClassLoader")).loadClass.overload('java.lang.String');
  const oldImpl = classOverload ? classOverload.implementation : null;
  const oldLoader = classLoader ? Java.classFactory.loader : null;
  if (classLoader) Java.classFactory.loader = classLoader || null;
  if (classOverload) classOverload.implementation = function (name) {
    try {
      return classOverload.call(this, name);
    } catch (error) {
      if (error.message.indexOf("java.lang.ClassNotFoundException") < 0 || ignoreClasses.indexOf(name) < 0) throw error;
      return classObject;
    }
  };
  try {
    return Java.use(className);
  } catch (error) {
    throw error;
  } finally {
    if (classOverload) classOverload.implementation = oldImpl;
    if (classLoader) Java.classFactory.loader = oldLoader;
  }
};

And sample usage is: javaUse('re.frida.NewApiClass', null, ['java.time.Duration'])

rotk19 commented 4 years ago

Enjoy Your Vacation ⭐️ Thank you so much 💯

My old code: Java.use("com.xxx.app.Push.MyPushReceiver"); It throw exception: Abort message: 'java_vm_ext.cc:542] JNI DETECTED ERROR IN APPLICATION: JNI GetArrayLength called with pending exception java.lang.NoClassDefFoundError: Failed resolution of: Lcom/xiaomi/mipush/sdk/MiPushMessage;'

My new code: javaUse("com.xxx.app.Push.MyPushReceiver", null, ['com.xiaomi.mipush.sdk.MiPushMessage']); It now throw new Exception: returned class java.lang.Object instead of com.xiaomi.mipush.sdk.MiPushMessage

Class com.xiaomi.mipush.sdk.MiPushMessage not packed in the apk. The class com.xiaomi.mipush.sdk.MiPushMessage showed as it being imported in the java decompiled code of com.xxx.app.Push.MyPushReceiver, but in the smali code it only appear few times, which is not always executed during normal program's flow. Somehow Frida pickup the not found class when I do Java.use.

Now it look like a casting problem. Hopefully you can take a look. I have included partial crash log here (with adb logcat -b crash):

*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
Revision: '0'
ABI: 'arm'
pid: 28237, tid: 28237, name: .cloud.app  >>> com.xxx.cloud.app <<<
signal 6 (SIGABRT), code -6 (SI_TKILL), fault addr --------
Abort message: 'java_vm_ext.cc:542] JNI DETECTED ERROR IN APPLICATION: JNI GetArrayLength called with pending exception java.lang.NoClassDefFoundError: Initiating class loader of type dalvik.system.PathClassLoader returned class java.lang.Object instead of com.xiaomi.mipush.sdk.MiPushMessage.'
    r0  00000000  r1  00006e4d  r2  00000006  r3  00000008
    r4  00006e4d  r5  00006e4d  r6  ff95830c  r7  0000010c
    r8  00000002  r9  e5090740  r10 e5e23d2c  r11 e4bac90e
    ip  e84223d8  sp  ff9582f8  lr  e8391621  pc  e8388e5e

backtrace:
    #00 pc 0001ce5e  /system/lib/libc.so (offset 0x1c000) (abort+58)
    #01 pc 00000307  <anonymous:e598f000>
EOF
***
rotk19 commented 4 years ago

So I fixed my problem 🍳 It work great for me. Instead of

if (error.message.indexOf("java.lang.ClassNotFoundException") < 0 || ignoreClasses.indexOf(name) < 0)
     throw error;
return classObject;

I used Java.registerClass to automatic register a new class.

if (error.message.indexOf("java.lang.ClassNotFoundException") < 0 || ignoreClasses.indexOf(name) < 0)
    throw error;
return Java.registerClass({name: name}).class;
gebing commented 4 years ago

I am glad that you solved your problem. Obviously my patch is not very suitable for your case, but your modification is a more general solution.