javaruntimebytecodebcel

BCel patching a method on the fly not working


I am trying to insert a callback into a Java method using BCEL but the callback is never called. The programs runs as if it was not instrumented at all.

Stripped down version of what I did:

package com.github.worldsender;

import java.lang.reflect.InvocationTargetException;

import org.apache.bcel.*;
import org.apache.bcel.classfile.*;
import org.apache.bcel.generic.*;

public class CustomHook {
    public static void callback() {
        System.out.println("Success");
    }

    private static JavaClass getOriginal() {
        try {
            return Repository.lookupClass("com.github.worldsender.Foo");
        } catch (ClassNotFoundException e) {
            throw new RuntimeException("Foo not found.", e);
        }
    }

    private static ClassGen modClass(ClassGen classGen) {
        for (Method method : classGen.getMethods()) {
            if (!method.getName().equals("main"))
                continue;
            classGen.removeMethod(method);
            MethodGen methodGen = modConstructor(classGen, method);
            classGen.addMethod(methodGen.getMethod());
            methodGen.getInstructionList().dispose();
            return classGen;
        }
        throw new RuntimeException("Method not found, abort");
    }

    private static MethodGen modConstructor(ClassGen classGen, Method constructor) {
        InstructionFactory factory = new InstructionFactory(classGen);
        ConstantPoolGen constants = classGen.getConstantPool();
        MethodGen methodGen = new MethodGen(constructor, classGen.getClassName(), constants);

        InstructionList ilist = methodGen.getInstructionList();

        String invokedClass = "com.github.worldsender.CustomHook";
        String invokedMethod = "callback";
        Type returnType = Type.VOID;
        Type[] arguments = Type.NO_ARGS;
        short invokeType = Constants.INVOKESTATIC;
        InvokeInstruction invoke = factory.createInvoke(invokedClass, invokedMethod, returnType, arguments, invokeType);

        ilist.insert(invoke);
        methodGen.stripAttributes(true);
        methodGen.setMaxStack();
        methodGen.setMaxLocals();
        return methodGen;
    }

    public static void main(String[] args) throws Exception {
        JavaClass original = getOriginal();
        ClassGen modClass = new ClassGen(original);
        modClass = modClass(modClass);

        Repository.removeClass(original);
        Repository.addClass(modClass.getJavaClass());

        Class<?> minecraftMain = Class.forName("com.github.worldsender.Foo");

        java.lang.reflect.Method meth = minecraftMain.getMethod("main", String[].class);
        meth.invoke(null, (Object) args);
    }
}
//// Other class
package com.github.worldsender;

public class Foo {
    public static void main(String[] args) {
        System.out.println("Here");
    }
}

All that is printed is:

Here

What I was expecting was:

Success
Here

What am I doing wrong?


Solution

  • When calling

    Repository.addClass(modClass.getJavaClass())
    

    you are adding the class to the BCEL repository but not to the current VM's class path. When calling

    Class.forName("com.github.worldsender.Foo")
    

    you are however instructing the VM to load the unmodified class file from the class path. Therefore, you cannot observe any effect. Have a look at BCEL's built-in class loader for loading a generated class.