# Branch differently in x86 / x86-64 using only printable visible ASCII characters in the machine code

The task is simple: write a program that branches differently in x86 (32-bit) and x86-64 (64-bit) using only printable visible ASCII characters 0x21...0x7e (space and del are not allowed) in the machine code.

• Conditional assembly is not allowed.
• Using API calls in not allowed.
• Using kernel-mode (ring 0) code is not allowed.
• The code must run without causing exceptions in both IA-32 and x86-64 in Linux or in some other protected mode OS.
• The functioning must not depend on command line parameters.
• All instructions must be encoded in machine code using only ASCII characters in the range 0x21...0x7e (33...126 decimal). So eg. cpuid is out of limits (it's 0f a2), unless you use self-modifying code.
• The same binary code must run in x86 and x86-64, but as file headers (ELF/ELF64/etc.) may be different, you may need to assemble and link it again. However, the binary code must not change.
• Solutions should work on all processors between i386...Core i7, but I'm interested also in more limited solutions.
• The code must branch in 32-bit x86 but not in x86-64, or vice versa, but using conditional jumps is not a requirement (indirect jump or call is also accepted). The branch target address must be such that there is space for some code, at least 2 bytes of space in which a short jump (jmp rel8) fits in.

The winning answer is the one that uses least bytes in the machine code. The bytes in the file header (ELF/ELF64 for example) are not counted, and any bytes of code after the branch (for testing purposes etc.) aren't counted neither.

My solution, 39 bytes:

ASCII: fhotfhatfhitfhutfhotfhatfhitfhut_H3<$t! hexadecimal: 66 68 6F 74 66 68 61 74 66 68 69 74 66 68 75 74 66 68 6F 74 66 68 61 74 66 68 69 74 66 68 75 74 5F 48 33 3C 24 74 21. Code: ; can be compiled eg. with yasm. ; yasm & ld: ; yasm -f elf64 -m amd64 -g dwarf2 x86_x86_64_branch.asm -o x86_x86_64_branch.o; ld x86_x86_64_branch.o -o x86_x86_64_branch ; yasm & gcc: ; yasm -f elf64 -m amd64 -g dwarf2 x86_x86_64_branch.asm -o x86_x86_64_branch.o; gcc -o x86_x86_64_branch x86_x86_64_branch.o section .text global main extern printf main: push word 0x746f ; 66 68 6f 74 (x86, x86-64) push word 0x7461 ; 66 68 61 74 (x86, x86-64) push word 0x7469 ; 66 68 69 74 (x86, x86-64) push word 0x7475 ; 66 68 75 74 (x86, x86-64) push word 0x746f ; 66 68 6f 74 (x86, x86-64) push word 0x7461 ; 66 68 61 74 (x86, x86-64) push word 0x7469 ; 66 68 69 74 (x86, x86-64) push word 0x7475 ; 66 68 75 74 (x86, x86-64) db 0x5f ; x86: pop edi ; x86-64: pop rdi db 0x48, 0x33, 0x3c, 0x24 ; x86: ; 48 dec eax ; 33 3c 24 xor edi,[esp] ; x86-64: ; 48 33 3c 24 xor rdi,[rsp] jz @bits_64 ; 0x74 0x21 ; branch only if running in 64-bit mode. ; the code golf part ends here, 39 bytes so far. ; the rest is for testing only, and does not affect the answer. nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop jmp @bits_32 @bits_64: db 0x55 ; push rbp db 0x48, 0x89, 0xe5 ; mov rbp,rsp db 0x48, 0x8d, 0x3c, 0x25 ; lea rdi, dd printf_msg ; [printf_msg] xor eax,eax mov esi,64 call printf db 0x5d ; pop rbp NR_exit equ 60 xor edi,edi mov eax,NR_exit ; number of syscall (60) syscall @bits_32: lea edi,[printf_msg] mov esi,32 call printf mov eax,NR_exit int 0x80 section .data printf_msg: db "running in %d-bit system", 0x0a, 0  • You really hit the hot hat in the hut :) – aditsu Mar 3 '13 at 17:39 • Nice. Odd, but nice. Since you are setting the winning condition as "shortest" I'm going to change the tag to [code-golf] and add a some new descriptive tags. If you don't like them, let me know. – dmckee Mar 3 '13 at 18:59 ## 4 Answers # 7 bytes 0000000: 6641 2521 2173 21 fA%!!s!  As 32 bit 00000000 6641 inc cx 00000002 2521217321 and eax,0x21732121  As 64 bit 00000000 6641252121 and ax,0x2121 00000005 7321 jnc 0x28  and clears the carry flag so the 64 bit version always jumps. For 64-bit the 6641 is the operand size override followed by rex.b so the operand size for the and comes out as 16 bit. On 32-bit the 6641 is a complete instruction so the and has no prefix and has a 32-bit operand size. This changes the number of immediate bytes consumed by the and giving two bytes of instructions that are only executed in 64-bit mode. • Congrats on reaching 1k. – DavidC Mar 6 '13 at 13:27 • this behaviour is CPU specific. Some 64-bit systems will ignore the 66 prefix in 64-bit mode. – peter ferrie Nov 26 '17 at 2:20 • @peterferrie Do you have a reference for that? My reading is that a 66h prefix is ignored when REX.W is set but this only has REX.B – Geoff Reedy Nov 26 '17 at 3:39 • excuse me, I'm wrong. It's only transfer-of-control that's affected that way (e.g. 66 e8 doesn't switch to 16-bit IP on Intel). – peter ferrie Nov 26 '17 at 4:16 ## 11 bytes ascii: j6Xj3AX,3t! hex: 6a 36 58 6a 33 41 58 2c 33 74 21  Uses the fact that in 32-bit, 0x41 is just inc %ecx, whereas in 64-bit it is the rax prefix that modifies the target register of the following pop instruction.  .globl _check64 _check64: .byte 0x6a, 0x36 # push$0x36
.byte   0x58            # pop %rax
.byte   0x6a, 0x33      # push $0x33 # this is either "inc %ecx; pop %eax" in 32-bit, or "pop %r8" in 64-bit. # so in 32-bit it sets eax to 0x33, in 64-bit it leaves rax unchanged at 0x36. .byte 0x41 # 32: "inc %ecx", 64: "rax prefix" .byte 0x58 # 32: "pop %eax", 64: "pop %r8" .byte 0x2c, 0x33 # sub$0x33,%al
.byte   0x74, 0x21      # je (branches if 32 bit)

mov     $1,%eax ret nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop mov$0,%eax
ret


Wrote this on OSX, your assembler might be different.

Call it with this:

#include <stdio.h>
extern int check64(void);
int main(int argc, char *argv[]) {
if (check64()) {
printf("64-bit\n");
} else {
printf("32-bit\n");
}
return 0;
}


## 7 bytes

Not relying on 66 prefix.

@\$Au!

32-bit:

00000000 24 24 and al,24h
00000002 40    inc eax
00000003 24 41 and al,41h
00000005 75 21 jne 00000028h

AL will have bit 0 set after the INC, the second AND will preserve it, the branch will be taken.

64-bit:

00000000 24 24    and al,24h
00000002 40 24 41 and al,41h
00000005 75 21    jne 00000028h

AL will have bit 0 clear after the first AND, the branch will not be taken.

If only C9h were printable...

32-bit:

00000000 33 C9 xor  ecx, ecx
00000002 63 C9 arpl ecx, ecx
00000004 74 21 je   00000027h

The ARPL will clear the Z flag, causing the branch to be taken.

64-bit:

00000000 33 C9 xor    ecx, ecx
00000002 63 C9 movsxd ecx, ecx
00000004 74 21 je     00000027h

The XOR will set the Z flag, the MOVSXD will not change it, the branch will not be taken.