【问题标题】:what does this instruction do?:- mov %gs:0x14,%eax这条指令是做什么的?:- mov %gs:0x14,%eax
【发布时间】:2012-08-27 10:06:15
【问题描述】:

我向大家问好。我有一个 C 程序,基本上是为测试缓冲区溢出而编写的。

    #include<stdio.h>
    void display()
    {
            char buff[8];
            gets(buff);
            puts(buff);
    }
    main()
    {
        display();
        return(0);
    }

现在我使用 GDB 分解显示和主要部分。代码:-

函数 main 的汇编代码转储:

    0x080484ae <+0>:    push   %ebp        # saving ebp to stack
    0x080484af <+1>:    mov    %esp,%ebp   # saving esp in ebp
    0x080484b1 <+3>:    call   0x8048474 <display>   # calling display function
    0x080484b6 <+8>:    mov    $0x0,%eax   # move 0 into eax , but WHY ????
    0x080484bb <+13>:   pop    %ebp        # remove ebp from stack
    0x080484bc <+14>:   ret                # return

汇编程序转储结束。

函数显示的汇编代码转储:

    0x08048474 <+0>:    push   %ebp          #saves ebp to stack        
    0x08048475 <+1>:    mov    %esp,%ebp     # saves esp to ebp
    0x08048477 <+3>:    sub    $0x10,%esp    # making 16 bytes space in stack
    0x0804847a <+6>:    mov    %gs:0x14,%eax  # what does it mean ????
    0x08048480 <+12>:   mov    %eax,-0x4(%ebp) # move eax contents to 4 bytes lower in stack
    0x08048483 <+15>:   xor    %eax,%eax       # xor eax with itself (but WHY??)
    0x08048485 <+17>:   lea    -0xc(%ebp),%eax  #Load effective address of 12 bytes 
                                              lower placed value ( WHY???? )

    0x08048488 <+20>:   mov    %eax,(%esp)      #make esp point to the address inside of eax
    0x0804848b <+23>:   call   0x8048374 <gets@plt>  # calling get, what is "@plt" ????
    0x08048490 <+28>:   lea    -0xc(%ebp),%eax       # LEA of 12 bytes lower to eax
    0x08048493 <+31>:   mov    %eax,(%esp)         # make esp point to eax contained address
    0x08048496 <+34>:   call   0x80483a4 <puts@plt>  # again what is "@plt" ????
    0x0804849b <+39>:   mov    -0x4(%ebp),%eax    # move (ebp - 4) location's contents to eax
    0x0804849e <+42>:   xor    %gs:0x14,%eax         # # again what is this ????
    0x080484a5 <+49>:   je     0x80484ac <display+56> # Not known to me
    0x080484a7 <+51>:   call   0x8048394 <__stack_chk_fail@plt>  # not known to me
    0x080484ac <+56>:   leave                        # a new instruction, not known to me
    0x080484ad <+57>:   ret                          # return to MAIN's next instruction

汇编程序转储结束。

所以伙计们,你们应该考虑一下我的功课。剩下的所有代码我都知道,除了几行。我已经包括了一个很大的“为什么????”以及每行前面的 cmets 中的更多问题。对我来说第一个障碍是“mov %gs:0x14,%eax”指令,我不能在这个指令之后制作流程图。有人请解释我,这几条指令是什么意思,在程序中做什么?谢谢...

【问题讨论】:

  • 在main中你返回0,这就是mov $0x0,%eax的原因。
  • xor %eax,%eax 是一种清除 %eax 的高效方法,因为异或相同的值总是产生 0。
  • gs:0x14 的操作看起来像 stack canaryxor %eax, %eax 只是将eax 设置为0 的一种方式。 lea -0xc(%ebp), %eax 将你的buff 的地址加载到eax 中,因此可以将其传递给gets/puts
  • 非常感谢 Qiau 和 DCoder... :-)
  • PLT 是过程链接表。 getsputs 位于动态库中,链接程序时不知道其地址。所以,以后每次加载程序的时候,它需要的动态库也会被加载。无论是在那个时候还是以后,当你第一次从动态库中调用一个函数时,它的地址就会被解析,并放入 GOT(全局偏移表)中。当您调用 gets@plt 时,它会间接跳转到相应 GOT 条目所指向的条目(或解析该地址的例程,如果仍未解析)。

标签: c assembly x86


【解决方案1】:
0x080484b6 <+8>:    mov    $0x0,%eax   # move 0 into eax , but WHY ????

你没有这个吗?:

return(0);

它们可能是相关的。 :)

0x0804847a <+6>:    mov    %gs:0x14,%eax  # what does it mean ????

这意味着从地址 gs:0x14 的内存中读取 4 个字节到 eax。 gs 是一个段寄存器。最有可能通过此寄存器引用线程本地存储(AKA TLS)。

0x08048483 <+15>:   xor    %eax,%eax       # xor eax with itself (but WHY??)

不知道。可能与优化有关。

0x08048485 <+17>:   lea    -0xc(%ebp),%eax  #Load effective address of 12 bytes 
                                          lower placed value ( WHY???? )

它使 eax 指向一个存在于堆栈上的局部变量。 sub $0x10,%esp 为他们分配了一些空间。

0x08048488 <+20>:   mov    %eax,(%esp)      #make esp point to the address inside of eax

错了。它将 eax 写入堆栈,堆栈顶部。它将作为堆栈参数传递给被调用函数:

0x0804848b <+23>:   call   0x8048374 <gets@plt>  # calling get, what is "@plt" ????

我不知道。可能是一些名称修改。

现在你应该已经猜到那是什么局部变量了。 buff,还能是什么?

0x080484ac <+56>:   leave                        # a new instruction, not known to me

你为什么不查一下CPU手册?

现在,我大概可以给你解释一下 gs/TLS 的事情了……

0x08048474 <+0>:    push   %ebp          #saves ebp to stack        
0x08048475 <+1>:    mov    %esp,%ebp     # saves esp to ebp
0x08048477 <+3>:    sub    $0x10,%esp    # making 16 bytes space in stack
0x0804847a <+6>:    mov    %gs:0x14,%eax  # what does it mean ????
0x08048480 <+12>:   mov    %eax,-0x4(%ebp) # move eax contents to 4 bytes lower in stack
...
0x0804849b <+39>:   mov    -0x4(%ebp),%eax    # move (ebp - 4) location's contents to eax
0x0804849e <+42>:   xor    %gs:0x14,%eax         # # again what is this ????
0x080484a5 <+49>:   je     0x80484ac <display+56> # Not known to me
0x080484a7 <+51>:   call   0x8048394 <__stack_chk_fail@plt>  # not known to me
0x080484ac <+56>

因此,此代码从 TLS 中获取一个值(位于 gs:0x14)并将其存储在已保存的 ebp 值下方(位于 ebp-4)。然后是get()put() 的内容。然后此代码检查来自 TLS 的值的副本是否未更改。 xor %gs:0x14,%eax 进行比较。

如果异或值相同,则异或结果为 0,flags.zf 为 1。否则,结果不为 0,flags.zf 为 0。

je 0x80484ac &lt;display+56&gt; 检查 flags.zf 并在 flags.zf = 1 时跳过 call 0x8048394 &lt;__stack_chk_fail@plt&gt;。IOW,如果来自 TLS 的值的副本未更改,则跳过此调用。

那是怎么回事?这是一种尝试捕获缓冲区溢出的方法。如果写入超出缓冲区的末尾,则会覆盖从 TLS 复制到堆栈的值。

为什么我们从 TLS 中获取这个值,为什么不只是一个固定的、硬编码的值?我们可能希望使用不同的、非硬编码的值来更频繁地捕获溢出(因此 TLS 中的值将在您的程序的运行到另一次运行时发生变化,并且在程序的不同线程中会有所不同)。如果每次程序运行时随机选择该值,这也会降低攻击者成功利用缓冲区溢出的机会。

最后,如果发现该值的副本由于缓冲区溢出而被覆盖,call 0x8048394 &lt;__stack_chk_fail@plt&gt; 将调用一个专用函数来执行任何必要的操作,例如报告问题并终止程序。

【讨论】:

  • 谢谢 Alexy,我希望我能给你不止一篇“有用的帖子”。非常感谢这么好的解释。
  • 阿列克西!我有个疑问。在指令期间:- call 0x8048374 ;; lea -0xc(%ebp),%eax ;; mov %eax,(%esp) ,为什么会这样?我的意思是 INPUTed 值已经在堆栈上。那么为什么程序将 LEA 加载到 eax ,然后将 eax 值移动到堆栈。输入已经存在,那么为什么要再次执行此过程?
  • 指向数组的指针,作为参数传递给gets()puts(),可以通过gets() 任意更改。调用代码确保puts()的参数正确。
【解决方案2】:
0x0804849e <+42>:   xor    %gs:0x14,%eax         # # again what is this ????
0x080484a5 <+49>:   je     0x80484ac <display+56> # Not known to me
0x080484a7 <+51>:   call   0x8048394 <__stack_chk_fail@plt>  # not known to me
0x080484ac <+56>:   leave                        # a new instruction, not known to me
0x080484ad <+57>:   ret                          # return to MAIN's next instruction

gs segment 可用于thread local storage。例如。它用于errno,因此多线程程序中的每个线程实际上都有自己的 errno 变量。

上面的函数名是一个很大的线索。这必须是stack canary

leave 是一些 CISC 指令,可以在实际 ret 之前完成您需要做的所有事情。我不知道细节)。

【讨论】:

  • 感谢朋友们,迟到了,感谢大家的回复。我是组装初学者。所以我在谷歌上搜索你的答案中使用的新术语。
  • +1 表示“堆栈金丝雀”一词以及其他解释。很好的信息,谢谢
【解决方案3】:

其他人已经解释了 GS 的事情(与线程有关)..

0x08048483 <+15>:   xor    %eax,%eax       # xor eax with itself (but WHY??)

解释这一点需要一些 X86 架构的历史:

xor eax, eax 指令清除寄存器 eax 中的所有位(加载零),但正如您已经发现的那样,这似乎是不必要的,因为寄存器在下一条指令中加载了新值。

但是,xor eax, eax 在 x86 上也做了其他事情。您可能知道您可以使用 al、ah 和 ax 访问寄存器 eax 的部分内容。从 386 开始就是这样,那时候 eax 真的是一个单一的寄存器时还可以。

但是,这已不复存在了。您在代码中看到和使用的寄存器只是占位符。 CPU 内部正在使用更多的内部寄存器和完全不同的指令集。您编写的指令被翻译成这个内部指令集。

例如,如果您使用 AL、AH 和 EAX,从 CPU 的角度来看,您正在使用三个不同的寄存器。

现在,如果您在使用 AL 或 AH 之后访问 EAX,CPU 必须合并这些不同的寄存器以构建有效的 EAX 值。

行:

0x08048483 <+15>:   xor    %eax,%eax       # xor eax with itself (but WHY??)

不仅清除寄存器 eax。它还告诉 CPU 所有重命名的子寄存器:AL、AH 和 AX 现在可以认为是无效的(设置为零)并且 CPU 不必进行任何子寄存器合并。

为什么编译器会发出这条指令?

因为编译器不知道 display() 将在哪个上下文中被调用。您可以从一段代码中调用它,该代码使用 AL 和 AH 执行大量字节运算。如果它不能通过 XOR 清除 EAX 寄存器,CPU 将不得不进行昂贵的寄存器合并,这需要很多周期。

所以在函数开始时做这些额外的工作可以提高性能。在您的情况下这是不必要的,但是因为编译器无法确定会发出指令。

【讨论】:

    【解决方案4】:

    stack_check_fail 是 gcc 缓冲区溢出检查的一部分。它使用 libssp (stack-smash-protection),并且您在开始时的移动为堆栈设置了保护,xor %gs:0x14... 是检查保护是否仍然正常。如果没问题,它会跳转到离开(检查汇编文档,它是堆栈处理的辅助指令)并跳过跳转到 stack_chk_fail,这将中止程序并发出错误消息。

    您可以使用 gcc 选项 -fno-stack-protector 禁用此溢出检查的发射。

    正如在 cmets 中已经提到的,xor x,x 只是一个清除 x 的快速命令,最后的 mov 0, %eax 是你的 main 的返回值。

    【讨论】:

      猜你喜欢
      • 2014-10-28
      • 2012-08-08
      • 2017-05-05
      • 2019-06-26
      • 2012-12-10
      • 2017-03-29
      相关资源
      最近更新 更多