5

I'm modifying a Java class bytecode through an hexadecimal editor, and I want to force a method to always return true.

  1. Replaced all its bytecode with nops to keep the size intact (original size is 1890).
  2. Execute a pop to restore the stack height since it receives an argument.
  3. Return true with iconst_1 followed by ireturn.
public static boolean test(java.lang.String);
        descriptor: (Ljava/lang/String;)Z
        flags: ACC_PUBLIC, ACC_STATIC
        Code:
          stack=5, locals=12, args_size=1
             0: nop
             1: nop
             2: nop
             3: nop
             4: nop
             [...]
          1886: nop
          1887: nop
          1888: pop
          1889: iconst_1
          1890: ireturn

But when executing it, I'm getting the following error

java.lang.VerifyError: (class: com/example/test/TestBytecode, method: test signature: (Ljava/lang/String;)Z) Inconsistent stack height 0 != 1

NOTE: with or without pop the result is exactly the same.

m0skit0
  • 25,268
  • 11
  • 79
  • 127
  • 2
    Are you sure it is a good idea to modify bytecode in a hex editor? There is a lot of things that can go wrong when doing this. I recommend using a bytecode instrumentation framework like ASM. That way, you can make sure to safely update everything outside the bytecode (attributes, vartables, line number tables, ...) and eliminate the need for 2 KB of `nop` instructions. – Clashsoft Aug 18 '15 at 10:49
  • Thanks, I know, but I want to do it with a hex editor and I don't want to modify the size of the original class. – m0skit0 Aug 18 '15 at 11:28
  • Can you post the entire error? Usually there is more information than just your single line. – Clashsoft Aug 18 '15 at 15:03
  • I don't have it at hand right now but that's the entire error message. The rest is the stacktrace of the call and doesn't contain any relevant information except from where it was called. In the end I'm trying to make it work using an assembly framework and I will check later how to do it manually through an hexeditor (for the sake of learning). – m0skit0 Aug 18 '15 at 20:21

3 Answers3

4

The pop is unnecessary, since the arguments are not on the stack at first. They are only pushed onto the stack when using *load instructions as if they were local variables, which can happen at any time.

Clashsoft
  • 11,553
  • 5
  • 40
  • 79
  • +1, JVM stack is more like a register store (it's a stack machine). Quite different from the C calling convention on a register-based machine. – Marko Topolnik Aug 18 '15 at 10:49
  • Thanks, but without the `pop` I get the exact same error (in fact this is why I put it in the first time). – m0skit0 Aug 18 '15 at 11:26
2

pop pops a value from the stack, but the string that is passed as argument is in "local variable" 0.
You should be able to safely omit that pop.

Also, you should be able to leave out all nops, and instead just replace instruction 0 with iconst_1, instruction 1 with ireturn, and leave the entire rest of the method unchanged.
This way you'd be doing less work and probably even increasing performance.

Siguza
  • 21,155
  • 6
  • 52
  • 89
  • 2
    I don't think replacing just the first two instructions is a good idea. It might be possible that they push values onto the stack, in which case you are again left with the same problem. Performance with `nop` instructions should not be a concern either, since they are likely going to be optimized even by the most primitive interpreter JVM anyway, let alone HotSpot. – Clashsoft Aug 18 '15 at 10:52
  • @Clashsoft The verifier will not realize that the rest is dead code? I thought it was sensitive to actually reachable code paths. – Marko Topolnik Aug 18 '15 at 10:57
  • You're probably right about the `nop`s being optimised out, but how can unchanged instructions push values onto the stack, if they come after an `ireturn`? I don't think they'd ever get run. – Siguza Aug 18 '15 at 10:58
  • They won't ever run, but I don't think the verifier realizes this. For example, if the first instructions are `fconst_0` and `lconst_1` and you replace them with `iconst_1` and `ireturn`, the next instruction will expect a long and a float on the stack, which are obviously not there. – Clashsoft Aug 18 '15 at 11:01
  • Sorry, I forgot to mention that the `pop` doesn't change anything. Without it, the exception is still thrown. – m0skit0 Aug 18 '15 at 11:29
  • The legacy verifier only verifies reachable code, so replacing the beginning will work. The only way it will fail is if there's an exception handler you leave in, or the part you replace straddles an existing instruction, in which case you'll have to nop that instruction out (unreachable code still has to be valid instructions, it's just not type or flow verified). I'm not sure how the stackmap verifier handles dead code. – Antimony Aug 19 '15 at 02:39
  • @Antimony: You’ll have to make sure that the legacy verifier will be used then. It definitely won’t work with the stackmap verifier. Simply because it doesn’t check whether the code is reachable but performs a single pass over the entire code as that’s what the new verifier is about. The instruction after the `return` must have an associated stackmap frame, otherwise the code is rejected. The verifier will use that frame to continue verification according to that frame. Besides that, I’m not sure if every version of the legacy verifier accepts/ignores unreachable code… – Holger Aug 19 '15 at 09:03
  • @Holger The legacy verifier has to ignore unreachable code, because there's no other sensible way to verify it. You can't verifiy something if you don't have any inferred inputs at that point. – Antimony Aug 19 '15 at 15:21
  • @Antimony: there is no way to verify it, but of course the option to simply reject it by throwing a `VerifierError`. That would be a simple, understandable reasoning: if you can’t verify it, don’t accept it… – Holger Aug 19 '15 at 16:22
1

If you are using Java 7 or later, probably the JVM is validating your bytecode with the stack map frames. (Check this question/answer for an explanation of the stack map frames)

If you are using Java 7, try using -XX:-UseSplitVerifier in the command line when you run your class.

If you are using java 8, then you will also have modify the stack map frames; doing so is not trivial, so I better recommend you to use a bytecode manipulation library like javassist.

UPDATE

Based on the comment of @Holger, he is right. However as far as I've seen you are filling unwanted op codes with NOPs rather than removing them.

As you probably already know, the machine instructions are located in an attribute called code; this attribute can have "sub-attributes" (attributes of code itself). One of these is the attribute StackMapTable, which is an "array" (a table) of "stack maps".

Replacing this attribute with zeros won't be enough, you'll have to:

  • set the number of entries of that table (the stack map table) to zero
  • delete the only entry on that table (and offset the following attributes and other fields)

still want to do it by hand? :-)

Community
  • 1
  • 1
morgano
  • 17,210
  • 10
  • 45
  • 56
  • 1
    Actually, you don’t have to modify the stackmap in this special case; you only have to remove it. The reason is that the resulting code does not contain any branches. – Holger Aug 18 '15 at 14:52
  • @Holger Can you please expand? Where do I find the stackmap? – m0skit0 Aug 18 '15 at 20:23
  • Yeah, it was the stack map table. Worked fine when modifying the `class` files using Javassist. And yes, I still want to do it by hand, just to get my hands dirty :) – m0skit0 Aug 19 '15 at 09:01
  • 1
    @m0skit0: See [here](http://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.7.4). It’s an attribute of the `Code` attribute. When using a hex editor, the easiest way to remove it is to rename it to something meaningless, i.e. modify the name index to point to some other string of the constant pool which happens not to be a predefined attribute name. – Holger Aug 19 '15 at 09:08