如何使用javassist检测从特定jar加载的方法?

时间:2019-11-05 19:11:26

标签: instrumentation javassist

我有一个示例jar,我将从磁盘将其加载到类池中。从那里,我可以轻松地访问此类的方法并对其进行检测,就像您看到的JsEval方法一样。

但是,在Helloworld示例类内部,我希望能够检测其他库函数调用。在此示例中,我尝试从nashorn脚本引擎中检测eval函数。但是,这不起作用。我可以很好地访问类(pool.get),并且可以为eval修补方法。但是,当我从cl.run()运行SampleClass时,方法的执行就像没有插入任何代码。我怀疑这与我用来执行Sampleclass的类加载器有关,但是我被卡住了。关于我在这里做错的任何想法吗?

public class maventest {

public static void main(String[] args) throws NotFoundException, CannotCompileException, Throwable {

    ClassPool pool = ClassPool.getDefault();
    Loader cl = new Loader(pool);

    //pool.importPackage(Test.class.getPackage().getName());
    //This works and the method is instrumented.
    pool.insertClassPath("Z:\\HelloWorld\\target\\HelloWorld-1.0-SNAPSHOT-jar-with-dependencies.jar");  //Get the Jar from disk.
    pool.importPackage("com.mycompany.helloworld");
    //pool.appendClassPath();
    CtClass helloworld = pool.get("com.mycompany.helloworld.SampleClass");
    helloworld.getDeclaredMethod("JsEval").insertBefore("System.out.println(\"Calling JsEval from within helloworld\\n\");");

    //This does not work.
    //Attempt to instrument the eval function that is called from inside of HelloWorld
    String classToLoad = "jdk.nashorn.api.scripting.NashornScriptEngine";
    String constuctor_name = "eval";
    CtClass nash = pool.get(classToLoad);
    //Multiple eval calls.. Just instrument them all.
    CtMethod[] meths = nash.getDeclaredMethods("eval");
    for (CtMethod m : meths) {
        m.insertBefore("System.out.println(\"Nashorn Scripting Engined eval called.\");");
    }

    //Execute the hello world class with null args
    cl.run("com.mycompany.helloworld.SampleClass", null);

}}

这是调用我要检测的lib函数的示例代码。

public class SampleClass {

public static void main(String[] args) throws IOException, NotFoundException {


    JsEval("var greeting='hello world'; print(greeting) + greeting");
}

private static void JsEval(String js) {
    ScriptEngine engine = new ScriptEngineManager().getEngineByName("nashorn");

    try {
        Object result = engine.eval(js);
    } catch (ScriptException ex) {
        Logger.getLogger(SampleClass.class.getName()).log(Level.SEVERE, null, ex);
    }
}}

1 个答案:

答案 0 :(得分:1)

我知道这个问题有点老了,但是仍然没有答案,我很好奇。

不起作用的原因是,getDeclaredMethods("eval")不会在超类中搜索方法,如Javadoc中所述。您要调用的方法(即采用单个String参数的方法)是在父类AbstractScriptEngine中定义的,但不是在NashornScriptEngine中定义的。因此,要么必须将目标类更改为真正定义了方法的类,要么通过getMethod(..)getMethods()搜索方法,这两种方法都会返回继承的方法。由于getMethods()不能采用方法名称参数,而是返回所有方法,并且您必须在检测循环中再次按名称过滤,因此建议您通过指定其确切签名来选择要真正检测的一个方法:< / p>

String classToLoad = "jdk.nashorn.api.scripting.NashornScriptEngine";
CtClass nash = pool.get(classToLoad);
CtMethod m = nash.getMethod(
  "eval",
  Descriptor.ofMethod(
    pool.get("java.lang.Object"),
    new CtClass[] { pool.get("java.lang.String") }
  )
);
m.insertBefore("System.out.println(\"Nashorn Scripting Engined eval called.\");");

或者如果Descriptor.ofMethod(..)对您来说太累了,您对描述符语法感到满意:

String classToLoad = "jdk.nashorn.api.scripting.NashornScriptEngine";
CtClass nash = pool.get(classToLoad);
CtMethod m = nash.getMethod("eval", "(Ljava/lang/String;)Ljava/lang/Object;");
m.insertBefore("System.out.println(\"Nashorn Scripting Engined eval called.\");");

现在您的控制台日志输出符合预期:

Calling JsEval from within helloworld

Warning: Nashorn engine is planned to be removed from a future JDK release
hello world

更新:糟糕,我错过了您尝试修改引导程序类或更笼统地说已经加载的类这一事实。在这种情况下,转换无效,除非您使用Java工具API,即使用ClassFileTransformer并将其集成到Java代理中(如果您不知道Java代理是什么,请使用喜欢的Web搜索引擎以及如何建立一个)或动态附加。在本示例中,我使用微小的byte-buddy-agent库来将其热连接到正在运行的JVM,只是为了向您展示效果。

一个超级简单的版本,它不是很通用,而是设计为仅查找eval(String)方法,如下所示:

import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import jdk.nashorn.api.scripting.NashornScriptEngine;
import net.bytebuddy.agent.ByteBuddyAgent;

import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
import java.io.ByteArrayInputStream;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.lang.instrument.Instrumentation;
import java.lang.instrument.UnmodifiableClassException;
import java.security.ProtectionDomain;
import java.util.logging.Level;
import java.util.logging.Logger;

public class MavenTest {

  public static void main(String[] args) throws UnmodifiableClassException {
    Instrumentation instrumentation = ByteBuddyAgent.install();
    instrumentation.addTransformer(new ScriptEngineTransformer());

    Class<?> targetClass = NashornScriptEngine.class;
    // Go up the super class hierarchy, pretending we don't know the exact
    // super class class in which the target method is defined
    while (!targetClass.equals(Object.class)) {
      instrumentation.retransformClasses(targetClass);
      targetClass = targetClass.getSuperclass();
    }

    jsEval("var greeting='hello world'; print(greeting)");
  }

  private static void jsEval(String js) {
    ScriptEngine engine = new ScriptEngineManager().getEngineByName("nashorn");
    try {
      engine.eval(js);
    }
    catch (ScriptException ex) {
      Logger.getLogger(MavenTest.class.getName()).log(Level.SEVERE, null, ex);
    }
  }

  static class ScriptEngineTransformer implements ClassFileTransformer {
    private static final ClassPool CLASS_POOL = ClassPool.getDefault();

    @Override
    public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
      CtClass targetClass;
      try {
        // Caveat: Do not just use 'classPool.get(className)' because we would miss previous transformations.
        // It is necessary to really parse 'classfileBuffer'.
        targetClass = CLASS_POOL.makeClass(new ByteArrayInputStream(classfileBuffer));
        CtMethod evalMethod = targetClass.getDeclaredMethod("eval", new CtClass[] { CLASS_POOL.get("java.lang.String") });
        targetClass.defrost();
        evalMethod.insertBefore("System.out.println(\"Scripting engine eval(String) called\");");
      }
      catch (Exception e) {
        return null;
      }

      byte[] transformedBytecode;
      try {
        transformedBytecode = targetClass.toBytecode();
      }
      catch (Exception e) {
        e.printStackTrace();
        return null;
      }

      return transformedBytecode;
    }
  }

}

您可能已经注意到,我重命名了您的一些类和方法名称,以使其符合Java标准。

现在控制台日志是:

Warning: Nashorn engine is planned to be removed from a future JDK release
Scripting engine eval(String) called
hello world
相关问题