x86 和 x64 中的 ret 指令有什么区别?
What is the difference between the ret instruction in x86 and x64?
我最近在 x64 上尝试堆栈溢出练习。在 x86 上执行此操作时,我希望垃圾覆盖地址如下(例如 'AAAA'):
- 我提供的数据溢出缓冲区,覆盖return地址
- 在
ret
之后,(被覆盖的)return 地址将(有效地)弹出到 EIP 寄存器中
- 意识到地址无效,并引发分段错误
在 x64 中,这似乎有所不同(除了上述步骤中 EIP 与 RIP 的交换)。当提供 'AAAAAAA' 的垃圾地址时,处理器似乎会在 弹出地址之前进行一些有效性检查 。通过观察,似乎要求地址的两个最高有效字节在加载之前为空。否则,会发生段错误。我相信这是由于在 x64 中使用了 48 位寻址,但我的印象是以 0xFFFF 开头的地址也是有效的,但这也会产生段错误。
这是对差异的准确描述吗?为什么这个检查在数据加载到RIP寄存器之前执行,而另一个有效性检查在之后执行?这些说明之间还有其他区别吗?
编辑:为了澄清我的观察,我注意到当提供 8 字节 return 地址时,RIP 仍然指向 ret
指令的地址,RSP 仍然指向到段错误时被覆盖的 return 地址。当提供 6 字节 return 地址时,当观察到段错误时,覆盖的地址已弹出到 RIP 中。
有趣的是 RSP 在故障发生前没有得到更新。所以它不是从错误的非规范地址获取代码,而是 ret
指令试图将 RIP 设置为非规范地址。
这使得整个 RET 指令出错,这意味着它的 none 效果是可见的。 (因为 Intel's manual 没有定义任何部分进度/更新的内容,即使是 ret
的错误行为也是如此。)
不幸的是,英特尔手册中 ret
的操作部分是一堆条件语句,因为它们使用一个块来记录 near 和 far,以及模式和操作数大小的每种组合。 Plain ret
在 64 位模式下是“IA-32e 模式”,operand-size=64,和“near”(不将 CS 更改为不同的代码段,只是更改 RIP)。
在那种情况下,x86-64 normal ret
基本上是 pop rip
.
32位模式正常ret
基本上就是pop eip
.
仅此而已。 RIP = *RSP++
.
另见
- Does it matter where the ret instruction is called in a procedure in x86 assembly
- What is the x86 "ret" instruction equivalent to?
- How can I simulate a CALL instruction by using JMP?
请注意,规范范围的上半部分从 0xffff800000000000
开始:48 位 符号扩展 到 64。0xffff7f0000000000
不是规范的。高 16 位必须匹配位 48.
一般来说,x86-64 似乎被设计成 RIP 只能真正像虚拟地址宽度一样宽,48 或 57 位,永远不需要保存非规范地址。任何将 RIP 设置为非规范值的尝试都会在尝试时出错,而不是稍后在代码获取时出错。
我最近在 x64 上尝试堆栈溢出练习。在 x86 上执行此操作时,我希望垃圾覆盖地址如下(例如 'AAAA'):
- 我提供的数据溢出缓冲区,覆盖return地址
- 在
ret
之后,(被覆盖的)return 地址将(有效地)弹出到 EIP 寄存器中 - 意识到地址无效,并引发分段错误
在 x64 中,这似乎有所不同(除了上述步骤中 EIP 与 RIP 的交换)。当提供 'AAAAAAA' 的垃圾地址时,处理器似乎会在 弹出地址之前进行一些有效性检查 。通过观察,似乎要求地址的两个最高有效字节在加载之前为空。否则,会发生段错误。我相信这是由于在 x64 中使用了 48 位寻址,但我的印象是以 0xFFFF 开头的地址也是有效的,但这也会产生段错误。
这是对差异的准确描述吗?为什么这个检查在数据加载到RIP寄存器之前执行,而另一个有效性检查在之后执行?这些说明之间还有其他区别吗?
编辑:为了澄清我的观察,我注意到当提供 8 字节 return 地址时,RIP 仍然指向 ret
指令的地址,RSP 仍然指向到段错误时被覆盖的 return 地址。当提供 6 字节 return 地址时,当观察到段错误时,覆盖的地址已弹出到 RIP 中。
有趣的是 RSP 在故障发生前没有得到更新。所以它不是从错误的非规范地址获取代码,而是 ret
指令试图将 RIP 设置为非规范地址。
这使得整个 RET 指令出错,这意味着它的 none 效果是可见的。 (因为 Intel's manual 没有定义任何部分进度/更新的内容,即使是 ret
的错误行为也是如此。)
不幸的是,英特尔手册中 ret
的操作部分是一堆条件语句,因为它们使用一个块来记录 near 和 far,以及模式和操作数大小的每种组合。 Plain ret
在 64 位模式下是“IA-32e 模式”,operand-size=64,和“near”(不将 CS 更改为不同的代码段,只是更改 RIP)。
在那种情况下,x86-64 normal ret
基本上是 pop rip
.
32位模式正常ret
基本上就是pop eip
.
仅此而已。 RIP = *RSP++
.
另见
- Does it matter where the ret instruction is called in a procedure in x86 assembly
- What is the x86 "ret" instruction equivalent to?
- How can I simulate a CALL instruction by using JMP?
请注意,规范范围的上半部分从 0xffff800000000000
开始:48 位 符号扩展 到 64。0xffff7f0000000000
不是规范的。高 16 位必须匹配位 48.
一般来说,x86-64 似乎被设计成 RIP 只能真正像虚拟地址宽度一样宽,48 或 57 位,永远不需要保存非规范地址。任何将 RIP 设置为非规范值的尝试都会在尝试时出错,而不是稍后在代码获取时出错。