【问题标题】:x86-32 / x86-64 polyglot machine-code fragment that detects 64bit mode at run-time?在运行时检测 64 位模式的 x86-32 / x86-64 多语言机器代码片段?
【发布时间】:2023-03-22 10:06:01
【问题描述】:

相同字节的机器码是否有可能判断它们是在 32 位还是 64 位模式下运行,然后做不同的事情?

即写polyglot机器码。

通常您可以在构建时使用#ifdef 宏进行检测。或者在 C 语言中,您可以编写一个 if() 以编译时常量作为条件,并让编译器优化它的另一面。

这仅对奇怪的情况有用,例如代码注入,或者只是为了看看它是否可能。


另请参阅:polyglot ARM / x86 machine code 以根据正在解码字节的架构分支到不同的地址。

【问题讨论】:

    标签: assembly x86


    【解决方案1】:

    最简单的方法是使用单字节inc 操作码,这些操作码在 64 位模式下被重新用作 REX 前缀。 REX 前缀对jcc 没有影响,所以你可以这样做:

    xor    eax,eax       ; clear ZF
    db  0x40             ; 32bit: inc eax.   64bit: useless REX prefix
    jz   .64bit_mode     ; REX jcc  works fine
    

    另请参阅根据其执行模式返回 16、32 或 64 的 3 路多语言:codegolf.SE 上的Determine your language's version


    提醒:通常您不希望将其作为已编译二进制文件的一部分。在构建时检测模式,因此任何基于此的决策都可以优化而不是在运行时完成。例如使用 #ifdef __x86_64__ 和/或 sizeof(void*)(但不要忘记 ILP32 x32 ABI 在长模式下具有 32 位指针)。


    这是一个完整的 Linux/NASM 程序,如果以 64 位运行,则使用 syscallexit(1),如果以 32 位运行,则使用 int 0x80exit(0)

    使用 BITS 32 和 BITS 64 可确保它以任何一种方式汇编成相同的机器代码。 (是的,我检查了objdump -d 以显示原始机器代码字节)

    即便如此,我还是使用了db 0x40 而不是inc eax,以便更清楚地了解有什么特别之处。

    BITS 32
    global _start
    _start:
            xor    eax,eax          ; clear ZF
            db 0x40                 ; 32bit: inc eax.  64bit: useless REX prefix
            jz      .64bit_mode     ; REX jcc  still works
    
            ;jmp .64bit_mode   ; uncomment to test that the 64bit code does fault in a 32bit binary
    
    .32bit_mode:
            xor     ebx,ebx
            mov     eax, 1          ; exit(0)
            int     0x80
    
    
    BITS 64
    .64bit_mode:
            lea  rdx, [rel _start]      ; An instruction that won't assemble in 32-bit mode.
            ;; arbitrary 64bit code here
    
            mov  edi, 1
            mov  eax, 231    ;  exit_group(1).
            syscall          ; This does SIGILL if this is run in 32bit mode on Intel CPUs
    

    ;;;;; Or as a callable function:
    BITS 32
    am_i_32bit:  ;; returns false only in 64bit mode
            xor     eax,eax
    
            db 0x40                 ; 32bit: inc eax
                                    ; 64bit: REX.W=0
            ;nop                     ; REX nop  is  REX xchg eax,eax
            ret                     ; REX ret works normally, too
    

    经过测试和工作。我构建了两次以获得围绕相同机器代码的不同 ELF 元数据。

    $ yasm -felf64 -Worphan-labels -gdwarf2 x86-polyglot-32-64.asm && ld -o x86-polyglot.64bit x86-polyglot-32-64.o
    $ yasm -felf32 -Worphan-labels -gdwarf2 x86-polyglot-32-64.asm && ld -melf_i386 -o x86-polyglot.32bit x86-polyglot-32-64.o
    $ ./x86-polyglot.32bit && echo 32bit || echo 64bit
    32bit
    $ ./x86-polyglot.64bit && echo 32bit || echo 64bit
    64bit
    

    (来自Assembling 32-bit binaries on a 64-bit system (GNU toolchain) 的构建命令,链接自 标签wiki 中的FAQ 部分)。

    【讨论】:

    • 轻微修正:syscall 适用于大多数 32 位模式的 AMD cpu。
    • @Jester:谢谢,我想知道为什么它在 32 位模式下毫无怨言地反汇编,并在早期版本的代码中组装。但它确实对我有用(在英特尔 Merom 上),以确认我在 32 位模式下运行错误的分支得到了 SIGILL。 (lea 只是解码为 dec 和 lea 具有不同但仍然有效的寻址模式。)无论如何,修复评论:)
    • @Jester:是的,但我认为他们只是将它作为 AMD64 的一部分添加,而不是作为 32 位模式下的新指令。 (我通常只看英特尔的 insn 集参考,它只是在兼容/旧版模式下说“无效”,没有脚注,所以我想这就是我得出这个结论的方式。)
    • 没有检查历史,但我认为syscall/sysenter 出现在 64 位之前。
    • 显然 32 位 syscall 规范已于 1997 年 5 月发布为 SYSCALL 和 SYSRET 指令规范应用说明,订单号 21086,比邮件列表讨论早了三年链接 64 位代码,32 位 AMD K6-2 系列处理器从 1998 年开始支持它。PS:是的,它在 32 位模式下的工作方式不同。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-10-20
    • 2011-08-26
    • 2015-01-20
    • 1970-01-01
    • 2015-06-20
    • 1970-01-01
    • 2023-03-27
    相关资源
    最近更新 更多