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

How to disable possible stack smashing protection (EIP is not being overwritten, EBP is)

我想弄清楚 stash smashing 是如何一步步进行的。我已经用了Google没有用,我还是不知道为什么我的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下面,但它仍然有来自main函数的地址。这是 main 的反汇编:

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

现在我正在一条一条地找出汇编程序指令,但我没有看到 EIP 在 [=15] 之后从堆栈中加载 return 地址的那一刻=] 完成。我尝试了 -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    [=14=]xfffffff0,%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    [=14=]x14,%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    [=14=]x4,%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    [=14=]x8,%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    [=14=]x10,%esp
   # Make space for 3 words - alignment + return value
   0x08048456 <+43>:    sub    [=14=]xc,%esp
   # Push the printf argument address (the string address)
   0x08048459 <+46>:    push   [=14=]x8048510
   # Call printf
   0x0804845e <+51>:    call   0x8048300 <puts@plt>
   # Remove 4 words - 1 for parameter and previous 3
   0x08048463 <+56>:    add    [=14=]x10,%esp
   # Reset 0x0 just because
   0x08048466 <+59>:    mov    [=14=]x0,%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 行的结论正确吗?

您首先要覆盖堆栈上的局部变量,其中包括编译器用来记住堆栈指针的 ecx 的已保存副本。因此,当代码到达 0x0804846b 时,堆栈上的值已被破坏,因此 ecx 加载了错误的值。您可以在寄存器转储中看到它是 0x41414141。接下来,esp是根据ecx加载的,所以esp也得到了错误的值。最后,ret 尝试从堆栈中弹出 return 地址,这当然使用了 esp 但正如我们在上面看到的那样,它的值很差。于是,崩溃。

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

免责声明:我在安装了 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' 处放置一个断点并将命令行参数设置为 "AAAAAAAAAABBBBBBBBBBCCCCCCCCCC" 并注意以下内容:

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

       0x0040157a <+0>:     push   %ebp
       0x0040157b <+1>:     mov    %esp,%ebp
    => 0x0040157d <+3>:     and    [=12=]xfffffff0,%esp
       0x00401580 <+6>:     sub    [=12=]x20,%esp
       0x00401583 <+9>:     call   0x401fd0 <__main>
       0x00401588 <+14>:    movl   [=12=]x5,0x1c(%esp)
       0x00401590 <+22>:    mov    0xc(%ebp),%eax
       0x00401593 <+25>:    add    [=12=]x4,%eax
       0x00401596 <+28>:    mov    (%eax),%eax
       0x00401598 <+30>:    mov    %eax,(%esp)
       0x0040159b <+33>:    call   0x401560 <copyinput>
       0x004015a0 <+38>:    movl   [=12=]x7,0x1c(%esp)
       0x004015a8 <+46>:    mov    [=12=]x0,%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
    

    我们对上面的 espebp 感兴趣。请记住 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
    

    原始内存转储不是很容易阅读,所以让我们折叠 bytes转化为words,将byte order转化为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
    

    我们可以看到ebpeip的存储值都被 在堆栈上被破坏。现在当我们 return 来自 copyinput 时 将弹出 eip(现在是 0x43434343)和 ebp(现在是 现在是 0x43434242) 离开堆栈并尝试执行 指令在 0x43434343;这显然会产生一个 例外。

像这样的堆栈攻击的主要目的是安排它,以便我们用我们选择的有效值覆盖 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 的值(这是我机器上 testinput 的位置)覆盖 copyinput 中的 return 地址,我们就能够使该函数执行。

=========================================== ==================================== 评论中问题的答案:

不确定 OS/compiler 您使用的是什么。我在 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    [=19=]xfffffff0,%esp
   0x00401566 <+6>: sub    [=19=]x20,%esp
   0x00401569 <+9>: call   0x401fc0 <__main>

这是 main 的序言,我们在其中设置 main 的堆栈帧。我们压入前一个堆栈帧的基指针(来自 运行-time 库提供的一些函数),然后将基指针移动到堆栈点所在的位置。接下来我们调整 esp 使其可以被 16 整除,然后我们从 esp 中减去 32 个字节(0x20)(记住堆栈向下增长,所以我们现在有一些 main 将要使用的 space。

push %ebpmov %esp, %ebp 然后 sub 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,argv1位于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
 = (void *) 0x28fea0
(gdb) print $ebp
 = (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    [=23=]x4,%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&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    [=26=]x4,%eax
   0x00401574 <+20>:    mov    (%eax),%eax
   0x00401576 <+22>:    mov    %eax,0x4(%esp)

我们将 0x0C 添加到我们当前的基指针,它的值为 0x28FED4,然后我们将该内存地址中包含的内容复制到 eax。通过GDB我们可以发现位于0x08FEC4的四个字节是0x00a31830,也就是argv[0]的地址。将 4 添加到 eax 会导致 eax 现在指向 argv1。接下来的两条指令有效地将 argv1 的地址移动到 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 被压入第二个。因此,编译器并没有将参数压入堆栈,而是预留了足够的 space 以便它可以在正确的位置加载所需的值。所以一切就绪,我们现在可以调用 strcpy.

接下来的几条指令只是设置对printf的调用(实际上是puts),我们需要将字符串"Done.\n"的地址移动到堆栈上,然后调用puts:

   0x00401586 <+38>:    movl   [=30=]x404024,(%esp)
   0x0040158d <+45>:    call   0x402750 <puts>

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

   0x00401592 <+50>:    mov    [=31=]x0,%eax
   0x00401597 <+55>:    leave  
   0x00401598 <+56>:    ret 

不确定我是否回答了您所有的问题,但我想我回答了。我也希望我没有把分析搞砸太多,我通常不会深入分析汇编或使用 AT&T 语法。

===============编辑2 =========================== ======

剩余三题:

Is the value in line +7 unnecessary? I don't see any use for it, so why is it stored?

您关于我们正在推动 esp 的原始未对齐值的分析似乎是正确的。我的预感是,在您反汇编的前几行中,我们 正在查看 main 的特殊启动代码。请记住,在为 main 创建堆栈帧之前,堆栈上有一个堆栈帧。您可能想看看 this link 以了解 Linux 下程序的正常启动顺序是什么。

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

In some places sp moves more than it has to - is it due to alignment? (e.g. line +14)

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

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

所以,我会声称 main+10main+14 是正常函数 prolog

Is my conclusion over line +71 correct?

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

call, ret — Subroutine call and return

These instructions implement a subroutine call and return. The call instruction first pushes the current code location onto the hardware supported stack in memory (see the push instruction for details), and then performs an unconditional jump to the code location indicated by the label operand. Unlike the simple jump instructions, the call instruction saves the location to return to when the subroutine completes.

The ret instruction implements a subroutine return mechanism. This instruction > first pops a code location off the hardware supported in-memory stack (see the pop instruction for details). It then performs an unconditional jump to the retrieved code location.

Syntax
call <label>
ret

有关 LEAVE 指令(在 main+67 中使用)的其他信息是(取自 here):

Releases the stack frame set up by an earlier ENTER instruction. The LEAVE instruction copies the frame pointer (in the EBP register) into the stack pointer register (ESP), which releases the stack space allocated to the stack frame. The old frame pointer (the frame pointer for the calling procedure that was saved by the ENTER instruction) is then popped from the stack into the EBP register, restoring the calling procedure's stack frame.

A RET instruction is commonly executed following a LEAVE instruction to return program control to the calling procedure.

See "Procedure Calls for Block-Structured Languages" in Chapter 6 of the IA-32 Intel Architecture Software Developer's Manual, Volume 1, for detailed information on the use of the ENTER and LEAVE instructions.

N.B. 可以通过以下方式更改 GDB 发出的反汇编的风格 使用以下命令:

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

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

PS 我在下面的回答中支持小丑的评论。将实际易受攻击的代码移到函数而不是 main 中将使分析更容易,因为您不必处理 main 的对齐以及 main 的唯一序言和结尾的奇怪之处。一旦你掌握了这种类型的堆栈利用,你就可以返回并处理一个漏洞在 main 中的示例。

PPS 在 Linux 系统上工作你也可能 运行 进入 ASLR 问题,因为每次你 运行 一个程序的东西是不同的内存位置,所以堆栈帧和堆栈帧位置之间的偏移量会发生变化。您可以使用以下短程序(摘自 The Shellcoder's Handbook:Chris Anley 的 Discovering and Exploiting Security Holes,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 运行ning。它会让你的生活更加艰难,但并非无法克服