2

Long story short:

  • I need to transform every class in my program (even the java
    library that are loaded before my agent).
  • I have find a way to do it but is not completly working. I'm open to new ideas.
  • My actual method act strange: it's supposed to print the same names in file and console but they are not. I'm sure that those classes reach my transform method because if I try to instrument them I get an error.

Complete story: I have created an agent in order to inject some custom code in every class that is loaded into my project. I add this agent at runtime using the option -javaagent and this work just fine.

The problem is that when my agent is attached to the JVM a lot of classes are already loaded (e.g. the java.io.* classes) and so my transformation miss an entire bunch of classes. So my question is: there is a way in which I can instrument all those class that I'm missing? Every suggestion is more then welcome.

For now I have tried like this:

public class MyTransformer implements ClassFileTransformer {


 public static void premain(String agentArgs, Instrumentation inst) {
    final MyTransformer  t = MyTransformer .getInstance();
    inst.addTransformer(t, true);
    MyTransformer .log.info(t + " registered via JVM option -javaagent");

    // TEST
    // by the time we are attached, the classes to be
    // dumped may have been loaded already. So, check
    // for candidates in the loaded classes.
    Class[] classes = inst.getAllLoadedClasses();
    List<Class> candidates = new ArrayList<Class>();
    for (Class c : classes) {
        if (inst.isModifiableClass(c) && inst.isRetransformClassesSupported()){
            candidates.add(c);
        }
    }
    System.out.println("There are "+candidates.size()+" classes");
    try {
        // if we have matching candidates, then
        // retransform those classes so that we
        // will get callback to transform.
        if (! candidates.isEmpty()) {
            Iterator it = candidates.iterator();
            while(it.hasNext()){
                Class c = (Class)it.next();
                if(!c.getName().startsWith("javassist")){
                    System.out.println(" ========================> In Progress:"+c.getName());
                    inst.retransformClasses(c);
                }
            }
        }else{
            System.out.println("candidates.isEmpty()");
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
  }

  public byte[] transform(ClassLoader loader, String className,
        Class<?> classBeingRedefined, ProtectionDomain protectionDomain,
        byte[] classfileBuffer) throws IllegalClassFormatException {

    byte[] byteCode = classfileBuffer;

    final String dot_classname    = className.replace('/', '.');
    // log classes that are going throug the instrumentor
    // just log if the java.io. is coming here
    try {
        PrintWriter out = new PrintWriter(new BufferedWriter(new FileWriter(path, true)));
        out.println(dot_classname);
        out.close();
     } catch (IOException ex) {
        Logger.getLogger(MyTransformer.class.getName()).log(Level.SEVERE, null, ex);
     }
     return byteCode;
  }
}

So my first attempt is to take in the "premain" method every class already loaded and if it's possible reTransfrom it with the method "retransfromClasses".

Now in my transform method I have only a log that write into a file every class that is going to be transformed. Now my test results are:

There are 1486 classes  
==> In Progress:com.sun.org.apache.xerces.internal.parsers.AbstractXMLDocumentParser
==> In Progress:java.lang.Enum  
==> In Progress:java.lang.ThreadGroup  
==> In Progress:java.nio.file.FileSystem  
==> In Progress:java.util.regex.Pattern$Prolog  
==> In Progress:com.sun.org.apache.xerces.internal.dom.AttributeMap 
==> In Progress:java.util.regex.Matcher  
==> In Progress:org.apache.commons.beanutils.converters.ShortConverter 
==> In Progress:com.google.gson.JsonNull 
==> In Progress:java.util.concurrent.CopyOnWriteArrayList$COWIterator 
==> In Progress:java.util.concurrent.locks.ReentrantLock 
==> In Progress:java.lang.NoSuchMethodError 
==> In Progress:org.apache.commons.lang.BooleanUtils 
==> In Progress:java.lang.reflect.WeakCache$CacheValue 
==> In Progress:com.google.gson.internal.bind.TypeAdapters$33 
==> In Progress:java.lang.reflect.Type 
==> In Progress:sun.reflect.generics.scope.AbstractScope 
==> In Progress:org.apache.log4j.helpers.DateTimeDateFormat 
==> In Progress:sun.nio.cs.MS1252 
==> In Progress:java.lang.Integer$IntegerCache 
==> In Progress:com.sun.org.apache.xerces.internal.utils.SecuritySupport$3 
==> In Progress:org.apache.commons.configuration.MapConfiguration 
==> In Progress:org.apache.commons.beanutils.IntrospectionContext 
==> In Progress:java.io.Reader 
==> In Progress:java.util.WeakHashMap$Holder 
==> In Progress:java.util.ServiceLoader$LazyIterator 
==> In Progress:java.util.regex.Pattern$Branch 
==> In Progress:java.lang.IllegalMonitorStateException 
==> In Progress:java.util.regex.Pattern$Curly 
==> In Progress:org.apache.commons.configuration.resolver.EntityRegistry 
==> In Progress:java.io.IOException 
==> In Progress:java.io.FilterOutputStream 
==> In Progress:org.apache.log4j.LogManager 
==> In Progress:sun.util.logging.PlatformLogger$Level 
==> In Progress:java.nio.charset.CoderResult$1 
==> In Progress:com.google.gson.FieldNamingPolicy$5 
==> In Progress:com.google.gson.internal.ObjectConstructor 
==> In Progress:sun.util.calendar.BaseCalendar$Date

So you can notice that I can find every class (even the java.io) in which I interested. Now if we take a look at the file that is supposed to contain every class that MyTransformer tried to transform we notice that there are no common entries and that's really strange.

org.apache.commons.beanutils.converters.ShortConverter
com.google.gson.JsonNull
org.apache.commons.lang.BooleanUtils
com.google.gson.internal.bind.TypeAdapters$33
org.apache.log4j.helpers.DateTimeDateFormat
org.apache.commons.configuration.MapConfiguration
org.apache.commons.beanutils.IntrospectionContext
org.apache.commons.configuration.resolver.EntityRegistry
org.apache.log4j.LogManager
com.google.gson.FieldNamingPolicy$5
com.google.gson.internal.ObjectConstructor
org.apache.log4j.Layout
com.google.gson.internal.bind.TypeAdapters
org.apache.log4j.PropertyConfigurator
com.sap.psr.vulas.java.JavaEnumId
org.apache.commons.collections.collection.AbstractSerializableCollectionDecorator
org.apache.commons.logging.impl.LogFactoryImpl
org.apache.commons.lang.text.StrTokenizer

Another problem that I found is that if I try to insert in Mytransformer.transform(..) method some tampering in the bytecode it will break my execution. I mean the transformation operation that I am using is perfectly fine as I use it on every class that is loaded after my agent is attached. But somehow if I try to use it one those "retransformed" classes I will rise this error:

 ==> In Progress:org.apache.commons.lang.text.StrMatcher$TrimMatcher java.lang.UnsupportedOperationException: class redefinition failed: attempted to change the schema (add/remove fields)
        at sun.instrument.InstrumentationImpl.retransformClasses0(Native Method)
        at sun.instrument.InstrumentationImpl.retransformClasses(InstrumentationImpl.java:144)

I know that there are limitations on the transformation of native classes but I don't thing that my transform operation is attempting to change the schema. I will investigate on this point and post updates.

David Hoelzer
  • 15,862
  • 4
  • 48
  • 67
rakwaht
  • 3,666
  • 3
  • 28
  • 45
  • Could you tell us why you would want to inject code in the native Java classes? There might be an easier way to achieve your goal than injecting code in every single classes of your environment. – Aaron Apr 22 '16 at 10:01
  • Is complicated but I ensure you that I need to change the bytCode of those classes. (my goal in simple words is to substitue the return statement of some methods, e.g substitue the return of the call byte[] java.io.readFile so that I can return my custom bytes instead of really read it. And this is going to happen on every call to this method even if i dont know where is used) – rakwaht Apr 22 '16 at 10:08

2 Answers2

1

This link is similar to the question you are asking. It looks like you can modify the body of a native method, but cannot add methods or fields.

I can see in your code that you are using Javassist to do it. I spent multiple months trying to get it to work but it wouldn't work for me, so I resulted to using ASM (which does work by the way).

So how do you do it? First, I would use the Attach API to hook onto rt.jar (because it is already loaded you need to attach your agent to the java process. Then you can add your transformer to the java agent from the agentmain method. You then create a class reader for the class you want to edit, and accept a ClassVisitor, which you extend. Then, you override a method, and, if it is the method you want, replace the code with other code. You can see an example on my Github. You will need to transform it each individual time because it loads onto memory - you don't need to worry about removing the code.

I hope this helps! Please comment if you have any questions :)

Community
  • 1
  • 1
JD9999
  • 394
  • 1
  • 7
  • 18
0

have you added required manifest entries to yours agent jar manifest.mf ?

How yours 'manifest.mf' looks like ? It should have something like

Can-Redefine-Classes: true

Can-Retransform-Classes: true

Regards,

Grzesiek

Grzesuav
  • 459
  • 2
  • 10
  • Yes it looks like exactly like this. I registered my agent and its ability to redefine and retransform. – rakwaht May 13 '16 at 09:08
  • As I just checked on https://docs.oracle.com/javase/7/docs/api/java/lang/instrument/Instrumentation.html it seems that retransformation Has limitation. But again, you start jvm with agent attached, or are you attching agent to running JVM ? – Grzesuav May 14 '16 at 14:02
  • I attach the agent passing the -javaagent option in the cmd line so its at starting time. The problem i that there are classes loaded before my agent is actually attached. – rakwaht May 16 '16 at 09:36