由于是刚刚学习一下Linux下的栈溢出,所以对于GDB调试也很不熟悉。以前在windows下做过栈溢出,感觉和linux的原理是相同的,就是在linux用GDB调试没有在windows下用OllyDbg方便。
首先存在溢出的代码:
#include"stdio.h" void return_input(void) { char array[30]; gets(array); printf("%s\n",array); } main() { return_input(); return 0; }
gcc3.4.6编译:
gcc -mpreferred-stack-boundary=2 -ggdb overflow.c -o overflow
其中“-mpreferred-stack-boundary=2 ”是使编译的时候栈以双字节为单位递增(或递减),否则gcc将对栈进行优化。 “-ggdb”是让编译后的程序支持gdb调试。
#gdb ./overflow
GNU gdb (GDB) 7.1-ubuntu
Copyright (C) 2010 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 "i486-linux-gnu".
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>...
Reading symbols from /root/Desktop/code/overflow...done.
查看input_return()的反汇编代码:
(gdb) disas return_input
Dump of assembler code for function return_input:
0x0804837c <+0>:push %ebp
0x0804837d <+1>:mov %esp,%ebp
0x0804837f <+3>:sub $0x28,%esp
0x08048382 <+6>:lea -0x20(%ebp),%eax
0x08048385 <+9>:mov %eax,(%esp)
0x08048388 <+12>:call 0x80482cc <gets@plt>
0x0804838d <+17>:lea -0x20(%ebp),%eax
0x08048390 <+20>:mov %eax,0x4(%esp)
0x08048394 <+24>:movl $0x804847c,(%esp)
0x0804839b <+31>:call 0x80482ec <printf@plt>
0x080483a0 <+36>:leave
0x080483a1 <+37>:ret
End of assembler dump.
需要注意的是linux的AT&T格式的汇编与Linux下的不同
AT&T的汇编语法中目标操作数在原操作数的右边
两个call分别是gets函数和Printf函数,测试发现当输入的字符大于35时将报错
root@bt:~/Desktop/code# ./overflow
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
Segmentation fault
给两个函数分别下断点:
(gdb) break *0x08048388
Breakpoint 1 at 0x8048388: file overflow.c, line 6.
(gdb) break *0x0804839b
Breakpoint 2 at 0x804839b: file overflow.c, line 7.
然后运行:
(gdb) run
Starting program: /root/Desktop/code/overflow
Breakpoint 1, 0x08048388 in return_input () at overflow.c:6
6gets(array);
程序中断在第一个断点(return_input函数)
先看看main函数的汇编代码:
(gdb) disas main
Dump of assembler code for function main:
0x080483a2 <+0>:push %ebp
0x080483a3 <+1>:mov %esp,%ebp
0x080483a5 <+3>:call 0x804837c <return_input>
0x080483aa <+8>:mov $0x0,%eax
0x080483af <+13>:pop %ebp
0x080483b0 <+14>:ret
End of assembler dump.
有windows下溢出基础的都知道,现在函数执行到return_input(),那么要返回到main函数,那么返回地址保存在栈当中,返回地址就是call 0x804837c <return_input> 这条指令的下一条指令的地址,即0x080483aa,好,现在我们看看堆栈的情况:
(gdb) x/20x $esp
0xbffff4f8: 0xbffff500 0x080483e9 0xb7fc9324 0xb7fc8ff4
0xbffff508: 0x080483d0 0xbffff528 0xb7ea24a5 0xb7ff1030
0xbffff518: 0x080483db 0xb7fc8ff4 0xbffff528 0x080483aa
0xbffff528: 0xbffff5a8 0xb7e89bd6 0x00000001 0xbffff5d4
0xbffff538: 0xbffff5dc 0xb7fe1858 0xbffff590 0x0177ff8e
可以看到main函数的返回的地址保存在0xbffff518 +0x0c处
现在我们尝试输入35个A看看
(gdb) continue
Continuing.
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
Breakpoint 2, 0x0804839b in return_input () at overflow.c:7
7printf("%s\n",array);
gets函数得到执行,现在看看堆栈的情况:
(gdb) x/20x 0xbffff4f8
0xbffff4f8:0x0804847c 0xbffff500 0x61616161 0x61616161
0xbffff508:0x61616161 0x61616161 0x61616161 0x61616161
0xbffff518:0x61616161 0x61616161 0x00616161 0x080483aa
0xbffff528:0xbffff5a8 0xb7e89bd6 0x00000001 0xbffff5d4
0xbffff538:0xbffff5dc 0xb7fe1858 0xbffff590 0x0177ff8e
很好,我们看到现在返回地址前面的栈空间已经被大量的61(‘a’的ASCII码)覆盖,这也解释
了为什么输入36个字符会崩溃。注意字符这个字符串是以'\0'结尾的,还有就是注意内存的大小
端问题。
现在就是要覆盖这个返回地址,当执行ret指令的时候将会把这个返回地址弹出给EIP,这样,我们
就可以用错误的返回地址控制EIP,改变程序的执行流程。
第二次调试我们尝试覆盖返回地址,用40个‘a’。
分别在gets和ret上设置断点:
(gdb) break * 0x08048388
Breakpoint 1 at 0x8048388: file overflow.c, line 6.
(gdb) break * 0x080483a1
Breakpoint 2 at 0x80483a1: file overflow.c, line 8.
然后继续执行程序:
(gdb) continue
Continuing.
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
Breakpoint 2, 0x080483a1 in return_input () at overflow.c:8
8}
查看一下堆栈:
(gdb) x/20x 0xbffff4f8
0xbffff4f8:0x0804847c 0xbffff500 0x61616161 0x61616161
0xbffff508:0x61616161 0x6161616 10x61616161 0x61616161
0xbffff518:0x61616161 0x61616161 0x61616161 0x61616161
0xbffff528:0xbffff500 0xb7e89bd6 0x00000001 0xbffff5d4
0xbffff538:0xbffff5dc 0xb7fe1858 0xbffff590 0x0177ff8e
可以看到返回地址已经被完全覆盖
看看EIP寄存器:
(gdb) x/1i $eip
=> 0x80483a1 <return_input+37>:ret
然后单步执行ret指令
(gdb) stepi
0x61616161 in ?? ()
(gdb) x/1i $eip
=> 0x61616161:Cannot access memory at address 0x61616161
看到EIP已经被我们输入的字符所控制,当然如果这里放是一段恶意代码(比如shellcode)
那么程序的执行流程就可以完全被我们的输入所控制,,,
待续....