覆盖主函数中的EIP

问题描述

我很好奇在main函数中覆盖堆栈与在其他函数中有何不同

以这个例子为例:

#include <stdio.h>

int main(int argc,char *argv[])
{
    char buf[8]; 
    gets(buf); 
}

在这代码中,要溢出的缓冲区是在主函数中创建的,结果我在输入了很多'A'后从gdb收到了这个输出

AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

Program received signal SIGSEGV,Segmentation fault.
0x5655620c in main (argc=<error reading variable: Cannot access memory at address 0x41414141>,argv=<error reading variable: Cannot access memory at address 0x41414145>) at source.c:7
7       }
(gdb) info registers eip
eip            0x5655620c          0x5655620c <main+63>

反汇编main

   0x000011cd <+0>:     endbr32
   0x000011d1 <+4>:     lea    ecx,[esp+0x4]
   0x000011d5 <+8>:     and    esp,0xfffffff0
   0x000011d8 <+11>:    push   DWORD PTR [ecx-0x4]
   0x000011db <+14>:    push   ebp
   0x000011dc <+15>:    mov    ebp,esp
   0x000011de <+17>:    push   ebx
   0x000011df <+18>:    push   ecx
   0x000011e0 <+19>:    sub    esp,0x10
   0x000011e3 <+22>:    call   0x120d <__x86.get_pc_thunk.ax>
   0x000011e8 <+27>:    add    eax,0x2df0
   0x000011ed <+32>:    sub    esp,0xc
   0x000011f0 <+35>:    lea    edx,[ebp-0x10]
   0x000011f3 <+38>:    push   edx
   0x000011f4 <+39>:    mov    ebx,eax
   0x000011f6 <+41>:    call   0x1070 <gets@plt>
   0x000011fb <+46>:    add    esp,0x10
   0x000011fe <+49>:    mov    eax,0x0
   0x00001203 <+54>:    lea    esp,[ebp-0x8]
   0x00001206 <+57>:    pop    ecx
   0x00001207 <+58>:    pop    ebx
   0x00001208 <+59>:    pop    ebp
   0x00001209 <+60>:    lea    esp,[ecx-0x4]
   0x0000120c <+63>:    ret

这里,EIP 寄存器没有被覆盖,显然 gdb 无法访问被覆盖地址的内存。

而在此示例中,缓冲区内容写入另一个函数中:

#include <stdio.h>

void over() {
    char buf[8]; 
    gets(buf); 
}

int main(int argc,char *argv[])
{
    over();
}
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

Program received signal SIGSEGV,Segmentation fault.
0x41414141 in ?? ()
(gdb) info registers eip
eip            0x41414141          0x41414141

反汇编main

   0x000011f9 <+0>:     endbr32
   0x000011fd <+4>:     push   ebp
   0x000011fe <+5>:     mov    ebp,esp
   0x00001200 <+7>:     and    esp,0xfffffff0
   0x00001203 <+10>:    call   0x1219 <__x86.get_pc_thunk.ax>
   0x00001208 <+15>:    add    eax,0x2dd0
   0x0000120d <+20>:    call   0x11cd <over>
   0x00001212 <+25>:    mov    eax,0x0
   0x00001217 <+30>:    leave
   0x00001218 <+31>:    ret

反汇编over

   0x000011cd <+0>:     endbr32
   0x000011d1 <+4>:     push   ebp
   0x000011d2 <+5>:     mov    ebp,esp
   0x000011d4 <+7>:     push   ebx
   0x000011d5 <+8>:     sub    esp,0x14
   0x000011d8 <+11>:    call   0x1219 <__x86.get_pc_thunk.ax>
   0x000011dd <+16>:    add    eax,0x2dfb
   0x000011e2 <+21>:    sub    esp,0xc
   0x000011e5 <+24>:    lea    edx,[ebp-0x10]
   0x000011e8 <+27>:    push   edx
   0x000011e9 <+28>:    mov    ebx,eax
   0x000011eb <+30>:    call   0x1070 <gets@plt>
   0x000011f0 <+35>:    add    esp,0x10
   0x000011f3 <+38>:    nop
   0x000011f4 <+39>:    mov    ebx,DWORD PTR [ebp-0x4]
   0x000011f7 <+42>:    leave
   0x000011f8 <+43>:    ret

提供的消息略有不同,EIP 被覆盖

为什么这会有所作为?为什么在main函数中创建buffer时EIP没有被覆盖?

我正在使用:gcc version 9.3.0 (Ubuntu 9.3.0-17ubuntu1~20.04)

并编译为:gcc -m32 -g -fno-stack-protector source.c -o vuln -z execstack

解决方法

差异非常随意。 GCC 生成的确切序言/尾声指令序列对于第二个示例中的 over() 与第一个示例中的 main() 不同。因此,从调试器的角度来看,它以一种非常不同的方式使其崩溃。在 GDB 中单步执行后,您可以看到原因,而我刚刚为此消磨了一些时间。

从gets() 返回时堆栈彻底损坏,因此所有赌注都已关闭,但无论如何,这里是。我运行第一个例子,在调用gets()返回后立即设置断点:

(gdb) disassemble 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>:    sub    $0xc,%esp
   0x0804843f <+20>:    lea    -0x10(%ebp),%eax
   0x08048442 <+23>:    push   %eax
   0x08048443 <+24>:    call   0x80482e0 <gets@plt>
   0x08048448 <+29>:    add    $0x10,%esp
   0x0804844b <+32>:    mov    $0x0,%eax
   0x08048450 <+37>:    mov    -0x4(%ebp),%ecx
   0x08048453 <+40>:    leave  
   0x08048454 <+41>:    lea    -0x4(%ecx),%esp
   0x08048457 <+44>:    ret    
End of assembler dump.
(gdb) b *0x08048448
Breakpoint 1 at 0x8048448: file source.c,line 6.
(gdb) 

现在继续输入一些垃圾,打断点,开始单步执行:

(gdb) r
Starting program: /home/lstrand/tmp/vuln 
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

Breakpoint 1,0x08048448 in main (argc=<error reading variable: Cannot access memory at address 0x41414141>,argv=<error reading variable: Cannot access memory at address 0x41414145>) at source.c:6
6       gets(buf); 
(gdb) disassemble
Dump of assembler code for function main:
   0x0804842b <+0>: lea    0x4(%esp),%eax
   0x08048442 <+23>:    push   %eax
   0x08048443 <+24>:    call   0x80482e0 <gets@plt>
=> 0x08048448 <+29>:    add    $0x10,%esp
   0x08048457 <+44>:    ret    
End of assembler dump.
(gdb) bt
#0  0x08048448 in main (argc=<error reading variable: Cannot access memory at address 0x41414141>,argv=<error reading variable: Cannot access memory at address 0x41414145>) at source.c:6
Backtrace stopped: Cannot access memory at address 0x4141413d
(gdb) stepi
0x0804844b  6       gets(buf); 
(gdb) 
7   }
(gdb) 
0x08048453  7   }
(gdb) 
0x08048454  7   }
(gdb) 
0x08048457  7   }
(gdb) 

Program received signal SIGSEGV,Segmentation fault.
0x08048457 in main (argc=<error reading variable: Cannot access memory at address 0x41414141>,argv=<error reading variable: Cannot access memory at address 0x41414145>) at source.c:7
7   }
(gdb) bt
#0  0x08048457 in main (argc=<error reading variable: Cannot access memory at address 0x41414141>,argv=<error reading variable: Cannot access memory at address 0x41414145>) at source.c:7
Backtrace stopped: Cannot access memory at address 0x4141413d
(gdb) info reg
eax            0x0  0
ecx            0x41414141   1094795585
edx            0xf7fa589c   -134588260
ebx            0x0  0
esp            0x4141413d   0x4141413d
ebp            0x41414141   0x41414141
esi            0xf7fa4000   -134594560
edi            0x0  0
eip            0x8048457    0x8048457 <main+44>
eflags         0x10286  [ PF SF IF RF ]
cs             0x23 35
ss             0x2b 43
ds             0x2b 43
es             0x2b 43
fs             0x0  0
gs             0x63 99
(gdb) 

在这里,我们死在 main() 中的 ret 指令上,因为堆栈指针 esp 的值是错误的 0x4141413d。 GDB 正确地将失败的指令定位为在 main() 中。

但是在 over() 的情况下会发生什么?一起来看看:

lstrand@styx:~/tmp$ gdb ./vuln2
GNU gdb (Ubuntu 8.1-0ubuntu3.2) 8.1.0.20180409-git
Copyright (C) 2018 Free Software Foundation,Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY,to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions,please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help,type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from ./vuln2...done.
(gdb) disassemble over
Dump of assembler code for function over:
   0x0804842b <+0>: push   %ebp
   0x0804842c <+1>: mov    %esp,%ebp
   0x0804842e <+3>: sub    $0x18,%esp
   0x08048431 <+6>: sub    $0xc,%esp
   0x08048434 <+9>: lea    -0x10(%ebp),%eax
   0x08048437 <+12>:    push   %eax
   0x08048438 <+13>:    call   0x80482e0 <gets@plt>
   0x0804843d <+18>:    add    $0x10,%esp
   0x08048440 <+21>:    nop
   0x08048441 <+22>:    leave  
   0x08048442 <+23>:    ret    
End of assembler dump.
(gdb) b *0x0804843d
Breakpoint 1 at 0x804843d: file source2.c,line 5.
(gdb) r
Starting program: /home/lstrand/tmp/vuln2 
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAa

Breakpoint 1,0x0804843d in over () at source2.c:5
5       gets(buf); 
(gdb) disassemble
Dump of assembler code for function over:
   0x0804842b <+0>: push   %ebp
   0x0804842c <+1>: mov    %esp,%eax
   0x08048437 <+12>:    push   %eax
   0x08048438 <+13>:    call   0x80482e0 <gets@plt>
=> 0x0804843d <+18>:    add    $0x10,%esp
   0x08048440 <+21>:    nop
   0x08048441 <+22>:    leave  
   0x08048442 <+23>:    ret    
End of assembler dump.
(gdb) info reg
eax            0xffffd198   -11880
ecx            0xf7fa45c0   -134593088
edx            0xf7fa589c   -134588260
ebx            0x0  0
esp            0xffffd180   0xffffd180
ebp            0xffffd1a8   0xffffd1a8
esi            0xf7fa4000   -134594560
edi            0x0  0
eip            0x804843d    0x804843d <over+18>
eflags         0x246    [ PF ZF IF ]
cs             0x23 35
ss             0x2b 43
ds             0x2b 43
es             0x2b 43
fs             0x0  0
gs             0x63 99
(gdb) stepi
6   }
(gdb) 
0x08048441  6   }
(gdb) 
0x08048442  6   }
(gdb) stepi
0x41414141 in ?? ()
(gdb) info reg
eax            0xffffd198   -11880
ecx            0xf7fa45c0   -134593088
edx            0xf7fa589c   -134588260
ebx            0x0  0
esp            0xffffd1b0   0xffffd1b0
ebp            0x41414141   0x41414141
esi            0xf7fa4000   -134594560
edi            0x0  0
eip            0x41414141   0x41414141
eflags         0x286    [ PF SF IF ]
cs             0x23 35
ss             0x2b 43
ds             0x2b 43
es             0x2b 43
fs             0x0  0
gs             0x63 99
(gdb) stepi

Program received signal SIGSEGV,Segmentation fault.
0x41414141 in ?? ()
(gdb) 

注意这里的细微差别。在这种情况下,尾声代码使用简单的算术展开 %esp:“add $0x10,%esp”(而不是像第一种情况那样从堆栈中恢复它)。 'leave' 指令将垃圾放入帧指针 %eb​​p,但从 %ebp 获得的新 %esp 值仍然有效。然后 ret 指令成功执行,给我们留下一个坏 ip,0x41414141。然后然后程序因 SIGSEGV 试图从无处读取指令而终止。

在这种情况下,GDB 没有希望解开堆栈:

Program received signal SIGSEGV,Segmentation fault.
0x41414141 in ?? ()
(gdb) bt
#0  0x41414141 in ?? ()
#1  0x41414141 in ?? ()
#2  0x41414141 in ?? ()
#3  0x41414141 in ?? ()
#4  0x41414141 in ?? ()
#5  0xf7006141 in ?? ()
#6  0xf7fa4000 in ?? () from /lib/i386-linux-gnu/libc.so.6
Backtrace stopped: previous frame inner to this frame (corrupt stack?)
(gdb) 

回想一下第一种情况,程序在 ret 指令本身上死亡,因为 %esp 已经坏了。在第一种情况下,GDB 仍然可以找到程序所在的位置,但在第二种情况下就不能了。