为什么这个内存地址 %fs:0x28 ( fs[0x28] ) 有一个随机值?

55

我写了一段C代码,然后我反汇编它并阅读寄存器,以便理解程序在汇编中的工作方式。

int test(char *this){
    char sum_buf[6];
    strncpy(sum_buf,this,32);
    return 0;
}

我一直在检查的代码片段是测试函数。当我反汇编我的测试函数输出时,我得到了...

   0x00000000004005c0 <+12>:        mov    %fs:0x28,%rax
=> 0x00000000004005c9 <+21>:        mov    %rax,-0x8(%rbp)
... stuff ..
   0x00000000004005f0 <+60>:        xor    %fs:0x28,%rdx
   0x00000000004005f9 <+69>:        je     0x400600 <test+76>
   0x00000000004005fb <+71>:        callq  0x4004a0 <__stack_chk_fail@plt>
   0x0000000000400600 <+76>:        leaveq 
   0x0000000000400601 <+77>:        retq 

我想知道mov %fs:0x28,%rax实际上是做什么的?


相关:https://dev59.com/CnM_5IYBdhLWcg3wgzhl - Ciro Santilli OurBigBook.com
3个回答

93
FSGS寄存器可以用作基指针地址,以访问特殊的操作系统数据结构。所以你看到的是从FS寄存器中保存的值的偏移量处加载的值,而不是对FS寄存器内容的位操作。 具体来说,在Linux上正在发生的是,FS:0x28存储了一个特殊的哨兵堆栈保护值,代码正在执行堆栈保护检查。例如,如果您在代码中进一步查看,就会发现FS:0x28处的值被存储在堆栈上,然后重新调用堆栈的内容,并对原始FS:0x28处的值执行XOR操作。如果两个值相等,也就是零位已设置,因为XOR两个相同的值的结果为零值,则跳转到test例程;否则,跳转到一个特殊函数,表明堆栈某些地方已经被破坏了,且堆栈上存储的哨兵值已更改。 如果使用GCC,可以通过这种方式禁用
-fno-stack-protector

好的,那很有道理。我该如何使用gdb读取这个地址和值? - Dr.Knowitall
4
最简单的方法是在MOV操作之后直接查看RAX寄存器的内容。 - Jason
3
分段寻址不再使用 — 这是不正确的,分段寻址仍然被100%使用,尽管用途已经发生了巨大变化,变成了虚拟空间而不是段。由于我们可以使用64位单个寄存器寻址整个内存(虽然在大多数情况下只用到48位),因此我们不修改这些寄存器,但它们与MMU和内存保护方案的联系非常重要。 - Alexis Wilke
1
嘿,我知道这已经很老了,但我想问为什么%fs:0x28必须通过rax,然后再添加到堆栈中?为什么我们不能直接像mov %fs:0x28, -0x8(%rbp)一样添加它呢? - need_to_know_now
6
这是一段关于x86架构指令的讨论,意思是无法直接进行内存到内存的转移操作,需要通过先加载到寄存器再用第二个指令存储的方式来实现。如果尝试汇编这样的指令,会出现错误提示。 - Nate Eldredge

0
glibc:
  uintptr_t stack_chk_guard = _dl_setup_stack_chk_guard (_dl_random);
# ifdef THREAD_SET_STACK_GUARD
  THREAD_SET_STACK_GUARD (stack_chk_guard); 

the _dl_random from kernel.

-3

15
这只是不正确的。 fs 不是一个“普通”的寄存器,而是一个段寄存器。在保护模式下,fs 是指向 GDT 的选择器。与之关联的有隐藏的“基地址”和“限制”寄存器,你看不见。因此,fs:0x28 实际上是 [hidden_fs_base + 0x28] - Jonathon Reinhart

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