段寄存器¶
最后,汇编语言有一组 16 位寄存器,叫做段寄存器。与其他寄存器不同,段寄存器不用于存储数据;它们用于定位数据。理论上,它们指向隔离的内存段,允许不同类型的数据存储在完全分离的内存段中。这种分段实现由操作系统决定。这些是 x86 段寄存器及其预期用途:
CS(代码段)(the code segment):指向保存应用程序代码的内存。
DS(数据段)(the data segment):指向保存应用程序数据的内存。
ES、FS 和 GS(附加段)(the extra segments):指向操作系统使用的任何专有内存段。
SS(栈段)(the stack segment):指向充当专用调用栈的内存。
在汇编代码中,段寄存器用作内存偏移量操作数的前缀。未指定段寄存器时,默认使用 DS。这意味着命令 PUSH [EBP]实际上与 PUSH DS:[EBP] 相同。但命令 PUSH FS:[EBP] 不同:它从 FS 段读取内存,而不是 DS 段。
如果你仔细看 Windows x86 内存分段实现,你可能会注意到这些段寄存器并未完全按预期使用。要看这一点,你可以在 OllyDbg 附加到暂停进程时,用 OllyDbg 命令行插件运行以下命令:
这个输出告诉我们三件不同的事情。首先,它显示 Windows 只使用三个段:FS、CS 和其他一切。这由 DS、SS、GS 和 ES 相等证明。同样地,这个输出显示 DS、SS、GS 和 ES 都可以互换使用,因为它们都指向相同的内存段。最后,由于 FS 随线程变化,这个输出显示它依赖线程。FS 是一个有趣的段寄存器,它指向某些特定于线程的数据。在第一百二十八页「生产中绕过 ASLR」中,我们将探索如何用 FS 中的数据绕过 ASLR——大多数机器人都需要这样做。
事实上,在编译器为 Windows 生成的汇编代码中,你只会看到三个段被使用:DS、FS 和 SS。有趣的是,即使 CS 似乎与 DS 有恒定偏移,它在用户模式代码中没有真正用途。知道所有这些,你可以进一步得出结论:Windows 只使用两个段:FS 和其他一切。
这两个段实际上指向同一内存中的不同位置(没有简单方法验证,但确实如此),这显示 Windows 实际上根本不使用内存段。相反,它使用扁平内存模型,段寄存器几乎无关紧要。虽然所有段寄存器都指向相同内存,只有 FS 和 CS 指向不同位置,而且 CS 不被使用。
总之,在 Windows 中使用 x86 汇编时,你只需要知道关于段寄存器的三件事。第一,DS、SS、GS 和 ES 可互换,但为清晰起见,访问数据应该用 DS,访问调用栈应该用 SS。第二,CS 可以放心忘记。第三,FS 是唯一有特殊用途的段寄存器;暂时别动它。