在main函数中覆盖EIP

Overwrite EIP in the main function

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

举个例子:

#include <stdio.h>

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

在这段代码中,要溢出的缓冲区是在 main 函数中创建的,结果我在输入了很多 '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 生成的确切 prologue/epilogue 指令序列对于第二个示例中的 over() 与第一个示例中的 main() 不同。所以从调试器的角度来看,它以一种非常不同的方式崩溃。在 GDB 中单步执行后,您会明白为什么,而我这样做只是消磨了一些时间。

从 gets() 返回时堆栈完全损坏,所以所有的赌注都没有了,但无论如何,开始吧。我 运行 第一个例子,调用 gets():

返回后立即设置断点
(gdb) disassemble main
Dump of assembler code for function main:
   0x0804842b <+0>: lea    0x4(%esp),%ecx
   0x0804842f <+4>: and    [=10=]xfffffff0,%esp
   0x08048432 <+7>: pushl  -0x4(%ecx)
   0x08048435 <+10>:    push   %ebp
   0x08048436 <+11>:    mov    %esp,%ebp
   0x08048438 <+13>:    push   %ecx
   0x08048439 <+14>:    sub    [=10=]x14,%esp
   0x0804843c <+17>:    sub    [=10=]xc,%esp
   0x0804843f <+20>:    lea    -0x10(%ebp),%eax
   0x08048442 <+23>:    push   %eax
   0x08048443 <+24>:    call   0x80482e0 <gets@plt>
   0x08048448 <+29>:    add    [=10=]x10,%esp
   0x0804844b <+32>:    mov    [=10=]x0,%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),%ecx
   0x0804842f <+4>: and    [=11=]xfffffff0,%esp
   0x08048432 <+7>: pushl  -0x4(%ecx)
   0x08048435 <+10>:    push   %ebp
   0x08048436 <+11>:    mov    %esp,%ebp
   0x08048438 <+13>:    push   %ecx
   0x08048439 <+14>:    sub    [=11=]x14,%esp
   0x0804843c <+17>:    sub    [=11=]xc,%esp
   0x0804843f <+20>:    lea    -0x10(%ebp),%eax
   0x08048442 <+23>:    push   %eax
   0x08048443 <+24>:    call   0x80482e0 <gets@plt>
=> 0x08048448 <+29>:    add    [=11=]x10,%esp
   0x0804844b <+32>:    mov    [=11=]x0,%eax
   0x08048450 <+37>:    mov    -0x4(%ebp),%ecx
   0x08048453 <+40>:    leave  
   0x08048454 <+41>:    lea    -0x4(%ecx),%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    [=12=]x18,%esp
   0x08048431 <+6>: sub    [=12=]xc,%esp
   0x08048434 <+9>: lea    -0x10(%ebp),%eax
   0x08048437 <+12>:    push   %eax
   0x08048438 <+13>:    call   0x80482e0 <gets@plt>
   0x0804843d <+18>:    add    [=12=]x10,%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,%ebp
   0x0804842e <+3>: sub    [=12=]x18,%esp
   0x08048431 <+6>: sub    [=12=]xc,%esp
   0x08048434 <+9>: lea    -0x10(%ebp),%eax
   0x08048437 <+12>:    push   %eax
   0x08048438 <+13>:    call   0x80482e0 <gets@plt>
=> 0x0804843d <+18>:    add    [=12=]x10,%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 仍然可以找到程序所在的位置,但在第二种情况下,它不能。