如何禁用可能的堆栈粉碎保护(EIP未被覆盖,EBP是)

时间:2015-08-13 11:02:45

标签: c gcc assembly x86 buffer-overflow

我试图弄清楚藏匿粉碎是如何逐步进行的。我已经使用谷歌无济于事,我仍然不知道为什么我的EIP没有被覆盖。我有这个示例程序:

 1 #include <stdio.h>
 2 #include <string.h>
 3 
 4 int main(int argc, char *argv[])
 5 {
 6     char buf[10];
 7 
 8     strcpy(buf, argv[1]);
 9     printf("Done.\n");
10     return 0;
11     
12 }

编译
gcc -g -o prog main.c

当我放入很多AAAAAA时,我得到了SEGV和寄存器EBP(以及argc和argv地址被覆盖:

Program received signal SIGSEGV, Segmentation fault.
0x08048472 in main (argc=<error reading variable: Cannot access memory at address 0x41414141>, argv=<error reading variable: Cannot access memory at address 0x41414145>)
    at main.c:12
12  }
(gdb) info reg
eax            0x0  0
ecx            0x41414141   1094795585
edx            0xb7fbb878   -1208240008
ebx            0xb7fba000   -1208246272
esp            0x4141413d   0x4141413d
ebp            0x41414141   0x41414141
esi            0x0  0
edi            0x0  0
eip            0x8048472    0x8048472 <main+71>
eflags         0x10282  [ SF IF RF ]
cs             0x73 115
ss             0x7b 123
ds             0x7b 123
es             0x7b 123
fs             0x0  0
gs             0x33 51

我认为EIP正好在EBP之下,但它仍然具有主要功能的地址。这是主要的反汇编:

(gdb) disass main
Dump of assembler code for function main:
   0x0804842b <+0>: lea    0x4(%esp),%ecx
   0x0804842f <+4>: and    $0xfffffff0,%esp
   0x08048432 <+7>: pushl  -0x4(%ecx)
   0x08048435 <+10>:    push   %ebp
   0x08048436 <+11>:    mov    %esp,%ebp
   0x08048438 <+13>:    push   %ecx
   0x08048439 <+14>:    sub    $0x14,%esp
   0x0804843c <+17>:    mov    %ecx,%eax
   0x0804843e <+19>:    mov    0x4(%eax),%eax
   0x08048441 <+22>:    add    $0x4,%eax
   0x08048444 <+25>:    mov    (%eax),%eax
   0x08048446 <+27>:    sub    $0x8,%esp
   0x08048449 <+30>:    push   %eax
   0x0804844a <+31>:    lea    -0x12(%ebp),%eax
   0x0804844d <+34>:    push   %eax
   0x0804844e <+35>:    call   0x80482f0 <strcpy@plt>
   0x08048453 <+40>:    add    $0x10,%esp
   0x08048456 <+43>:    sub    $0xc,%esp
   0x08048459 <+46>:    push   $0x8048510
   0x0804845e <+51>:    call   0x8048300 <puts@plt>
   0x08048463 <+56>:    add    $0x10,%esp
   0x08048466 <+59>:    mov    $0x0,%eax
   0x0804846b <+64>:    mov    -0x4(%ebp),%ecx
   0x0804846e <+67>:    leave  
   0x0804846f <+68>:    lea    -0x4(%ecx),%esp
=> 0x08048472 <+71>:    ret    
End of assembler dump.

现在我正逐步找出汇编程序指令,但是我没有看到EIP在strcpy之后加载了堆栈返回地址的时刻。饰面。我试过了-fno-stack-protector,但它并没有改变一件事。这可能是什么原因?

编辑:

好的,我会尝试一步一步地检查它,请在我错误的地方纠正我

   # Just below the sp are argc and argv and the sp points to the address
   # where RET will be stored
   # This one moves the address of argc (which is on the stack) to $ecx 
   0x0804842b <+0>: lea    0x4(%esp),%ecx
   # Move stack pointer down for alignment
   0x0804842f <+4>: and    $0xfffffff0,%esp
   # Push the value to which $sp pointed to before alignment 
   # It is never used - correct me if I'm wrong  
   0x08048432 <+7>: pushl  -0x4(%ecx)
   # Push last used base pointer value (and start creating another frame)
   0x08048435 <+10>:    push   %ebp
   # Set current position sp as bp - I think here the main body starts
   0x08048436 <+11>:    mov    %esp,%ebp
   # Push the address of argc - it's later used for calculating 
   # the address of argv[1]. 
   0x08048438 <+13>:    push   %ecx
   # Make some space on the stack (20 bytes - 5 words - first two I'm 
   # sure for what (alignment and not used here return value?) 
   # another 3 for buffer[10]
   0x08048439 <+14>:    sub    $0x14,%esp
   # Move argc address to $eax
   0x0804843c <+17>:    mov    %ecx,%eax
   # Move argv address to $eax
   0x0804843e <+19>:    mov    0x4(%eax),%eax
   # Move past argv - $eax should now point to pointer to first 
   # argument string
   0x08048441 <+22>:    add    $0x4,%eax
   # Move the address of the parameter string to $eax
   0x08048444 <+25>:    mov    (%eax),%eax
   # Make space for 2 words 
   # (probably alignment and return value from strcpy)
   0x08048446 <+27>:    sub    $0x8,%esp
   # Push the parameter address
   0x08048449 <+30>:    push   %eax
   # Get the address of the local buffer
   0x0804844a <+31>:    lea    -0x12(%ebp),%eax
   # Push it
   0x0804844d <+34>:    push   %eax
   # Call strcpy
   0x0804844e <+35>:    call   0x80482f0 <strcpy@plt>
   # Remove 4 words - 2 for arguments and 2 for return + alignment
   0x08048453 <+40>:    add    $0x10,%esp
   # Make space for 3 words - alignment + return value
   0x08048456 <+43>:    sub    $0xc,%esp
   # Push the printf argument address (the string address)
   0x08048459 <+46>:    push   $0x8048510
   # Call printf
   0x0804845e <+51>:    call   0x8048300 <puts@plt>
   # Remove 4 words - 1 for parameter and previous 3
   0x08048463 <+56>:    add    $0x10,%esp
   # Reset 0x0 just because
   0x08048466 <+59>:    mov    $0x0,%eax
   # Load previously saved address of argc
   0x0804846b <+64>:    mov    -0x4(%ebp),%ecx
   # not sure about that leave...
   0x0804846e <+67>:    leave  
   # Reload $esp starting value
   0x0804846f <+68>:    lea    -0x4(%ecx),%esp
   # Pop the RET address - this one should be changed to 
   # pointer to malicious code
=> 0x08048472 <+71>:    ret    
  1. +7行中的值是否不必要?我没有看到任何用途,为什么要存储?
  2. 在某些地方sp移动得比它更多 - 是否由于对齐? (例如,行+14)
  3. 我对第+71行的结论是否正确?

2 个答案:

答案 0 :(得分:6)

免责声明:我在安装了gnuwin32的Windows 7系统上使用gcc-4.8.3。 Windows默认情况下似乎没有启用ASLR,因此当我运行此程序时,我可以获得可重现的内存地址,这使生活更容易。此外,如果你遵循这个,你得到的记忆地址很可能是不同的。

现在考虑一下这个程序:

#include <string.h>

void copyinput(char* input)
{
    char buf[10];
    strcpy(buf, input);
}

int main(int argc, char** argv)
{
    int a = 5;
    copyinput(argv[1]);
    a = 7;

    return 0;
}

我们可以用这个命令行编译:

gcc -g -ansi -pedantic -Wall overflow2.c -o overflow

然后在gdb下运行该程序。

我们在'main&#39;处设置一个断点。并将命令行参数设置为&#34; AAAAAAAAAABBBBBBBBBBCCCCCCCCCC&#34;并注意以下内容:

  1. 首先注意main的反汇编:

       0x0040157a <+0>:     push   %ebp
       0x0040157b <+1>:     mov    %esp,%ebp
    => 0x0040157d <+3>:     and    $0xfffffff0,%esp
       0x00401580 <+6>:     sub    $0x20,%esp
       0x00401583 <+9>:     call   0x401fd0 <__main>
       0x00401588 <+14>:    movl   $0x5,0x1c(%esp)
       0x00401590 <+22>:    mov    0xc(%ebp),%eax
       0x00401593 <+25>:    add    $0x4,%eax
       0x00401596 <+28>:    mov    (%eax),%eax
       0x00401598 <+30>:    mov    %eax,(%esp)
       0x0040159b <+33>:    call   0x401560 <copyinput>
       0x004015a0 <+38>:    movl   $0x7,0x1c(%esp)
       0x004015a8 <+46>:    mov    $0x0,%eax
       0x004015ad <+51>:    leave  
       0x004015ae <+52>:    ret    
       0x004015af <+53>:    nop
    

    我们感兴趣的是下一个的地址 我们致电copyinput后的指示。这将是价值 eip 在传递控制流时被压入堆栈 copyinput

  2. 让我们看一下寄存器:

    (gdb) info reg
    eax            0x1  1
    ecx            0x752c1162   1965822306
    edx            0xa02080 10494080
    ebx            0x2  2
    esp            0x28fea0 0x28fea0
    ebp            0x28fec8 0x28fec8
    esi            0xa01858 10491992
    edi            0x1f 31
    eip            0x401590 0x401590 <main+22>
    eflags         0x202    [ IF ]
    cs             0x23 35
    ss             0x2b 43
    ds             0x2b 43
    es             0x2b 43
    fs             0x53 83
    gs             0x2b 43
    

    我们对上面的 esp ebp 感兴趣。请记住 ebp 在函数调用期间也应该被压入堆栈 copyinput

  3. 单步调用copyinput然后进入该步骤 功能。此时,查看寄存器(在调用之前) strcpy)再次:

    (gdb) info reg
    eax            0x9218b0 9574576
    ecx            0x752c1162   1965822306
    edx            0x922080 9576576
    ebx            0x2  2
    esp            0x28fe70 0x28fe70
    ebp            0x28fe98 0x28fe98
    esi            0x921858 9574488
    edi            0x1f 31
    eip            0x401566 0x401566 <copyinput+6>
    eflags         0x202    [ IF ]
    cs             0x23 35
    ss             0x2b 43
    ds             0x2b 43
    es             0x2b 43
    fs             0x53 83
    gs             0x2b 43
    

    我们在这里看到的是copyinput的堆栈帧来自 0x28fe70到0x28fe98,并且回到第(2)点我们可以看到 main的堆栈帧基于0x28fec8。

  4. 我们可以检查从0x28fe70到0x28fec8的堆栈(总共88个 字节)像这样:

        (gdb) x/88xb 0x28fe70
    
        0x28fe70:   0x50    0x15    0x40    0x00    0xdc    0x00    0x00    0x00
        0x28fe78:   0xff    0xff    0xff    0xff    0x30    0x60    0x44    0x00
        0x28fe80:   0x03    0x00    0x00    0x00    0x8c    0xfe    0x28    0x00
        0x28fe88:   0x00    0x00    0x00    0x00    0x8f    0x17    0x40    0x00
        0x28fe90:   0x50    0x1f    0x40    0x00    0x1c    0x50    0x40    0x00
        0x28fe98:   0xc8    0xfe    0x28    0x00    0xa0    0x15    0x40    0x00
        0x28fea0:   0xb0    0x18    0x92    0x00    0x00    0x50    0x40    0x00
        0x28fea8:   0x88    0xff    0x28    0x00    0xae    0x1f    0x40    0x00
        0x28feb0:   0x50    0x1f    0x40    0x00    0x60    0x00    0x00    0x40
        0x28feb8:   0x1f    0x00    0x00    0x00    0x05    0x00    0x00    0x00
        0x28fec0:   0x58    0x17    0x92    0x00    0x1f    0x00    0x00    0x00
    

    原始内存转储不是很容易阅读,所以让我们崩溃 将字节转换为单词,并将字节顺序转换为big-endian,我们 可以看到某些值所在的位置:

    0x28fe70: 0x00401550  <- esp for `copyinput`
              0x000000dc
    0x28fe78: 0xffffffff
              0x00446030
    0x28fe80: 0x00000003    
              0x0028fe8c
    0x28fe88: 0x00000000    
              0x0040178f
    0x28fe90: 0x00401f50    
              0x0040501c
    0x28fe98: 0x0028fec8 <- stored *ebp* for `main``s stack frame
              0x004015a0 <- stored *eip*, 
    0x28fea0: 0x009218b0 <- esp for `main``s stack frame    
              0x00405000
    

    因此我们可以看到存储的 eip 位于 堆栈在地址0x28fe9C。从中您可以看到 eip 首先被压入堆栈,然后 ebp 被推入堆栈。

  5. 现在单步执行直到调用字符串复制和检查 记忆再次显示:

    (gdb) x/88xb 0x28fe70
    0x28fe70:   0x86    0xfe    0x28    0x00    0xb0    0x18    0x92    0x00
    0x28fe78:   0xff    0xff    0xff    0xff    0x30    0x60    0x44    0x00
    0x28fe80:   0x03    0x00    0x00    0x00    0x8c    0xfe    0x41    0x41
    0x28fe88:   0x41    0x41    0x41    0x41    0x41    0x41    0x41    0x41
    0x28fe90:   0x42    0x42    0x42    0x42    0x42    0x42    0x42    0x42
    0x28fe98:   0x42    0x42    0x43    0x43    0x43    0x43    0x43    0x43
    0x28fea0:   0x43    0x43    0x43    0x43    0x00    0x50    0x40    0x00
    0x28fea8:   0x88    0xff    0x28    0x00    0xae    0x1f    0x40    0x00
    0x28feb0:   0x50    0x1f    0x40    0x00    0x60    0x00    0x00    0x40
    0x28feb8:   0x1f    0x00    0x00    0x00    0x05    0x00    0x00    0x00
    0x28fec0:   0x58    0x17    0x92    0x00    0x1f    0x00    0x00    0x00
    

    我们可以看到 ebp eip 的存储值都已存在 被摧毁在堆栈上。现在当我们从copyinput返回时 将弹出 eip (现在为0x43434343)和 ebp (其中的值 现在是0x43434242)离开堆栈并尝试执行 指令0x43434343;这显然会产生一个 异常。

  6. 像这样的堆栈攻击的主要目的是安排它,以便我们用我们选择的有效值覆盖 eip 。例如,请考虑以下程序:

    #include <stdio.h>
    #include <string.h>
    
    void copyinput(char* input)
    {
       char buf[10];
       strcpy(buf, input);
    }
    
    void testinput()
    {
        printf("we should never see this\n");
    }
    
    int main(int argc, char** argv)
    {
        int a = 5;
        copyinput(argv[1]);
        a = 7;
    
       return 0;
    }
    

    永远不会调用函数testinput。但是,如果我们可以使用值0x0040157a(我的机器上copyinput的位置)覆盖testinput中的返回地址,我们就能够执行该函数。

    =============================================== ================================== 对评论中提出的问题的答案:

    不确定您使用的操作系统/编译器。我在Windows 7机器上使用gcc-4.8.3对你的示例程序进行了编译。我对main的反汇编看起来像这样:

    (gdb) disass main
    Dump of assembler code for function main:
       0x00401560 <+0>: push   %ebp
       0x00401561 <+1>: mov    %esp,%ebp
       0x00401563 <+3>: and    $0xfffffff0,%esp
       0x00401566 <+6>: sub    $0x20,%esp
       0x00401569 <+9>: call   0x401fc0 <__main>
    

    这是main的前导码,我们正在为main设置堆栈帧。我们推送前一个堆栈帧的基指针(来自运行时库提供的某些函数),然后将基指针移动到堆栈点所在的位置。接下来我们调整 esp 使其可以被16整除,然后我们从 esp 中减去32个字节(0x20)(请记住堆栈增长了,所以我们现在有了一些主要使用的空间。

    push %ebpmov %esp, %ebpsub xxx, %esp的常见模式是函数的常见前导码。

    让我们试着找出事物在记忆中的位置。在gdb中,我们可以执行以下操作:

    (gdb) x/16xb &argv[0]
    0xa31830:   0x58    0x18    0xa3    0x00    0x98    0x18    0xa3    0x00
    0xa31838:   0x00    0x00    0x00    0x00    0xab    0xab    0xab    0xab
    

    这是我们所期望的,两个32位指针后跟一个空终止符。所以argv [0]位于0x00a31858,而argv 1位于0x00a31898;通过检查这两个位置的记忆可以看出:

    (gdb) x/20cb 0x00a31858
    0xa31858:   100 'd' 58 ':'  92 '\\' 117 'u' 115 's' 101 'e' 114 'r' 115 's'
    0xa31860:   92 '\\' 103 'g' 104 'h' 117 'u' 98 'b'  101 'e' 114 'r' 92 '\\'
    0xa31868:   71 'G'  78 'N'  85 'U'  72 'H'
    (gdb) x/20xb 0x00a31898
    0xa31898:   0x41    0x41    0x41    0x41    0x41    0x41    0x41    0x41
    0xa318a0:   0x41    0x41    0x00    0xab    0xab    0xab    0xab    0xab
    0xa318a8:   0xab    0xab    0xab    0xfe
    

    我们可以找到缓冲区的位置,但在GDB中执行以下操作:

    (gdb) print $esp
    $4 = (void *) 0x28fea0
    (gdb) print $ebp
    $5 = (void *) 0x28fec8
    (gdb) x/40xb $esp
    0x28fea0:   0xb6    0xfe    0x28    0x00    0x98    0x18    0xa3    0x00
    0x28fea8:   0x88    0xff    0x28    0x00    0x9e    0x1f    0x40    0x00
    0x28feb0:   0x40    0x1f    0x40    0x00    0x60    0x00    0x41    0x41
    0x28feb8:   0x41    0x41    0x41    0x41    0x41    0x41    0x41    0x41
    0x28fec0:   0x00    0x17    0xa3    0x00    0x0b    0x00    0x00    0x00
    

    所以我们可以看到我们的缓冲区从0x28feb6开始

    现在我们已经解决了这个问题,让我们看一下代码的下一部分,它应该设置为调用strcpy

       0x0040156e <+14>:    mov    0xc(%ebp),%eax
       0x00401571 <+17>:    add    $0x4,%eax
       0x00401574 <+20>:    mov    (%eax),%eax
       0x00401576 <+22>:    mov    %eax,0x4(%esp)
       0x0040157a <+26>:    lea    0x16(%esp),%eax
       0x0040157e <+30>:    mov    %eax,(%esp)
       0x00401581 <+33>:    call   0x402748 <strcpy>
    

    提醒一下,在AT&amp; T汇编语法中,地址操作数如下所示:

    displacement(base register, offset register, scalar multiplier)
    

    相当于intel语法:

    [base register + displacement + offset register * scalar multiplier]
    

    因此,

       0x0040156e <+14>:    mov    0xc(%ebp),%eax
       0x00401571 <+17>:    add    $0x4,%eax
       0x00401574 <+20>:    mov    (%eax),%eax
       0x00401576 <+22>:    mov    %eax,0x4(%esp)
    

    我们将0x0C添加到当前的基指针,其值为0x28FED4,然后我们将该内存地址中包含的内容复制到 eax 。通过使用GDB,我们可以发现位于0x08FEC4的四个字节是0x00a31830,它是argv [0]的地址。向 eax 添加四个会导致 eax 现在指向argv 1。接下来的两条指令有效地将argv 1的地址移动到 esp 之上的四个字节。

       0x0040157a <+26>:    lea    0x16(%esp),%eax
       0x0040157e <+30>:    mov    %eax,(%esp)
    

    继续,我们将 esp 增加0x16(这给我们0x28FEB6,我们之前已经确定它是buf[10]所在的位置。然后我们将此值移动到的位置esp 就在。目前,我们的堆栈现在看起来像:

               ~            ~
               |            |
               +------------+
    0x28fea4   | 0x00a31898 |    remember that this is the address of argv[1][0]
               +------------+
    0x28fea0   | 0x0028feb6 |    remember that this is the address of buf[0]
               +------------+
    

    鉴于strcpy的函数原型是:

    ,这是有道理的
        char*  strcpy(char* dst, const char* src);
    

    通常情况下,参数会从右向左推入堆栈,因此我们希望src先被推送,然后dst会被推到第二位。因此,编译器不是仅仅将参数推送到堆栈中,而是留出足够的空间,以便它可以在正确的位置加载所需的值。所以一切都已到位,我们现在可以致电strcpy

    接下来的几条指令只是设置了对printf(实际上是puts)的调用,我们需要移动字符串的地址&#34;完成。\ n&#34;到堆栈然后调用puts

       0x00401586 <+38>:    movl   $0x404024,(%esp)
       0x0040158d <+45>:    call   0x402750 <puts>
    

    最后,我们将返回值移动到 eax (这是通常包含函数返回值的寄存器),然后我们退出main

       0x00401592 <+50>:    mov    $0x0,%eax
       0x00401597 <+55>:    leave  
       0x00401598 <+56>:    ret 
    

    不确定我是否回答了你的所有问题,但我想我做到了。另外,我希望我没有过多地搞砸分析,我通常不会对汇编进行深入分析,也不会使用AT&amp; T语法。

    =============== edit2 =============================== ====

    其余三个问题:

      

    +7行中的值是否不必要?我没有看到任何用途,所以   为什么要存储?

    我们正在推动 esp 的原始未对齐值的分析显示正确。我的预感是,在你拆卸的前几行我们 正在寻找主要的特殊启动代码。请记住,在为main创建堆栈帧之前,堆栈上有堆栈帧。您可能需要查看this link以了解Linux下程序的正常启动顺序。

    我的预感是我们需要保留 esp 的未修改值,以便我们可以将早期的堆栈帧恢复到正确的位置。

      

    在某些地方sp移动得比它更多 - 是否由于对齐?   (例如第+ 14行)

    我会分析这些行是我们实际为main设置堆栈帧的地方。在main+14中,我们从 esp 中减去20个字节,因此我们分配20个字节供主函数使用。我们可以争辩说这些字节中有12个是由我们的缓冲区使用的(请记住,在缓冲区的末尾可能会有两个填充字节,因此存储在堆栈中的下一个值将位于32位字边界)。

       0x08048435 <+10>:    push   %ebp
       0x08048436 <+11>:    mov    %esp,%ebp
       0x08048438 <+13>:    push   %ecx
       0x08048439 <+14>:    sub    $0x14,%esp
    

    所以,我会声称main+10main+14是正常的功能序言

      

    我对第+71行的结论是否正确?

    是。此时我们需要覆盖堆栈中存储的 eip ,这将导致RET指令读取我们的值。以下对RET指令的描述取自from here(实际上这个页面有很多关于汇编的信息,值得一读。唯一的缺点是这个页面使用的是Intel语法并且你一直在展示AT&amp; T语法。)

      

    call,ret - 子程序调用并返回

         

    这些指令实现子程序调用并返回。电话   指令首先将当前代码位置推送到   内存中的硬件支持堆栈(参见推送指令)   详细信息),然后执行无条件跳转到代码位置   标签操作数表示。与简单的跳转指令不同,   调用指令保存返回的位置   子程序完成。

         

    ret指令实现子程序返回机制。该指令&gt;首先从支持的硬件弹出代码位置   内存堆栈(有关详细信息,请参阅pop指令)。然后呢   执行无条件跳转到检索到的代码位置。

    Syntax
    call <label>
    ret
    

    有关LEAVE指令的附加信息(在main+67使用)是(摘自here):

      

    释放先前的ENTER指令设置的堆栈帧。该   LEAVE指令将帧指针(在EBP寄存器中)复制到   堆栈指针寄存器(ESP),释放堆栈空间   分配给堆栈帧。旧帧指针(帧指针   对于由ENTER指令保存的调用过程)   然后从堆栈弹出到EBP寄存器,恢复   调用程序的堆栈帧。

         

    RET指令通常在LEAVE指令之后执行   将程序控制返回给调用过程。

         

    参见&#34;过程调用块结构化语言&#34;在第6章   IA-32英特尔架构软件开发人员手册,第1卷,   有关使用ENTER和LEAVE的详细信息   指令。

    N.B。可以改变GDB发出的反汇编的味道 使用以下命令:

    set disassembly-flavor att
    set disassembly-flavor intel
    show disassembly-flavor
    

    第三个命令显示当前的味道。

    PS 我的第二个Jesters在下面的回答中发表评论。将实际易受攻击的代码移到函数而不是main中会使分析变得更容易,因为您不必处理main和唯一的prolog和epilog对齐的奇怪性。一旦你掌握了这种类型的堆栈利用,你就可以回过头来处理漏洞主要存在的例子。

    在Linux系统上工作的

    PPS 您也可能遇到ASLR问题,因为每次运行程序时都会有不同的内存位置,因此堆栈帧和堆栈帧位置之间的偏移将会更改。您可以使用以下简短程序(摘自The Shellcoder的手册:发现和利用Chris Anley的安全漏洞, et.al )来查看ASLR是否存在问题

        #include <stdio.h>
        unsigned long find_start(void)
        {
            __asm__("movl %esp, %eax");
        }
    
        int main()
        {
            printf("0x%x\n", find_start());
            return (0);
        }
    

    多次运行程序,如果输出不同,则运行某个版本的ASLR。它会让你的生活变得更加艰难,但并非不可克服

答案 1 :(得分:3)

您首先覆盖堆栈上的本地化,其中包括编译器用于记住堆栈指针的ecx的保存副本。因此,当代码到达0x0804846b时,堆栈上的值被破坏,因此ecx加载了错误的值。您可以在注册转储中看到它0x41414141。接下来,esp基于ecx加载,因此esp也会得到错误的值。最后,ret尝试从堆栈中弹出返回地址,当然使用esp,但是我们已经看到了它的价值很低。因此,崩溃。

请注意,此代码通常仅为main生成以用于对齐目的,因此如果您将代码插入到仅从main调用的单独函数中,则可能会更好运。