isX64 Gem

I needed a multi-arch shellcode for both x86 and x64 in the same code. Suppose you want to attack a platform, which can either be x86 or x64 where you don’t know in advance which it is. The problem is which version you really need to use at runtime then, right?

This is a tiny trick I’ve been using for a long while now which tells whether you run on x64 or not:

XOR EAX, EAX
INC EAX ; = DB 0x40
NOP
JZ x64_code
x86_code:
bits 32
.
.
.
RET
x64_code:
bits 64
.
.

The idea is very simple, since x64 and x86 share most opcodes’ values, there is a small in-similarity with the range of 0x40-0x50, in x86 it used for one byte INC and DEC opcodes. Since there’re 8 GPRs (General Purpose Register), and 2 opcodes, it spans over the whole range of 0x40-0x50.
Now when AMD64’s ISA (Instruction Set Architecture) was designed, they added another set of 8 GPRs, making it a total of whopping 16 GPRs. In a world where x86 ruled, you only needed 3 bits in the ModRM byte (some byte in the instruction that tells the processor how to read its operands) to access a specific register from 0 to 8. With the new ISA, an extra bit was required in order to be able to address all 16 registers. Therefore, a new prefix (called the REX prefix) was added to solve this problem with an extra bit (and there’s more to it, not relevant for now). The new prefix used the range of 0x40-0x50, thus eliminating old one byte INC/DEC (no worries however, now compilers use the 2 bytes existent variation for these instructions).

Back to our assembly code, it depends on the fact that in x86 the INC EAX, really increments EAX by one, and so it will become 1 if the code runs on x86. And when it’s run on x64, it becomes a prefix to the NOP instruction, which doesn’t do anything anyway. And hence, EAX stays zero. Just a final note for the inexperienced that in x64, operations on 32 bit registers are automatically promoted to 64 bit registers, so RAX is also 0.

13 Responses to “isX64 Gem”

  1. Ange says:

    elegant, and handy.

  2. Ange says:

    (didn’t test) can’t CS value tell you the answer directly btw ?

  3. Myria says:

    @Ange
    Well, if you’re willing to hardcode the expected segment values, yes. There are other ways, like using the “lar” instruction on the CS selector and checking for the “long” bit.

  4. arkon says:

    push cs; pop ax would work (0x23 and 0x33), but my code is platform independent.

  5. ziggz says:

    neat trick!

  6. Myria says:

    A similar trick might also work with the “ARPL” “MOVSXD” instructions.

  7. Peter Ferrie says:

    Yes, Ange used that trick in another context, but using it here would end up being longer than Arkon’s version because you’d need an explicit test afterwards.

  8. SkyLined says:

    Nice trick, thanks for sharing!

    I’ve recently rewritten my x86 Windows shellcode that executes calc.exe on all versions of Windows, on all Service Packs using tips from Peter Ferrie. I created a x64 version as well and, using this trick, I created one shellcode that runs on all versions, Service Packs and architectures (x86 and x64) of Windows. See http://code.google.com/p/win-exec-calc-shellcode/ for source and binaries.

  9. Noobieboobie says:

    Sounds cool, but i have some questions. (i’m pretty new to low-level fun)

    How do you assemble this code? Can you decide to which instruction set it’s assembled? How can you write to 2 different instruction sets in one code?

    It would make sense to me to also put the isX64 part under ‘bits 32’, is it?

    Your blog is incredible, i wish you would update more frequently!
    Thank you :)

  10. […] NOTE: This difference in the opcode translation is leveraged in a very neat trick in order to make your shellcode architecture independent. Read more here. […]

  11. hatter says:

    Nice trick! Good for binary – if you need an alphanumeric one, check this out http://www.blackhatlibrary.net/Architecture_detection_shellcode – mind if I add your method to our shellcode page(s)?

  12. Peter Ferrie says:

    I finally noticed the obvious 5 bytes version:
    XOR ECX, ECX
    INC ECX ; = DB 0×41
    LOOP x64_code

    ecx is returned to 0 in 32-bit mode, and the inc/loop combination becomes the equivalent of loopq in 64-bit mode, reducing rcx to -1 and taking the branch. That’s false and true values right there. ;-)

  13. greg says:

    this is neat :) I know I’m a few years late to the party but just discovered this blog and have been going through all the posts :D.

Leave a Reply