如何将ATT汇编转换为Intel语法?在不使用寄存器的情况下跳转到非相对地址?

3

我正在阅读这篇文章 "汇编挑战:不使用寄存器跳转到非相对地址"。

我需要按照他在这里提出的方法(在不使用寄存器的情况下跳转到一个非相对地址)实现,但是我需要用intel语法而不是att。

他为att语法找到的解决方案是:

jmp *0f(%eip)
0: .int 0x12345678

这个在Intel语法中会是什么样子?

那是使用寄存器:%eip。 - Erik Eidt
1
@ErikEidt:x86-64 RIP相关寻址(使用奇怪的地址大小覆盖到32位)实际上并不是在使用寄存器,而只是从指令中进行相对位移。问题在于,RIP相关语法取决于您使用的Intel语法汇编器。例如,NASM jmp dword [rel foo]。 (在32位模式下不可用PC相关寻址) - Peter Cordes
1
所以我检查了链接中的代码。作者建议在32位模式下使用jmp *0f(%eip)。但是它不起作用,因为在32位模式下没有EIP相对寻址可用。反汇编显示,汇编器实际上给出的只是jmp *0x0,即跳转目标从绝对地址0加载,这当然会崩溃,因为空指针引用。(可能有一个重定位要求链接器填写“0f”标签的绝对地址,但如果需要位置无关代码,则这并没有帮助。) - Nate Eldredge
1
就32位模式而言,作者只是感到困惑。对于64位模式,他们提出的 jmp *0f(%rip) ; 0: .quad 0x1234567890 是可以的,但请注意它引用了 rip 而不是 eip(应该这样做,32位地址大小是可能的,但几乎肯定不是您想要的),并使用 .quad 来组装完整的64位地址。 - Nate Eldredge
1
@PeterCordes:是的,看起来这是clang 6及更早版本中的一个错误:https://godbolt.org/z/xW13hEsT8。而readelf输出并没有显示该指令的任何重定位,所以我猜它确实只是尝试从绝对地址0加载。实际上,如果在跳转和标签之间放一些其他垃圾,看起来它会将标签的位移相对于下一条指令的开头,并将其用作绝对地址。所以,是的,我想博客的作者只是看到它被汇编了并称之为好,而没有真正尝试运行它。 - Nate Eldredge
显示剩余5条评论
2个回答

3
这篇博客实际上建议在32位模式下使用jmp *0f(%eip)。但是这是错误的;在32位模式下没有EIP相对寻址,因此这不是有效的32位汇编。看起来clang 6.0及更早版本存在缺陷,它仍然会接受jmp *0f(%eip),但输出显示它实际汇编的指令是jmp *0,即尝试从绝对地址0(而不是*0f,即您放置了一些数据的本地标签的地址)加载跳转目标。这不会起作用,只会崩溃,假设页面0未映射,这在正常操作系统下是情况。
更普遍地说,该错误似乎会导致jmp label(%eip)使用label的位移作为绝对地址,这是从下一条指令中获取的,永远不会有用。即在32位模式下编码,好像EIP相对寻址在工作; 在64位模式下,同样的机器码将使用这4个字节的机器码作为rel32相对位移而不是disp32绝对地址。但是x86-64无法改变32位机器码的工作方式,同时保持向后兼容性。因此,作者在这方面是错误的,可能没有真正测试他们提出的代码。
你标记了 ,所以我认为你实际上对 64 位模式感兴趣。在这种情况下,博客的建议是:
    jmp *0f(%rip)
0:
    .quad 0x1234567890

是有效的。请注意使用64位程序计数器rip和使用.quad获取64位地址。(在这里使用eip实际上是一个有效的指令,对应于0x67地址大小覆盖,但它会导致加载地址被截断为32位,这不太可能是所需的。)

RIP相对寻址的英特尔语法因汇编器而异。在NASM中,您将编写:

    jmp [rel label]
label:
    dq 0x1234567890

其他的汇编器可能会需要一些变化,或者默认情况下将jmp [label]汇编为RIP相关的。请查看您想使用的汇编器的手册。
如果你真的想在32位模式下完成这个任务,那会更难。如果你知道代码段的正确选择器,它在许多操作系统上将是一个固定值,你可以进行直接的远跳转,并将6字节的段/偏移编码直接嵌入指令中。否则,我不立即看到在不使用寄存器或堆栈的情况下完成此操作的方法。
当然,使用堆栈很容易,暂时修改ESP:
push $0xdeadbeef   # push the constant
ret                # pop it into EIP

你链接的博客也错了,写了push 0xdeadbeef,这是一个内存源操作数,从该绝对地址加载4个字节。
下一个例子也有问题,使用mov %eax,0xdeadbeef(将EAX存储到绝对地址),然后jmp %eax(GAS将其汇编为jmp *%eax,警告缺少间接跳转的*)。
看起来他们习惯英特尔语法;.intel_syntax noprefix可以避免转换为AT&T。该博客引用了他们提出的SO问题,其中出现了相同的示例。@fuz在那里的回答确实纠正了AT&T语法。

如果您仍然想使用0:作为标签名称,那么GAS.intel_syntax noprefix版本将是jmp [RIP + 0f]x86-64 GAS Intel-syntax中类似于"[RIP + _a]"的RIP相对变量引用是如何工作的? - Peter Cordes

1
好的,我会按照个人的主要方法来回答这样的问题。
创建一个包含以下内容的文件:
.text
        jmp *0f(%eip)
0:      .int 0x12345678

编译它并检查相同内容的报告(嗯,您的.int被解码为命令)。
$ gcc -c so_72135694.S 
$ objdump -d so_72135694.o

so_72135694.o:     file format elf64-x86-64


Disassembly of section .text:

0000000000000000 <.text>:
   0:   67 ff 25 00 00 00 00    jmpq   *0x0(%eip)        # 0x7
   7:   78 56                   js     0x5f
   9:   34 12                   xor    $0x12,%al

为什么使用gcc而不是直接使用as - 嗯,我太懒了,不想记住as的选项。

然后,称之为Intel风格解码:

$ objdump -d -Mintel-syntax so_72135694.o

so_72135694.o:     file format elf64-x86-64


Disassembly of section .text:

0000000000000000 <.text>:
   0:   67 ff 25 00 00 00 00    jmp    QWORD PTR [eip+0x0]        # 0x7
   7:   78 56                   js     0x5f
   9:   34 12                   xor    al,0x12

让我们回头来比较一下:

$ cat so_72135694.intel.S
.intel_syntax noprefix
.text
        jmp QWORD PTR [eip+0x0]
0:      .int 0x12345678
$ gcc -c so_72135694.intel.S 
$ objdump -d so_72135694.intel.o

so_72135694.intel.o:     file format elf64-x86-64


Disassembly of section .text:

0000000000000000 <.text>:
   0:   67 ff 25 00 00 00 00    jmpq   *0x0(%eip)        # 0x7
   7:   78 56                   js     0x5f
   9:   34 12                   xor    $0x12,%al
$ objdump -d -Mintel-syntax so_72135694.intel.o

so_72135694.intel.o:     file format elf64-x86-64


Disassembly of section .text:

0000000000000000 <.text>:
   0:   67 ff 25 00 00 00 00    jmp    QWORD PTR [eip+0x0]        # 0x7
   7:   78 56                   js     0x5f
   9:   34 12                   xor    al,0x12

你可以很容易地看出它们是相同的,而且你可以针对所有类似的问题使用此方法。

注1:注意,Unix binutils 对于“Intel语法”的解释与Intel本身的想法(甚至在语法基础上,如0x1234 vs. 1234h)以及像NASM或FASM等广泛流行的工具有微小的差异。在这里,我假设如果你谈到AT&T语法,则使用最典型的Binutils包(GNU one)(我的系统是Ubuntu 20.04 / x86-64,几乎是最流行的) 。如果我在这里错了,请随意探索其他工具的细节。

注2:你代码中真正令人困惑的事情是使用EIP相对寻址。这种寻址方式只能用于64位模式,但在这种情况下使用EIP很奇怪。将其编译为32位模式(例如使用.code32)自然会失败。


请注意,地址0x12345678应该是64位。 - Nate Eldredge
尝试使用32位内存间接操作数实际执行jmp失败:jmp dword ptr [RIP + 0f]汇编为66 ff 2d 00 00 00 00 ljmpw *0x0(%rip)-一个16位操作数大小的far跳转,加载新的CS:IP! NASM拒绝汇编jmp dword [rel foo]。这是有道理的,因为https://www.felixcloutier.com/x86/jmp显示,在64位模式下不支持`jmp r/m32`。 - Peter Cordes
此外,使用%eip导致地址大小覆盖可能是OP的错误,应该只使用%rip - Nate Eldredge
@NateEldredge: 是的,它应该是一个64位地址,但问题中的代码似乎是试图将其保持为32位(这是不可能的);我想知道他们是否使用EIP是因为地址大小会暗示内存操作数大小?显然,“pushq imm32” /“ret”会更紧凑,但通过扰乱返回地址预测来降低性能。 - Peter Cordes
1
@PeterCordes:我再次查看了链接,并在问题上添加了一些评论。作者建议在32位模式下使用jmp 0f(%eip),这是完全错误的。对于64位模式,他们使用带有64位地址的jmp 0f(%rip),一切都很合理。 - Nate Eldredge

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接