ASM - несовместимые кадры стековой карты в целевой ветви

Я пытаюсь сделать простой обфускатор Java байт-код, который работает, заменив GOTO инструкции с простыми условными переходами, скажем, if 10 != 15 GOTO else throw IllegalStateException, Мой текущий код:

    final AbstractInsnNode[] insns = method.instructions.toArray().clone();

    for (final AbstractInsnNode insn : insns) {
        final int op = insn.getOpcode();

        if ((op == GOTO) || (op == IFLE) || (op == IFGE)) {
            LabelNode l0 = new LabelNode();
            LabelNode l1 = new LabelNode();
            LabelNode l2 = new LabelNode();

            int locals = (method.localVariables == null) ? 0 : method.localVariables.size();
            int params = (method.parameters == null) ? 0 : method.parameters.size();

            int v0index = locals + params;
            int v1index = v0index + 1;
            int exindex = v1index + 1;

            // Init fake conditional fields
            method.instructions.insertBefore(insn, new LdcInsnNode(10F));
            method.instructions.insertBefore(insn, new VarInsnNode(FSTORE, v0index));

            method.instructions.insertBefore(insn, new LdcInsnNode(45F));
            method.instructions.insertBefore(insn, new VarInsnNode(FSTORE, v1index));

            // Crossing jumps
            method.instructions.insertBefore(insn, l1);
            method.instructions.insert(insn, l0);
            method.instructions.insert(l0, l2);

            LabelNode l3 = new LabelNode();
            LabelNode l4 = new LabelNode();

            method.instructions.insert(l2, l3);
            method.instructions.insert(l3, l4);

            // If 'v0!=v1', jump to l0, otherwise goto l3
            method.instructions.insertBefore(l1, new VarInsnNode(FLOAD, v0index));
            method.instructions.insertBefore(l1, new VarInsnNode(FLOAD, v1index));
            method.instructions.insertBefore(l1, new InsnNode(FCMPG));
            method.instructions.insertBefore(l1, new JumpInsnNode(IFNE, l0));
            method.instructions.insertBefore(l1, new JumpInsnNode(GOTO, l3));

            // Jump to l3 results in throwing an exception
            // Create and throw the exception
            method.instructions.insertBefore(l4, new TypeInsnNode(NEW, "java/lang/IllegalStateException"));
            method.instructions.insertBefore(l4, new InsnNode(DUP));
            method.instructions.insertBefore(l4, new MethodInsnNode(INVOKESPECIAL, "java/lang/IllegalStateException", "<init>", "()V", false));
            method.instructions.insertBefore(l4, new InsnNode(ATHROW));

            method.instructions.insertBefore(l0, new JumpInsnNode(GOTO, l2));
            method.instructions.insertBefore(l2, new JumpInsnNode(GOTO, l1));

            // Exception handler
            LabelNode start = new LabelNode();
            LabelNode handler = new LabelNode();
            LabelNode end = new LabelNode();

            method.instructions.insertBefore(l0, start);

            method.instructions.insert(l2, end);
            method.instructions.insert(end, handler);

            // Just throw the exception again
            LabelNode l5 = new LabelNode();

            method.instructions.insert(handler, l5);
            method.instructions.insertBefore(l5, new TypeInsnNode(NEW, "java/lang/IllegalStateException"));
            method.instructions.insertBefore(l5, new InsnNode(DUP));
            method.instructions.insertBefore(l5, new MethodInsnNode(INVOKESPECIAL, "java/lang/IllegalStateException", "<init>", "()V", false));
            method.instructions.insertBefore(l5, new InsnNode(ATHROW));

            // Try/catch
            TryCatchBlockNode tryBlock = new TryCatchBlockNode(start, end, handler, "java/lang/IllegalStateException");
            method.tryCatchBlocks.add(tryBlock);

            // Init local variables
            method.visitLocalVariable("_v0_" + Rand.alphaNumeric(5), "F", null, l0.getLabel(), l2.getLabel(), v0index);
            method.visitLocalVariable("_v1_" + Rand.alphaNumeric(5), "F", null, l0.getLabel(), l2.getLabel(), v1index);
            method.visitLocalVariable("_ex_" + Rand.alphaNumeric(5), "Ljava/lang/IllegalArgumentException;", null, start.getLabel(), handler.getLabel(), exindex);
        }
    }

куда method является параметром метода запутывания типа MethodNodeи класс реализует интерфейс Opcodes,

Это работает нормально, но не со всеми методами (я совсем новичок в байт-коде, и поэтому не знаю точных случаев). Например, он отлично работает для main метод:

Оригинальный код Java (декомпилированный в Procyon): https://p.reflex.rip/DLMT.cs

Оригинальный байт-код: https://p.reflex.rip/ywJt.go

Запутанный код Java (декомпилированный в Procyon): https://p.reflex.rip/Er9V.cs

Обфусцированный байт-код: https://p.reflex.rip/JBAb.go

Тем не менее, это ломает один из других, divMinByMax, метод:

Исходный код Java (декомпилированный в Procyon): https://p.reflex.rip/AW9W.java

Оригинальный байт-код: https://p.reflex.rip/GX2k.cpp

Запутанный код Java (декомпилированный в Procyon, FAILED): https://p.reflex.rip/Eqju.java

Обфусцированный байт-код: https://p.reflex.rip/isiX.cpp

Этот метод вызывает VerifyError, когда я пытаюсь запустить запутанный JAR с java -jar:

Error: A JNI error has occurred, please check your installation and try again
Exception in thread "main" java.lang.VerifyError: Inconsistent stackmap frames at branch target 27
Exception Details:
  Location:
    test/one/HelloRandom.divMinByMax(DD)D @21: goto
  Reason:
    Current frame's stack size doesn't match stackmap.
  Current Frame:
    bci: @21
    flags: { }
    locals: { double, double_2nd, float, float }
    stack: { }
  Stackmap Frame:
    bci: @27
    flags: { }
    locals: { double, double_2nd, float, float }
    stack: { 'java/lang/IllegalStateException' }
  Bytecode:
    0x0000000: 2826 9712 7145 1272 4624 2596 9a00 0ca7
    0x0000010: 0014 9e00 48a7 0006 a7ff fabb 0016 59b7
    0x0000020: 0073 bfbb 0016 59b7 0073 bf00 0000 0000
    0x0000030: 00bf 0000 00bf 0000 0000 0000 00bf 0000
    0x0000040: bf00 00bf 0000 bf00 00bf 0000 0000 0000
    0x0000050: 00bf 0000 0000 0000 00bf 2826 6faf
  Exception Handler Table:
    bci [24, 27] => handler: 27
  Stackmap Table:
    full_frame(@18,{Double,Float,Float},{Integer})
    same_locals_1_stack_item_frame(@24,Integer)
    same_locals_1_stack_item_frame(@27,Object[#22])
    same_locals_1_stack_item_frame(@35,Integer)
    full_frame(@43,{},{Object[#159]})
    same_locals_1_stack_item_frame(@50,Object[#159])
    same_locals_1_stack_item_frame(@54,Object[#159])
    same_locals_1_stack_item_frame(@62,Object[#159])
    same_locals_1_stack_item_frame(@65,Object[#159])
    same_locals_1_stack_item_frame(@68,Object[#159])
    same_locals_1_stack_item_frame(@71,Object[#159])
    same_locals_1_stack_item_frame(@74,Object[#159])
    same_locals_1_stack_item_frame(@82,Object[#159])
    append_frame(@90,Double,Float,Float)
    same_locals_1_stack_item_frame(@93,Double)

Я провел много исследований, и единственное, что я нашел, было причиной: как я понял, проблема в том, что стек @21 (GOTO который переходит к метке, которая делает throw new IllegalStateException):

stack: { }

(который является пустым) не соответствует стеку в @27на метке цели перехода:

stack: { 'java/lang/IllegalStateException' }(который содержит исключение, которое он должен "выбросить").

В общем, ошибка, как я понимаю, происходит, когда я пытаюсь выполнить GOTO <n> прыгать, где <n> это номер метки, которая "выбрасывает" IllegalStateException,

Как я могу исправить эту проблему? Может быть, есть способ сделать стек в @21 содержать java/lang/IllegalStateException перед прыжком (чтобы эти два стека, один до и один после прыжка, совпадали)? Или что-то еще я могу сделать с этим?

1 ответ

Решение

Вы вставляете обработчик исключения после инструкции, но когда инструкция является условной ветвью, т.е. IFLE или же IFGEветвь может не быть занята, и поток кода продолжается после инструкции, выполняющейся в обработчике исключений.

Это создает противоречивое состояние, так как обработчик исключений ожидает Throwable в стеке, который не существует, когда поток кода продолжается после инструментированной инструкции. Но, конечно, вы не хотите запускать обработчик исключений в этом случае, поэтому вам нужно вставить другой GOTO, от l2 в l5 если я правильно понял

Это не проблема при установке GOTO инструкции, которые никогда не продолжаются после инструкции.

В этом месте я бы порекомендовал другой стиль кодирования. Вставка до и после различных ссылочных узлов делает невозможным прогнозирование фактической структуры кода при чтении вашего кода. Это было бы гораздо проще в обслуживании, если бы вы просто вставили линейный список инструкций, используя только один ссылочный узел.

Другие вопросы по тегам