Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Understanding how to use visitFrame

I am reading in a bunch of classes from a JAR file in which I plan to inject a simple method (and then dump the new jar) in Java which posts some data to a PHP file:

public static void post(final String n, final String o){
    try{
        final URL url = new URL("http://urltophpfile.com/phpfile.php");
        final HttpURLConnection connection = (HttpURLConnection) url.openConnection();
        connection.setReadTimeout(60000);
        connection.setConnectTimeout(60000);
        connection.setDoInput(true);
        connection.setDoOutput(true);
        connection.setRequestMethod("POST");
        connection.addRequestProperty("User-Agent", "useragent");
        final BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(connection.getOutputStream()));
        writer.write(String.format("n=%s&p=%s", n, o));
        writer.flush();
        writer.close();
        connection.getInputStream().read();
    }catch(IOException ex){
        ex.printStackTrace();
    }
}

Then I viewed the bytecode for it using the Intellij Bytecode Viewer which produced:

    public static post(Ljava/lang/String;Ljava/lang/String;)V
    TRYCATCHBLOCK L0 L1 L2 java/io/IOException
            L0
    LINENUMBER 11 L0
    NEW java/net/URL
            DUP
    LDC "http://urltophpfile.com/phpfile.phpp"
    INVOKESPECIAL java/net/URL.<init> (Ljava/lang/String;)V
    ASTORE 2
    L3
    LINENUMBER 12 L3
    ALOAD 2
    INVOKEVIRTUAL java/net/URL.openConnection ()Ljava/net/URLConnection;
    CHECKCAST java/net/HttpURLConnection
    ASTORE 3
    L4
    LINENUMBER 13 L4
    ALOAD 3
    LDC 60000
    INVOKEVIRTUAL java/net/HttpURLConnection.setReadTimeout (I)V
            L5
    LINENUMBER 14 L5
    ALOAD 3
    LDC 60000
    INVOKEVIRTUAL java/net/HttpURLConnection.setConnectTimeout (I)V
            L6
    LINENUMBER 15 L6
    ALOAD 3
    ICONST_1
    INVOKEVIRTUAL java/net/HttpURLConnection.setDoInput (Z)V
            L7
    LINENUMBER 16 L7
    ALOAD 3
    ICONST_1
    INVOKEVIRTUAL java/net/HttpURLConnection.setDoOutput (Z)V
            L8
    LINENUMBER 17 L8
    ALOAD 3
    LDC "POST"
    INVOKEVIRTUAL java/net/HttpURLConnection.setRequestMethod (Ljava/lang/String;)V
            L9
    LINENUMBER 18 L9
    ALOAD 3
    LDC "User-Agent"
    LDC "useragent"
    INVOKEVIRTUAL java/net/HttpURLConnection.addRequestProperty (Ljava/lang/String;Ljava/lang/String;)V
            L10
    LINENUMBER 19 L10
    NEW java/io/BufferedWriter
            DUP
    NEW java/io/OutputStreamWriter
            DUP
    ALOAD 3
    INVOKEVIRTUAL java/net/HttpURLConnection.getOutputStream ()Ljava/io/OutputStream;
    INVOKESPECIAL java/io/OutputStreamWriter.<init> (Ljava/io/OutputStream;)V
    INVOKESPECIAL java/io/BufferedWriter.<init> (Ljava/io/Writer;)V
    ASTORE 4
    L11
    LINENUMBER 20 L11
    ALOAD 4
    LDC "n=%s&p=%s"
    ICONST_2
    ANEWARRAY java/lang/Object
            DUP
    ICONST_0
    ALOAD 0
    AASTORE
            DUP
    ICONST_1
    ALOAD 1
    AASTORE
    INVOKESTATIC java/lang/String.format (Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/String;
    INVOKEVIRTUAL java/io/BufferedWriter.write (Ljava/lang/String;)V
            L12
    LINENUMBER 21 L12
    ALOAD 4
    INVOKEVIRTUAL java/io/BufferedWriter.flush ()V
            L13
    LINENUMBER 22 L13
    ALOAD 4
    INVOKEVIRTUAL java/io/BufferedWriter.close ()V
            L14
    LINENUMBER 23 L14
    ALOAD 3
    INVOKEVIRTUAL java/net/HttpURLConnection.getInputStream ()Ljava/io/InputStream;
    INVOKEVIRTUAL java/io/InputStream.read ()I
            POP
    L1
    LINENUMBER 26 L1
    GOTO L15
    L2
    LINENUMBER 24 L2
    FRAME SAME1 java/io/IOException
    ASTORE 2
    L16
    LINENUMBER 25 L16
    ALOAD 2
    INVOKEVIRTUAL java/io/IOException.printStackTrace ()V
            L15
    LINENUMBER 27 L15
    FRAME SAME
    RETURN
            L17
    LOCALVARIABLE url Ljava/net/URL; L3 L1 2
    LOCALVARIABLE connection Ljava/net/HttpURLConnection; L4 L1 3
    LOCALVARIABLE writer Ljava/io/BufferedWriter; L11 L1 4
    LOCALVARIABLE ex Ljava/io/IOException; L16 L15 2
    LOCALVARIABLE n Ljava/lang/String; L0 L17 0
    LOCALVARIABLE o Ljava/lang/String; L0 L17 1
    MAXSTACK = 6
    MAXLOCALS = 5
}

Which I then converted to ASM:

    final MethodNode postMethod = new MethodNode(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC, "post", "(Ljava/lang/String;Ljava/lang/String;)V", null, null);
    final Label tryStart = new Label();
    postMethod.visitLabel(tryStart);
    postMethod.visitTypeInsn(Opcodes.NEW, "java/net/URL");
    postMethod.visitInsn(Opcodes.DUP);
    postMethod.visitLdcInsn("http://urltophpfile.com/phpfile.php");
    postMethod.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/net/URL", "<init>", "(Ljava/lang/String;)V");
    postMethod.visitVarInsn(Opcodes.ASTORE, 2);
    postMethod.visitVarInsn(Opcodes.ALOAD, 2);
    postMethod.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/net/URLConnection", "openConnection", "()Ljava/net/URLConnection;");
    postMethod.visitTypeInsn(Opcodes.CHECKCAST, "java/net/HttpURLConnection");
    postMethod.visitVarInsn(Opcodes.ASTORE, 3);
    postMethod.visitVarInsn(Opcodes.ALOAD, 3);
    postMethod.visitLdcInsn(60000);
    postMethod.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/net/HttpURLConnection", "setReadTimeout", "(I)V");
    postMethod.visitVarInsn(Opcodes.ALOAD, 3);
    postMethod.visitLdcInsn(60000);
    postMethod.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/net/HttpURLConnection", "setConnectTimeout", "(I)V");
    postMethod.visitVarInsn(Opcodes.ALOAD, 3);
    postMethod.visitInsn(Opcodes.ICONST_1);
    postMethod.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/net/HttpURLConnection", "setDoInput", "(Z)V");
    postMethod.visitVarInsn(Opcodes.ALOAD, 3);
    postMethod.visitInsn(Opcodes.ICONST_1);
    postMethod.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/net/HttpURLConnection", "setDoOutput", "(Z)V");
    postMethod.visitVarInsn(Opcodes.ALOAD, 3);
    postMethod.visitLdcInsn("POST");
    postMethod.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/net/HttpURLConnection", "setRequestMethod", "(Ljava/lang/String;)V");
    postMethod.visitVarInsn(Opcodes.ALOAD, 3);
    postMethod.visitLdcInsn("User-Agent");
    postMethod.visitLdcInsn("useragent");
    postMethod.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/net/HttpURLConnection", "addRequestProperty", "(Ljava/lang/String;Ljava/lang/String;)V");
    postMethod.visitTypeInsn(Opcodes.NEW, "java/io/BufferedWriter");
    postMethod.visitInsn(Opcodes.DUP);
    postMethod.visitTypeInsn(Opcodes.NEW, "java/io/OutputStreamWriter");
    postMethod.visitInsn(Opcodes.DUP);
    postMethod.visitVarInsn(Opcodes.ALOAD, 3);
    postMethod.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/net/HttpURLConnection", "getOutputStream", "()Ljava/io/OutputStream;");
    postMethod.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/io/OutputStreamWriter", "<init>", "(Ljava/io/OutputStream;)V");
    postMethod.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/io/BufferedWriter", "<init>", "(Ljava/io/Writer;)V");
    postMethod.visitVarInsn(Opcodes.ASTORE, 4);
    postMethod.visitVarInsn(Opcodes.ALOAD, 4);
    postMethod.visitLdcInsn("n=%s&p=%s");
    postMethod.visitInsn(Opcodes.ICONST_2);
    postMethod.visitTypeInsn(Opcodes.ANEWARRAY, "java/lang/Object");
    postMethod.visitInsn(Opcodes.DUP);
    postMethod.visitInsn(Opcodes.ICONST_0);
    postMethod.visitVarInsn(Opcodes.ALOAD, 0);
    postMethod.visitInsn(Opcodes.AASTORE);
    postMethod.visitInsn(Opcodes.DUP);
    postMethod.visitInsn(Opcodes.ICONST_1);
    postMethod.visitVarInsn(Opcodes.ALOAD, 1);
    postMethod.visitInsn(Opcodes.AASTORE);
    postMethod.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/String", "format", "(Ljava/lang/String;[Ljava/lang/Object;)L/java/lang/String;");
    postMethod.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/BufferedWriter", "write", "(Ljava/lang/String;)V");
    postMethod.visitVarInsn(Opcodes.ALOAD, 4);
    postMethod.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/BufferedWriter", "flush", "()V");
    postMethod.visitVarInsn(Opcodes.ALOAD, 4);
    postMethod.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/BufferedWriter", "close", "()V");
    postMethod.visitVarInsn(Opcodes.ALOAD, 3);
    postMethod.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/net/HttpURLConnection", "getInputStream", "()Ljava/io/InputStream;");
    postMethod.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/InputStream", "read", "()I");
    postMethod.visitInsn(Opcodes.POP);
    final Label gotoEnd = new Label();
    postMethod.visitJumpInsn(Opcodes.GOTO, gotoEnd);
    // FRAME SAME1 java/io/IOException <- how to create instruction?
    final Label catchStart = new Label();
    postMethod.visitLabel(catchStart);
    postMethod.visitVarInsn(Opcodes.ASTORE, 2);
    postMethod.visitVarInsn(Opcodes.ALOAD, 2);
    postMethod.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/IOException", "printStackTrace", "()V");
    final Label tryEnd = new Label();
    postMethod.visitLabel(tryEnd);
    postMethod.visitLabel(gotoEnd);
    // FRAME SAME <- how to create instruction?
    postMethod.visitInsn(Opcodes.RETURN);
    postMethod.visitTryCatchBlock(tryStart, tryEnd, catchStart, "java/io/IOException");
    postMethod.visitMaxs(6, 5);

The problem is that when I try dumping all of the classes to the jar and starting it, I receive the exception:

Exception in thread "AWT-EventQueue-0" java.lang.VerifyError: Expecting a stackmap frame at branch target 124
Exception Details:
Location:
        Client.post(Ljava/lang/String;Ljava/lang/String;)V @0: new
Reason:
Expected stackmap frame at this location.
        Bytecode:
        0000000: bb0d ed59 1316 21b7 1119 4d2c b616 27c0
0000010: 1629 4e2d 1316 2ab6 162d 2d13 162a b616
0000020: 302d 04b6 1633 2d04 b616 362d 1316 38b6
0000030: 163b 2d13 163d 1316 3fb6 1642 bb14 5059
        0000040: bb16 4459 2db6 1645 b716 48b7 1456 3a04
0000050: 1904 1316 4a05 bd03 0059 032a 5359 042b
0000060: 53b8 164d b616 4f19 04b6 1652 1904 b614
0000070: 652d b616 53b6 1657 57a7 0008 4d2c b613
0000080: 9ab1
Exception Handler Table:
bci [0, 129] => handler: 124

I receive this exception because I don't know how to properly use the visitFrame method. I've tried to look at the documentation for visitFrame but that didn't help much. Can someone explain how to properly use visitFrame in my scenario? Any help would be greatly appreciated, thanks.

like image 816
Josh M Avatar asked Dec 05 '13 04:12

Josh M


1 Answers

With Java 6 Oracle introduced stack map frames to make run time validation of Java classes easier. (Easier as in easier for the run time, not as in easier for the author of byte code.) The idea of such frames is that you take away some weight of the Java run time validator by telling the validator what kind of values are lying on the operand stack and what values are stored in the local variables at each target of a jump instruction. All this information has to be provided by calling visitFrame when using ASM. This way, the verfifier does not have to infer these values at run time. In Java 6 the existence of stack map frames was optional, in Java 7, stack map frames became mandatory.

Your code contains two byte code level jump instruction:

  • the beginning of the catch block where you would jump to when an exception occures
  • the end of the catch block right before the end of the method where the control flow jumps to when no exception occurs (byte code always contains an explicit return statement while it can be skipped in Java source code)

Therefore, you would have to add a stack map frame after calling:

postMethod.visitJumpInsn(Opcodes.GOTO, gotoEnd);

and before the explicit return statement in the byte code:

postMethod.visitInsn(RETURN);

This stack map frame would then be added to the method's stack map table. Your options are therefore:

  • Either create a Java class with an older class version (for example Java 5 where stack map frames were unknown.
  • Compile the code with a newer Java version (for example 7) and run the ASMifier on this class. You will get an example output considering these stack map frames by calls to MethodVisitor.visitFrame.
  • Disable the part of your verifier that validates the stack map frames by using the JVM option -XX:-UseSplitVerifier.

If you are creating Java classes with a version number indicating a class compiled by a Java 7 compiler, you must create these stack map frames after each target of a jump instruction. Otherwise, the run time verifier will complain what is exactly what you observe.

For the sake of completeness, here is what I get when using ASMifier on your code:

mv = cw.visitMethod(ACC_PUBLIC + ACC_STATIC, "post", "(Ljava/lang/String;Ljava/lang/String;)V", null, null);
mv.visitCode();
Label l0 = new Label();
Label l1 = new Label();
Label l2 = new Label();
mv.visitTryCatchBlock(l0, l1, l2, "java/io/IOException");
mv.visitLabel(l0);
mv.visitTypeInsn(NEW, "java/net/URL");
mv.visitInsn(DUP);
mv.visitLdcInsn("http://urltophpfile.com/phpfile.php");
mv.visitMethodInsn(INVOKESPECIAL, "java/net/URL", "<init>", "(Ljava/lang/String;)V");
mv.visitVarInsn(ASTORE, 2);
mv.visitVarInsn(ALOAD, 2);
mv.visitMethodInsn(INVOKEVIRTUAL, "java/net/URL", "openConnection", "()Ljava/net/URLConnection;");
mv.visitTypeInsn(CHECKCAST, "java/net/HttpURLConnection");
mv.visitVarInsn(ASTORE, 3);
mv.visitVarInsn(ALOAD, 3);
mv.visitLdcInsn(new Integer(60000));
mv.visitMethodInsn(INVOKEVIRTUAL, "java/net/HttpURLConnection", "setReadTimeout", "(I)V");
mv.visitVarInsn(ALOAD, 3);
mv.visitLdcInsn(new Integer(60000));
mv.visitMethodInsn(INVOKEVIRTUAL, "java/net/HttpURLConnection", "setConnectTimeout", "(I)V");
mv.visitVarInsn(ALOAD, 3);
mv.visitInsn(ICONST_1);
mv.visitMethodInsn(INVOKEVIRTUAL, "java/net/HttpURLConnection", "setDoInput", "(Z)V");
mv.visitVarInsn(ALOAD, 3);
mv.visitInsn(ICONST_1);
mv.visitMethodInsn(INVOKEVIRTUAL, "java/net/HttpURLConnection", "setDoOutput", "(Z)V");
mv.visitVarInsn(ALOAD, 3);
mv.visitLdcInsn("POST");
mv.visitMethodInsn(INVOKEVIRTUAL, "java/net/HttpURLConnection", "setRequestMethod", "(Ljava/lang/String;)V");
mv.visitVarInsn(ALOAD, 3);
mv.visitLdcInsn("User-Agent");
mv.visitLdcInsn("useragent");
mv.visitMethodInsn(INVOKEVIRTUAL, "java/net/HttpURLConnection", "addRequestProperty", "(Ljava/lang/String;Ljava/lang/String;)V");
mv.visitTypeInsn(NEW, "java/io/BufferedWriter");
mv.visitInsn(DUP);
mv.visitTypeInsn(NEW, "java/io/OutputStreamWriter");
mv.visitInsn(DUP);
mv.visitVarInsn(ALOAD, 3);
mv.visitMethodInsn(INVOKEVIRTUAL, "java/net/HttpURLConnection", "getOutputStream", "()Ljava/io/OutputStream;");
mv.visitMethodInsn(INVOKESPECIAL, "java/io/OutputStreamWriter", "<init>", "(Ljava/io/OutputStream;)V");
mv.visitMethodInsn(INVOKESPECIAL, "java/io/BufferedWriter", "<init>", "(Ljava/io/Writer;)V");
mv.visitVarInsn(ASTORE, 4);
mv.visitVarInsn(ALOAD, 4);
mv.visitLdcInsn("n=%s&p=%s");
mv.visitInsn(ICONST_2);
mv.visitTypeInsn(ANEWARRAY, "java/lang/Object");
mv.visitInsn(DUP);
mv.visitInsn(ICONST_0);
mv.visitVarInsn(ALOAD, 0);
mv.visitInsn(AASTORE);
mv.visitInsn(DUP);
mv.visitInsn(ICONST_1);
mv.visitVarInsn(ALOAD, 1);
mv.visitInsn(AASTORE);
mv.visitMethodInsn(INVOKESTATIC, "java/lang/String", "format", "(Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/String;");
mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/BufferedWriter", "write", "(Ljava/lang/String;)V");
mv.visitVarInsn(ALOAD, 4);
mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/BufferedWriter", "flush", "()V");
mv.visitVarInsn(ALOAD, 4);
mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/BufferedWriter", "close", "()V");
mv.visitVarInsn(ALOAD, 3);
mv.visitMethodInsn(INVOKEVIRTUAL, "java/net/HttpURLConnection", "getInputStream", "()Ljava/io/InputStream;");
mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/InputStream", "read", "()I");
mv.visitInsn(POP);
mv.visitLabel(l1);
Label l3 = new Label();
mv.visitJumpInsn(GOTO, l3);
mv.visitLabel(l2);
mv.visitFrame(Opcodes.F_SAME1, 0, null, 1, new Object[] {"java/io/IOException"});
mv.visitVarInsn(ASTORE, 2);
mv.visitVarInsn(ALOAD, 2);
mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/IOException", "printStackTrace", "()V");
mv.visitLabel(l3);
mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
mv.visitInsn(RETURN);
mv.visitMaxs(6, 5);
mv.visitEnd();
like image 102
Rafael Winterhalter Avatar answered Nov 02 '22 01:11

Rafael Winterhalter