Question

I am reverse engineering a Java client application of which I'd like to track modifications of certain fields to see what's changed after which action to resolve the obfuscated names.

I could probably use a debugger of some kind, but I decided to homebrew it for the learning experience.

I've created a class adapter using ASM which is looking through every method's every instruction and when it hits PUTFIELD instruction its supposed to call the object's fireCallback method

It's pretty simple as long as I can expect the PUTFIELD target to be the working class, but if its being called on other object I can't figure out how can I detect which object is it called on

I was reading JVM spec and I found there's a DUP and POP instructions that could be used to stack manipulation and that when PUTFIELD is called there's on stack the object reference and the setting value so I thought why couldn't I just duplicate the stack before putfield and then simply pop the setting value and call fireCallback on the remaining objectref

But it just can't be that simple, unfortunately the PUTFIELD takes two words if the setting value is of type double or long, and I can't simply figure how can I handle this exception?

Or is there some simpler way? How do I know what object is loaded to the stack before putfield?

My current code:

    for (MethodNode method : (List<MethodNode>) methods) {

        if (ADD_FIELD_CALLBACKS) {
            Iterator<AbstractInsnNode> insIt = method.instructions
                    .iterator();

            while (insIt.hasNext()) {
                AbstractInsnNode ins = insIt.next();

                int opcode = ins.getOpcode();
                if (ins.getOpcode() == Opcodes.PUTFIELD) {
                    FieldInsnNode fieldInsNode = (FieldInsnNode) ins;

                    System.out.println(name + "'s updating "
                            + fieldInsNode.owner + "'s "
                            + fieldInsNode.name + " in method "
                            + method.name);

                    InsnList onFieldEditInsList = new InsnList();

                    method.instructions.insertBefore(ins, new InsnNode(
                            Opcodes.DUP2));

                    onFieldEditInsList.add(new InsnNode(Opcodes.POP));
                    onFieldEditInsList.add(new LdcInsnNode(
                            fieldInsNode.name));
                    onFieldEditInsList
                            .add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL,
                                    fieldInsNode.owner, "fireCallback",
                                    "(Ljava/lang/String;)V", false));

                    method.maxStack += 2;

                    method.instructions.insert(ins, onFieldEditInsList);
                }

            }
        }
    }

this results in a VerifyError with message "Expecting to find object/array on stack" in case there was different set of items in the stack at the time..

Was it helpful?

Solution

Your question looks a bit strange as the putfield instruction already contains every information you need. It has a reference to the owner class and a field signature telling the type of the field.

So for a valid byte code when a putfield instruction is encountered, the topmost stack value must be compatible with the field’s type and the next value must be a reference to an instance compatible with the owner class. In other words, if the signature of the field is either "J" or "D" you have a value occupying two words on top of the stack, otherwise it’s one word.

Note that for such tasks using a GeneratorAdapter is more convenient. When used as a visitor it will by default replicate the code encountered and it can calculate the maxStack and maxLocal values etc. So you only have to override the visit methods for instructions where you want to inject code and don’t need to implement the iteration logic.

E.g. for calling a method like public static void fieldWrite(Object owner, String fieldName) in a class mypackage.MyDebugger for every putField instruction the code looks like:

// i.e. class MyCodeTransformer extends GeneratorAdapter
@Override
public void visitFieldInsn(int opcode, String owner, String name, String desc) {
  if(opcode==Opcodes.PUTFIELD) {
    final Type fieldType = Type.getType(desc);
    super.swap(Type.getObjectType(owner), fieldType);
    if(fieldType.getSize()==1) super.dupX1(); else super.dupX2();
    super.visitLdcInsn(name);
    super.visitMethodInsn(Opcodes.INVOKESTATIC, "mypackage/MyDebugger",
        "fieldWrite", "(Ljava/lang/Object;Ljava/lang/String;)V", false);
  }
  super.visitFieldInsn(opcode, owner, name, desc);
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top