x86 和 x64 中的 ret 指令有什么区别?

Vor*_*Dev 4 x86 assembly return x86-64

我最近在 x64 上尝试堆栈溢出练习。在 x86 上执行此操作时,我预计垃圾覆盖地址会出现以下情况(例如“AAAA”):

  1. 我提供的数据溢出缓冲区,并覆盖返回地址
  2. 之后ret,(覆盖的)返回地址将(有效)弹出到 EIP 寄存器中
  3. 意识到该地址无效,并引发分段错误

在 x64 中,这似乎有所不同(除了上述步骤中 EIP 与 RIP 的互换之外)。当提供“AAAAAAA”垃圾地址时,处理器似乎会在弹出该地址之前进行一些有效性检查。通过观察,在加载地址之前,似乎要求地址的两个最高有效字节为空。否则,会发生段错误。我相信这是由于 x64 中使用了 48 位寻址,但我的印象是以 0xFFFF 开头的地址也是有效的,但这也会产生段错误。

这是对差异的准确描述吗?为什么这个检查是在数据加载到RIP寄存器之前执行的,而另一个有效性检查是在数据加载到RIP寄存器之后执行的?这些说明之间还有其他差异吗?

编辑:为了澄清我的观察,我注意到当提供 8 字节返回地址时,RIP 仍然指向指令的地址ret,并且 RSP 仍然指向段错误时被覆盖的返回地址。当提供 6 字节返回地址时,当观察到段错误时,覆盖的地址已被弹出到 RIP 中。

Pet*_*des 5

有趣的是,RSP 在故障发生之前没有得到更新。因此,故障并非来自非规范地址的代码提取,而是指令ret尝试将 RIP 设置为非规范地址。

这使得整个 RET 指令出现故障,这意味着它的任何效果都不可见。(因为英特尔的手册没有定义任何部分进度/更新的内容,即使是针对 的故障行为ret。)

不幸的是,英特尔手册中的操作部分ret是一堆条件语句,因为它们使用一个块来记录近和远,以及模式和操作数大小的每一种组合。ret64位模式下的Plain是“IA-32e模式”,操作数大小=64,“near”(不将CS更改为不同的代码段,只是更改RIP)。

这样的话,x86-64正常ret基本上就可以了pop rip
32位模式正常ret基本就可以了pop eip
不多不少。 RIP = *RSP++

也可以看看


请注意,规范范围的上半部分始于0xffff800000000000: 48 位符号扩展至 64。 0xffff7f0000000000不是规范的。高 16 位必须与第 48 位匹配。

一般来说,x86-64 的设计似乎是为了使 RIP 真正可以仅与虚拟地址宽度一样宽,即 48 或 57 位,而无需保存非规范地址。任何将 RIP 设置为非规范值的尝试都会在尝试时出错,而不是在稍后获取代码时出错。