由於是剛剛學習一下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)
那麼程序的執行流程就可以完全被我們的輸入所控制,,,
待續....