【问题标题】:What happens if x86 jmp jumps to an address between two other consecutive valid ones?如果 x86 jmp 跳转到另外两个连续有效地址之间的地址会发生什么?
【发布时间】:2021-10-27 09:12:44
【问题描述】:

在 x86 汇编的例程中,如果代码包含指向两个有效地址之间的有效地址的跳转会发生什么?这是一个人为的例子:

0x0001: mov ...
0x0005: add ... 
0x0009: jmp 0x0003

此外,我如何在本地机器或在线上试验类似的东西?我查了https://defuse.ca/online-x86-assembler.htm#disassembly之类的在线x86编辑器,但是它不允许我放像“0x0001”这样的指令地址。

【问题讨论】:

  • jmp label+2label-1 在大多数汇编程序中应该可以正常组装。或jmp .-8 (GAS) 或jmp $-8 (NASM) 相对于jmp 指令的start 跳转。 (不直接设置 rel8 机器码编码,汇编器仍然计算相对于指令末尾的值,因为 x86 机器码就是这样工作的。)

标签: assembly x86 machine-code


【解决方案1】:

没有“有效”或“无效”地址之类的东西。每个地址都可以跳转,如果对应的页面被映射,则执行。

那么当你在指令“之间”跳转时会发生什么?好吧,处理器不知道您打算在哪里开始和结束指令。它只是执行它看到的字节。这段代码将与您的预期不同,因为 CPU 会尝试将其他一些指令的中间部分解析为操作码。

您的具体示例不足以让我说出指令的结果。也许您可以提供一个完整的示例(包括机器代码),以便我可以给出更好的解释。

【讨论】:

    【解决方案2】:

    CPU 将从目标地址开始解码指令。

    您在反汇编中看到的指令流(当使用像 objdump 这样的工具时)只是对程序可执行字节的一种解释,假设给定的起点。

    碰巧“跳到指令中间”是一种混淆技术,有时被恶意软件用来向线性扫描反汇编程序隐藏程序语义(如objdump)。更复杂的反汇编程序将尝试遵循这些“未对齐”的跳转,但这可能是不可能的,这取决于静态/动态可以/不能确定的内容。

    论文"Obfuscation of executable code to improve resistance to static disassembly" by Linn and Debray对此进行了更详细的讨论。

    请参阅第 3.2 节“垃圾插入”。您描述的场景是他们所说的“部分或完全重叠的指令”,即字节流的不同解释可以为重叠的地址范围提供不同的汇编指令。

    【讨论】:

      【解决方案3】:

      我最近在 codegolf 的“使用 x86/x64 机器码打高尔夫球的技巧”中添加了一个关于 skipping instructions 的技巧。您会发现这些是跳入先前指令的一部分的有意应用。而且不仅仅是为了混淆。这是该答案的全文:

      跳过说明

      跳过指令是与一个或多个后续操作码组合的操作码片段。后续的操作码可以用于与前置跳过指令不同的入口点。使用跳过指令而不是无条件短跳转可以节省代码空间,速度更快,并设置诸如NC(无进位)之类的附带状态。

      我的示例都是针对 16 位实/虚拟 86 模式的,但其中很多技术可以类似地用于 16 位保护模式或 32 位或 64 位模式。

      引用from my ACEGALS guide:

      11:跳过指令

      常量 __TEST_IMM8、__TEST_IMM16 和 __TEST_OFS16_IMM8 被定义为这些指令的相应字节串。它们可用于跳过适合以下 1、2 或 3 个字节的后续指令。但是,请注意,它们会修改标志寄存器,包括始终设置 NC。 16 位偏移加 16 位立即测试指令不包括在这些用途中,因为它可能访问段中偏移 0FFFFh 处的字。此外,所提供的 __TEST_OFS16_IMM8 应仅在 86M 中使用,以避免访问超出段限制的数据。在使用这些常量之一的 db 指令之后,括号中应列出哪些指令被跳过。

      86位模式定义in lmacros1.mac 323cc150061e (2021-08-29 21:45:54 +0200)

      %define __TEST_IMM8 0A8h                        ; changes flags, NC
      %define __TEST_IMM16 0A9h                       ; changes flags, NC
                                              ; Longer NOPs require two bytes, like a short jump does.
                                              ; However they execute faster than unconditional jumps.
                                              ; This one reads random data in the stack segment.
                                              ;  (Search for better ones.)
      %define __TEST_OFS16_IMM8 0F6h,86h              ; changes flags, NC
      

      16 位模式下的0F6h,86h 操作码是test byte [bp + disp16], imm8 指令。我相信我实际上并没有在任何地方使用这个。 (实际上,堆栈内存访问实际上可能比无条件短跳转慢。)

      0A8htest al, imm8 在任何模式下的操作码。 0A9h 操作码在 32 位和 64 位模式下更改为 test eax, imm32 形式的指令。

      两个用例in ldosboot boot32.asm 07f4ba0ef8cd (2021-09-10 22:45:32 +0200)

      首先,为一个公共函数链接两个不同的入口点,这两个入口点都需要初始化一个字节大小的寄存器。 mov al, X 指令每条占用 2 个字节,因此 __TEST_IMM16 可用于跳过此类指令。 (如果有两个以上的入口点,则可以重复此模式。)

      error_fsiboot:
              mov al,'I'
      
              db __TEST_IMM16 ; (skip mov)
      read_sector.err:
              mov al, 'R'     ; Disk 'R'ead error
      
      error:
      

      其次,某个入口点需要两个字节的额外拆卸,但可以与后续代码部分的失败案例共享。

                      mov bx, [VAR(para_per_sector)]
                      sub word [VAR(paras_left)], bx
                      jbe @F          ; read enough -->
      
                      loop @BB
                      pop bx
                      pop cx
      
                      call clust_next
                      jnc next_load_cluster
                      inc ax
                      inc ax
                      test al, 8      ; set in 0FFF_FFF8h--0FFF_FFFFh,
                                      ;  clear in 0, 1, and 0FFF_FFF7h
                      jz fsiboot_error_badchain
                      db __TEST_IMM16
      @@:
                      pop bx
                      pop cx
                      call check_enough
                      jmp near word [VAR(fsiboot_table.success)]
      

      这是一个用例 in inicomp lz4.asm 4d568330924c (2021-09-03 16:59:42 +0200),我们依赖于 test al, X 指令清除进位标志:

      .success:
              db __TEST_IMM8                  ; (NC)
      .error:
              stc
              retn
      

      最后,在DOSLFN Version 0.41c (11/2012) 中有一个非常相似的跳过指令用法。他们使用的是mov cx, imm16 而不是test ax, imm16,这对状态标志没有影响,但会破坏cx 寄存器。 (操作码 0B9h 在非 16 位模式下为 mov ecx, imm32,写入完整的 ecxrcx 寄存器。)

      ;THROW-Geschichten... [english: THROW stories...]
      SetErr18:
              mov     al,18
              db      0B9h            ;mov cx,nnnn
      SetErr5:
              mov     al,5
              db      0B9h            ;mov cx,nnnn
      SetErr3:
              mov     al,3
              db      0B9h            ;mov cx,nnnn
      SetErr2:
              mov     al,2
      SetError:
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2010-10-20
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-06-01
        • 2021-09-01
        • 1970-01-01
        • 2020-02-19
        相关资源
        最近更新 更多