V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
gam2046
V2EX  ›  Java

如何 Hook 方法 Class.class.getResourceAsStream?

  •  
  •   gam2046 · 2017-10-19 14:01:27 +08:00 · 1632 次点击
    这是一个创建于 2591 天前的主题,其中的信息可能已经有所发展或是发生改变。

    起因

    leader 提的诡异需求,有一批旧项目,需要在一个 android 项目中使用。 使用场景是在 android 设备中通过网络下载这些 jar (已使用 dx --dex 转换 bytecode ),加载并启动。 但是由于环境的变化导致原项目中一些资源请求不存在,需要重定向。

    经过

    目前的做法是,在 android 项目中使用自定义 ClassLoader 加载这些 jar,并启动。 目前碰到的问题是由于旧项目中大量的使用了 Class.class.getResourceAsSteamthis.getClass().getResourceAsStream方法。

    后者由于是通过我自定义的 ClassLoader 加载, 因此调用请求会被委托到我自定义 ClassLoader 的 getResourceAsStream 方法上。没有问题。

    问题就在于Class.class.getResourceAsSteam这样的调用,由于双亲委派,是由 Boot ClassLoader 加载。 因此我无法重定向这些资源请求。

    尝试过这些方案:

    • 自定义 ClassLoader,不执行双亲委派原则。但是此时我的自定义 ClassLoader 无法加载系统类的.jar 文件。提示 refusing reopen framework .....(filename)
    • 在自定义 ClassLoader 中,尝试通过双亲委派获得的 Class 对象,通过反射修改其 classloader 对象。[失败]原因不明,提示找不到 classloader 对应的字段。通过Field.getDeclaredFields()方法,也确实没有该属性

    求结果

    目前需要的效果是,可以通过各种奇技淫巧(不包括修改旧项目的源代码),重定向这些代码中的类似 String/Class/System.clsss.getResourceAsStream方法的调用。

    感谢各位能够提供解决方法或者思路的。

    第 1 条附言  ·  2017-10-20 16:36:16 +08:00

    @cppgohan @sutra 再次尝试过这些方法

    • 将java.boot.class.path的所有文件复制到程序内部
    • 自定义ClassLoader加载程序内部的java.boot.class.path副本,并丢弃双亲委派机制

    自定义类加载器逻辑:

    protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
        Class clazz = this.findLoadedClass(name);
            if (clazz == null) {
                    clazz = this.findClass(name);
                }
            }
        return clazz
    }
    

    但是依旧失败。在自定义ClassLoader下,确实实现了与宿主环境完全隔离,但是当我加载自己的类的出现了异常 dalvikvm: (LDemoActivity; had used a different Landroid/app/Activity; during pre-verification) 对于这个异常,我不是很理解,因为整个对象是在我自己的ClassLoader下加载,应该不会出现这个问题。

    这个异常说明在我的类加载器下,依旧返回了宿主环境的Activity类。

    苦于一直找不到原因,因此在自己的类加载器中做了区别对待:

            clazz = this.findLoadedClass(name);
    
            if (clazz == null) {
                if (name.startsWith("android.")) {
                    clazz = super.loadClass(name, resolve);
                    Log.i(TAG, "successfully parent load class " + name);
                } else {
                    clazz = this.findClass(name);
                    Log.i(TAG, "successfully self load class " + name);
                }
            }
    

    这样固然可以正常启动,但是由于Activity和其他类的ClassLoader不同,造成大量的类型不匹配,因此也无法跑起来。这就说明,在类加载的验证阶段,期待的是宿主环境下的Activity类,而实际上获得是隔离环境下的Activity类。

    那么现在的问题就是当我使用自己的类加载器,调用loadClass方法尝试加载DemoActivity时,为何在验证环境其父类会变成要求是宿主环境的Activity类?根据我的理解,此时自定义ClassLoader并未加载Activity类,上下文中也不存在Activity(由于ClassLoader产生的隔离环境),不应该抛出这样的异常。

    求大佬科普

    3 条回复    2017-10-23 15:45:32 +08:00
    cppgohan
        1
    cppgohan  
       2017-10-20 10:27:42 +08:00
    关注一下, 总觉得用反射修改 classloader 应该是可以一试的.. 不知道为什么会找不到
    sutra
        2
    sutra  
       2017-10-20 15:28:47 +08:00
    在 classpath 里给它那个 resource,用符号链接,不知道行不行。
    teemoer
        3
    teemoer  
       2017-10-23 15:45:32 +08:00
    xposed (手动滑稽)
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   5539 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 32ms · UTC 06:37 · PVG 14:37 · LAX 22:37 · JFK 01:37
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.