c - 如何禁用可能的堆栈破坏保护(未覆盖EIP,而是EBP)

标签 c gcc assembly x86 buffer-overflow

我试图弄清楚如何一步一步地进行藏匿粉碎。我已经用过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的反汇编:
(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.

现在,我正在一步一步地确定汇编程序指令,但是在strcpy完成之后,我没有看到从堆栈中加载返回地址的EIP的那一刻。我尝试了-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    
  • +7行中的值是否不必要?我看不到有什么用,为什么要存储它?
  • 在某些地方,sp的移动超出了它的移动范围-是由于对齐? (例如+14行)
  • 我对+71行的结论正确吗?
  • 最佳答案

    免责声明:我在装有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”处放置一个断点,并将命令行参数设置为“AAAAAAAAAABBBBBBBBBBBBCCCCCCCCCC”,并注意以下几点:
  • 首先注意主要的反汇编:
       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之后的指令。这将是
    将控制流传递给栈时被推入堆栈的eipcopyinput
  • 让我们看一下寄存器:
    (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
  • 单步调用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。
  • 我们可以检查从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被压入堆栈。
  • 现在单步执行,直到调用字符串复制并检查之后
    记忆再次显示:
    (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上的指令;这显然会产生一个
    例外。

  • 这样的堆栈攻击的主要目的是安排它,以便我们用选择的有效值覆盖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函数。但是,如果我们可以将copyinput中的返回地址覆盖为0x0040157a(这是testinput在我的机器上的位置)的值,我们将能够使该函数执行。

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

    不确定您使用的是哪个OS /编译器。我带您的示例程序在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)(请记住,堆栈变小了,所以我们现在有一些空间要使用main。
    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&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加4会使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的函数原型(prototype)为:
        char*  strcpy(char* dst, const char* src);
    

    通常,参数从右到左被压入堆栈,因此我们期望src首先被压入,然后dst其次被压入。因此,编译器不只是将参数推入堆栈,还预留了足够的空间,以便可以将所需的值加载到正确的位置。一切就绪,我们现在可以调用strcpy了。

    接下来的几条指令仅设置了对printf的调用(实际上就是puts),我们需要将字符串“Done。\ n”的地址移到堆栈上,然后调用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&T语法中进行。

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

    剩下的三个问题:

    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个字节供主函数使用。我们可以争辩说缓冲区中的12个字节已被缓冲区使用(请记住,缓冲区的末尾可能会有两个填充字节,以便存储在堆栈中的下一个值将位于32位字边界处)。
       0x08048435 <+10>:    push   %ebp
       0x08048436 <+11>:    mov    %esp,%ebp
       0x08048438 <+13>:    push   %ecx
       0x08048439 <+14>:    sub    $0x14,%esp
    

    因此,我认为main+10main+14是正常的功能序言

    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我的第二个 clown 在下面的回答中评论。将实际的易受攻击的代码移到某个函数上而不是移到main函数上,将使分析变得更加容易,因为您不必处理main函数与main函数的唯一序言和结语之间的怪异。掌握了这种类型的堆栈利用之后,您就可以返回并研究其中主要存在漏洞的示例。

    在Linux系统上运行的PPS可能还会遇到ASLR问题,因为每次运行程序时,它们的存储位置都不同,因此堆栈帧和堆栈帧位置之间的偏移量将发生变化。您可以使用以下简短程序(摘自《 Shellcoder的手册:克里斯·安利等人的发现和利用安全漏洞》)来查看ASLR是否是一个问题
        #include <stdio.h>
        unsigned long find_start(void)
        {
            __asm__("movl %esp, %eax");
        }
    
        int main()
        {
            printf("0x%x\n", find_start());
            return (0);
        }
    

    多次运行该程序,如果输出不同,则说明您正在运行某些版本的ASLR。它将使您的生活更加艰难,但并非无法克服

    关于c - 如何禁用可能的堆栈破坏保护(未覆盖EIP,而是EBP),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/31986411/

    相关文章:

    c - 为什么递归函数调用需要返回一个值给调用函数?

    linux - 在 Ubuntu 上使用 makefile 编译时出现问题

    c++ - 链接使用不同版本的 gcc 编译的库

    assembly - 如何通过gdb获取从链接器导入的全局变量的值?

    gcc - 在 Jonesforth 中使用 GDB 显示字典单词

    c - c中的volatile和指针变量之间的区别

    c - 移动时在二维数组中分配nodeid++时覆盖nodeid

    c - 方阵对角线中的最小元素?

    c++ - 从模板类调用可变参数函数

    c++ - 混合使用 C++ 和程序集无法将多个参数从 C++ 函数传递给程序集