【发布时间】:2020-06-13 18:59:44
【问题描述】:
我最近尝试在 x64 上进行堆栈溢出练习。在 x86 上执行此操作时,我希望垃圾覆盖地址(例如“AAAA”)如下所示:
- 我提供的数据溢出缓冲区,覆盖了返回地址
-
ret时,(被覆盖的)返回地址将(有效)弹出到 EIP 寄存器中 - 发现地址无效,引发段错误
在 x64 中,这似乎有所不同(除了上述步骤中 EIP 与 RIP 的互换)。当提供“AAAAAAA”的垃圾地址时,处理器似乎会在弹出地址之前进行一些有效性检查。通过观察,似乎要求地址的两个最高有效字节在加载之前为空。否则,会发生段错误。我相信这是由于在 x64 中使用了 48 位寻址,但我的印象是,以 0xFFFF 开头的地址也是有效的,但这也会产生段错误。
这是对差异的准确描述吗?为什么这个检查是在数据加载到 RIP 寄存器之前执行的,而另一个有效性检查是在之后执行的?这些说明之间还有其他区别吗?
编辑:为了澄清我的观察,我注意到当提供一个 8 字节的返回地址时,RIP 仍然指向 ret 指令的地址,并且 RSP 仍然指向在 segfault 上被覆盖的返回地址。当提供一个 6 字节的返回地址时,当观察到 segfault 时,被覆盖的地址已经被弹出到 RIP 中。
【问题讨论】:
-
检查提前完成,因为
rip寄存器可能甚至没有硬件中的位,因此无法加载。是的,地址范围的顶部也应该是规范的,但您必须使用符号扩展。因此,例如0xffff800000000000是规范的,将被加载到rip中,之后只会出错:)0xffff4141414141414141不是规范的。 -
以
0xffff开头的地址仅在内核模式afaik 中有效。 -
@fuz:它们不会自动无效(非规范),它们仅受正常页表机制的保护。 (例如,内核在 PTE 中设置 U/S 位,以便大多数/所有这些虚拟页面都是主管专用的)。例如,Linux 将
ffffffffff600000-ffffffffff601000范围映射到用户空间进程作为[vsyscall]页面。 (cat /proc/self/maps)。 -
@Vortix:你到底声称发生了什么?那个RSP没有更新还是什么?您如何区分从导致段错误的无效页面获取代码与尝试将非规范地址加载到 RIP 中?
ret手册的操作部分因retf(远)的混乱而变得复杂,但 IA-32E-MODE-RETURN-TO-SAME-PRIVILEGE-LEVEL: 包括IF the return instruction pointer is not within canonical address space THEN #GP(0); FI;。而不是#PF,而是GP或无效页面错误都让内核传递SIGSEGV -
你应该edit你的问题说得更清楚。有趣的是,RSP 在故障之前没有得到更新。因此,错误不是从非规范地址提取代码,而是
ret指令尝试将 RIP 设置为非规范地址。这使得整个 RET 指令出错,这意味着它的任何效果都不可见。
标签: assembly x86 return x86-64