使用字节码操作库ASM在每个putfield操作之后调用对象的方法

时间:2014-06-03 23:59:32

标签: java reverse-engineering instrumentation java-bytecode-asm bytecode-manipulation

我正在对Java客户端应用程序进行逆向工程,我希望跟踪某些字段的修改以查看在解决混淆名称的操作之后发生了哪些更改。

我可能会使用某种类型的调试器,但我决定将它自制用于学习体验。

我已经使用ASM创建了一个类适配器,它正在查看每个指令的每个方法,当它到达PUTFIELD指令时,它应该调用对象的fireCallback方法

只要我能期望PUTFIELD目标成为工作类,它就非常简单了,但是如果在其他对象上调用它,我就无法弄清楚如何检测它被调用的对象上

我正在阅读JVM规范,我发现DUP和POP指令可用于堆栈操作,当PUTFIELD被调用时,堆栈上有对象引用和设置值,所以我想为什么我不能在putfield之前复制堆栈然后只是弹出设置值并在剩余的objectref上调用fireCallback

但它只是那么简单,不幸的是,如果设置值是double或long类型,PUTFIELD会接受两个字,而我无法简单地知道如何处理这个异常?

还是有一些更简单的方法?我怎么知道putfield之前加载到堆栈的对象?

我目前的代码:

    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);
                }

            }
        }
    }

这导致VerifyError出现消息&#34;期望在堆栈上找到对象/数组&#34;如果当时堆栈中有不同的项目集......

1 个答案:

答案 0 :(得分:1)

您的问题看起来有点奇怪,因为putfield指令已包含您需要的所有信息。它有一个reference to the owner class和一个field signature telling the type of the field

因此,对于遇到putfield指令时的有效字节代码,最顶层的堆栈值必须与字段的类型兼容,并且下一个值必须是对与所有者类兼容的实例的引用。换句话说,如果字段的签名是"J""D",则您有一个值占用堆栈顶部的两个单词,否则它就是一个单词。

请注意,对于使用GeneratorAdapter的此类任务更方便。当用作访问者时,它将默认复制遇到的代码,并且可以计算maxStack和maxLocal值等。因此,您只需要覆盖访问方法,以获取要注入代码的指令,而不需要实现迭代逻辑。

E.g。用于为类public static void fieldWrite(Object owner, String fieldName)中的mypackage.MyDebugger方法调用代码的每个putField指令:

// 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);
}