1

I know it's not a good demand. But I need to do that now.

I need to replace the method of Class.getResourceAsStream(String)

But ClassLoader.preDefineClass(String , ProtectionDomain) checks whether the class name of the full name starts with java., and if so, throws an exception and is not allowed to load.

Is there a way to bypass runtime security checks instead of compile time?

Update

The real requirement is that many older projects need to migrate to new environments, because some of these projects may be out of date and some of them may not be able to find the source code. I am required to simulate the old environment in a new environment so that these ancient projects can continue to function.

Upadte (GhostCat)

I tried the way which you given.

ClassLoader Class

public class ClassLoaderTest extends ClassLoader {
    @Override
    public InputStream getResourceAsStream(String name) {
        System.out.println("getResourceAsStream " + name);
        return new ByteArrayInputStream(new byte[]{1, 3, 5, 7, 9});
    }
    @Override
    public Class<?> loadClass(String name) throws ClassNotFoundException {
        System.out.println("Load class " + name);
        String fileName = name.substring(name.lastIndexOf(".") + 1) + ".class";
        InputStream is = getClass().getResourceAsStream(fileName);

        if (is == null) {
            return super.loadClass(name);
        }

        byte[] b = new byte[0];
        try {
            b = new byte[is.available()];
            is.read(b);
        } catch (IOException e) {
            e.printStackTrace();
        }

        return defineClass(name, b, 0, b.length);
    }
}

Invoke Class

public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, InstantiationException {
    ClassLoaderTest classLoaderTest = new ClassLoaderTest();
    Class clazz = Class.forName("com.Client", true, classLoaderTest);
    Object client = clazz.newInstance();
    Method method = clazz.getDeclaredMethod("method");
    method.invoke(client);
}

Client Class

public class Client {
    public void method() {
        System.out.println(this.getClass().getResourceAsStream("aaa"));
        System.out.println(Class.class.getResourceAsStream("bbb"));
        System.out.println("".getClass().getResourceAsStream("ccc"));
    }
}

It really works with this.getClass().getResourceAsStream(String), but Class.class.getResourceAsStream(String) / "".getClass().getResourceAsStream(String) . Because I cannot load class using my custom ClassLoader which name start with java.

forDream
  • 386
  • 1
  • 6
  • 17
  • This is disabled due to security considerations. you can extend the class needed and call your own class if needed, but replacement as such is not possible. – Rishi Goel Aug 08 '17 at 11:49
  • 1
    Why exactly do you think you need to replace `Class.getResourceAsStream()`? That's what your question should be about. – Kayaman Aug 08 '17 at 11:53
  • @Kayaman Yes, it's really my question. Because a very old project has migrated to a new environment, the old project has long been without maintenance and is now trying to simulate an environment consistent with the old project. – forDream Aug 08 '17 at 12:19
  • @RishiGoel Thanks.It's a very old project, but it's practically useless, but it's not willing to throw it away. I guess a part of the project may have been unable to find the source code – forDream Aug 08 '17 at 12:21
  • What is `ClassLoader.preDefineClass(String, ProtectionDomain)`? There is no such method. – Holger Aug 08 '17 at 12:22
  • @Holger It's a private method. However, it looks like he's going to have to come up with a different clever way to finish the migration. – Kayaman Aug 08 '17 at 12:23
  • @Kayaman: then, I don’t see the relevance here. If it’s `private`, it’s a method that `ClassLoader` will invoke itself, but can’t be changed (doing so would imply that changing `ClassLoader` is already changed). – Holger Aug 08 '17 at 12:26
  • @Holger This method is called in `ClassLoader.defineClass(String , byte[] , int , int ,ProtectionDomain)`, which is mandatory. In fact, this is not necessary, this method is only a security check. – forDream Aug 08 '17 at 12:33
  • Well, throwing an exception for `java.*` classes is within [the contract of `defineClass`](https://docs.oracle.com/javase/8/docs/api/java/lang/ClassLoader.html#defineClass-java.lang.String-byte:A-int-int-): “*Throws: … SecurityException - … if name begins with "java."*”. So it’s irrelevant, which `private` methods are involved. – Holger Aug 08 '17 at 12:36

3 Answers3

4

You can't.

This class gets loaded by the bootstrap class loader. And you can't change what this guy is doing - it is written in native code. In other words: this is at the core of the JVM - and you are not allowed to tamper with that.

That is the "whole idea" of that the java security concept: the user can't "get" into things that are guaranteed by the JVM platform.

Given the edit by the OP that this is about legacy code: it seems that you want to hook into Class.getResourceAsStream(String) for classes that are not part of the JVM - but that belong to your own code.

If that is the case, then the "answer" is simply: you have to make sure that all your classes are loaded by your class loader. The javadoc for getResourceAsStreeam() clearly states:

Finds a resource with a given name. The rules for searching resources associated with a given class are implemented by the defining class loader of the class. This method delegates to this object's class loader.

Thus: learn how to use a custom classloader. Start here for example.

GhostCat
  • 137,827
  • 25
  • 176
  • 248
  • Thanks. I have updated the question. The main problem is that many of these ancient projects use `Class.getResourceAsStream(String)` to obtain resources, but in the new environment, these resources do not exist and are redirected to other places – forDream Aug 08 '17 at 12:49
  • See my updated answer then. And just in case you consider accepting my answer ... feel free to wait until tomorrow with that ;-) – GhostCat Aug 08 '17 at 13:00
  • Thanks again.I tried it, it's works with `this.getClass().getResourceAsStream(String)`. But it's not work within `Class.class.getResourceAsStream(String)` / `String.class.getResourceAsStream(String)` – forDream Aug 09 '17 at 03:28
2

Throwing an exception for attempts to define classes with a qualified name starting with java., is part of the contract of defineClass, but that’s irrelevant. Any attempt to define a class with the name of an existing class on a ClassLoader can only have one of two possible results:

  1. If the existing class has been defined by the same ClassLoader, attempting to define it again will be rejected and an exception will be thrown. defineClass does not change existing classes, that’s simply not within its functionality.

  2. If the existing class has been loaded by a different ClassLoader (or the bootstrap loader), you may define a class with the same qualified name on this ClassLoader, but it will be an entirely different class, unrelated to the class with the same name but a different defining class loader.

The fact that you can not define such classes with the same name and a different loader for java.* classes is just an additional limitation, but your attempt wouldn’t work anyway.

If you want to replace the class ClassLoader after it has been loaded, you can only use an Agent, e.g. a Java Agent using the Instrumentation API, or a debugger. Alternatively, you can simply add your own version to the bootstrap class path using the -Xbootclasspath/p:… command line option.

Of course replacing it with a specific version only works in the environment, from which this version has been derived. Also such applications are not allowed to be publicly deployed.

A more general solution would be an Instrumentation Agent using the current version of ClassLoader and modify it on-the-fly, however, if you have to option of developing a byte code transforming Agent, it would be much easier and more reliable to transform the application code instead, just replacing all invocations of Class.getResourceAsStream(String) with an invocation of a custom method, without touching code not needing such adaptation.

Holger
  • 285,553
  • 42
  • 434
  • 765
1

I wrote a longish answer on how to implement runtime class transforms here: how to retransform a class at runtime

An easier way would be my retransformer project at https://github.com/nickman/retransformer which will transform java.* classes, including native methods. (I used it to modify Runtime.availableProcessors )

Nicholas
  • 15,916
  • 4
  • 42
  • 66