问题描述
以这个例子为例:
#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' 指令将垃圾放入帧指针 %ebp,但从 %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 仍然可以找到程序所在的位置,但在第二种情况下就不能了。