Assembly Is Too High-Level: Consistent Instruction Sizes 1


AVX

YES! I want that. I want ALL my instructionz to be 15 bytes. Forget that this part of the manual is talking about AVX instructions, I don’t care about that; I just want 15 byte instructions, so when I look at machine code in a debugger, it doesn’t look all jagged and ugly.

What do I mean by ugly? The below code is just a PoC that launches /bin/sh with the execve() syscall (like a lot of my weird PoC’s). Notice the middle column, made up of 2, 3, 4, and 5 byte instructions. It’s just an eyesore that I can’t have anything to do with! It would be much better if these were all the same, and super large 15-byte instructions.

no67s

Below is what that looks like. Notice that the assembly language part (right-most column) is completely identical!

67s

Ahh, that looks much better.

 

WAT?

Ok, so what did I just do? I abused the operand-size override prefix (66) and the address-size override prefix (67). Before explaining the abuse (which may even already be obvious what I’m doing). Let’s look at the behavior of the prefixes in use. We will demonstrate with the XOR instruction that normally takes a 32-bit register/memory_address as a first operand, and a register as a second. But notice that the Intel manual uses the same opcode (31) for the 16-bit version as well:

xor1

Note, for the below examples, I am doing this on a 64-bit machine…

Let’s just ¬†XOR [rax] with eax:

xor

Just a standard (31) XOR where [rax], eax is encoded as just 00 on the ModR/M byte. So as stated above, when prefixing with 66, this will override the operand size. In this case, it is referring the the last operand (not register/location where the result is saved). Let’s see what happens when we put a 66 byte in front of the same machine code:

xor66

So we see the 32-bit eax register gets overrode (is that a word?) to a 16-bit ax register. Now let’s try 67, the address (in this case [rax]) is supposed to get overridden (how about this?), using the 67 prefix shows that the [rax] address is overroded (haha) to [eax] instead (demonstrating that these prefixes aren’t strictly a 32/16 bit thing; but is contextual to the processor being run on):

xor67

 

Abuse

In assembly, when typing XOR [rax], ax, the assembler knows to just put the 66 in front of the machine code that would otherwise be XOR [rax], eax. That sounds too damn high-level to me. As a person that doesn’t know machine-code, they might not know that this is something that happens under the hood. Assuming I haven’t read all 3 volumes of the Intel manual (not true), I might assume that 66 or 67 by itself could not be interpreted as an opcode/operation (this is true). If I were to execute a nop right before a prefixed XOR (as above), it would look like 90 66 31 00. The thing before our prefix (66) is a nop (90), not a prefix, but an entirely other instruction. In fact, when programming in assembly language, anything before this 66 would be part of the previous instruction…but what would happen if we just made it another 66 or 67 (aside from using a db like assembler feature, there is no assembly for this). The answer to this question is that you can put as many extra prefixes in front of your machine code as you want (without it affecting the logical operation any differently, aside from the changes the prefixing would make for the first prefix), so long as the entire instruction does not exceed 15 bytes. So that’s what was going on in the first example above.

…And I know what you’re thinking, so here’s what a 16-byte instruction looks like and does:

16

 


Leave a comment

Your email address will not be published. Required fields are marked *

One thought on “Assembly Is Too High-Level: Consistent Instruction Sizes