仅在机器代码中使用可打印的可见ASCII字符在x86 / x86-64中进行不同的分支


14

任务很简单:编写一个程序,仅使用机器代码中的可打印可见ASCII字符0x21 ... 0x7e(不允许使用空格和del)在x86(32位)和x86-64(64位)中分支不同。

  • 不允许有条件的汇编。
  • 不允许使用API​​调用。
  • 不允许使用内核模式(0环)代码。
  • 在Linux或某些其他保护模式的OS中,代码必须在不引起IA-32和x86-64异常的情况下运行。
  • 功能不得依赖于命令行参数。
  • 所有指令必须仅使用0x21 ... 0x7e(十进制33 ... 126)范围内的ASCII字符以机器代码编码。因此,例如。cpuid超出限制(是0f a2),除非您使用自我修改的代码。
  • 相同的二进制代码必须在x86和x86-64中运行,但是由于文件头(ELF / ELF64 / etc。)可能不同,因此您可能需要重新组装并链接它。但是,二进制代码不得更改。
  • 解决方案应该可以在i386 ... Core i7之间的所有处理器上工作,但是我也对更有限的解决方案感兴趣。
  • 该代码必须在32位x86中分支,但不能在x86-64中分支,反之亦然,但不是必须使用条件跳转(也可以接受间接跳转或调用)。分支目标地址必须有一定的空间,可以容纳一些代码,至少2个字节的空间可插入短跳转(jmp rel8)。

胜出的答案是在机器代码中使用最少字节的答案。文件头中的字节(例如ELF / ELF64)不计算在内,分支(出于测试目的等)之后的任何代码字节也不计算在内。

请以ASCII,十六进制字节和注释代码的形式显示您的答案。

我的解决方案,39个字节:

ASCII: fhotfhatfhitfhutfhotfhatfhitfhut_H3<$t!

十六进制: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

码:

; 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

1
您真的在小屋里打了个热帽子:)
aditsu退出了,因为SE邪恶

真好 奇怪,但是很好。由于您将获胜条件设置为“最短”,因此我将标签更改为[code-golf],并添加一些新的描述性标签。如果您不喜欢它们,请告诉我。
dmckee ---前主持人小猫,

Answers:


16

7字节

0000000: 6641 2521 2173 21                        fA%!!s!

作为32位

00000000  6641              inc cx
00000002  2521217321        and eax,0x21732121

作为64位

00000000  6641252121        and ax,0x2121
00000005  7321              jnc 0x28

and清除进位标志,以便64位版本始终跳转。对于64位,6641它是操作数大小的覆盖,后跟是rex.b这样,因此输出的操作数大小为and16位。在32位上,这6641是一条完整的指令,因此and它没有前缀,并且具有32位操作数大小。这将改变and仅在64位模式下执行的两个字节指令所消耗的立即字节数。


1
恭喜您达到1k。
DavidC

此行为是特定于CPU的。某些64位系统将在64位模式下忽略66前缀。
彼得·费里

@peterferrie您是否为此提供参考?我的阅读是,设置REX.W时会忽略66h前缀,但这仅具有REX.B
Geoff Reedy

对不起,我错了。唯一受到这种方式影响的控制转移(例如66 e8不会在Intel上切换到16位IP)。
彼得·费里

7

11字节

ascii: j6Xj3AX,3t!
hex: 6a 36 58 6a 33 41 58 2c 33 74 21

使用以下事实:在32位中,0x41是just inc %ecx,而在64位中,rax前缀是修改以下pop指令的目标寄存器的前缀。

        .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

在OSX上编写此代码,您的汇编程序可能会有所不同。

调用它:

#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;
}

2

7字节

不依赖66前缀。

$$@$Au!

32位:

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

AL将在INC之后设置位0,第二个AND将保留它,分支将被采用。

64位:

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

在第一个AND之后,AL将清零位,该分支将不被采用。


0

如果只有C9h可打印...

32位:

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

ARPL将清除Z标志,从而导致转移。

64位:

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

XOR将设置Z标志,MOVSXD将不会更改它,分支将不会被使用。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.