网络渗透技术入门篇
網絡滲透技術入門篇
緩沖區溢出通常是向數組中寫數據時,寫入的數據的長度超出了數組原始定義的大小。?
比如前面你定義了intbuff[10],那么只有buff[0]-buff[9]的空間是我們定義buff時?
申請的合法空間,但后來往里面寫入數據時出現了buff[12]0x10則越界了。C語言常用的?
strcpy、sprintf、strcat等函數都非常容易導致緩沖區溢出問題。?
? ? 查閱C語言編程的書籍時通常會告訴你程序溢出后會發生不可預料的結果。在網絡安?
全領域,緩沖區溢出利用的藝術在于讓這個“不可預料的結果”變為我們期望的結果。?
看下面這個演示程序:buf.c?
/*buffer overflow exampleby?2133529@qq.com*/?
#include<stdio.h>?
voidwhy_here(void) /*這個函數沒有任何地方調用過*/?
{?
? ?? ???printf("whyu here?! ");?
? ?? ???_exit(0);?
}?
intmain(intargc,char * argv[])?
{?
? ?? ???intbuff[1];?
? ?? ???buff[2] (int)why_here;?
? ?? ???return 0;?
}?
在命令行用VC的命令行編譯器編譯(在Linux下用gcc編譯并運行也是同樣結果):?
C:\Temp>clbuf.c?
運行程序:
C:\Temp>buf.EⅩE?
whyu here?!?
仔細分析程序和打印信息,你可以發現程序中我們沒有調用過why_here函數,但該函數卻?
在運行的時候被調用了!!?
這里唯一的解釋是buff[2]why_here;操作導致了程序執行流程的變化。?
要解釋此現象需要理解一些C語言底層(和計算機體系結構相關)及一些匯編知識,尤其是?
“棧”和匯編中CALL/RET的知識,如果這方面你尚有所欠缺的話建議參考一下相關書籍,?
否則后面的內容會很難跟上。?
假設你已經有了對棧的基本認識,我們來理解一下程序運行情況:?
進入main函數后的棧內容下:?
[ eip ][ ebp ][buff[0]]?
高地址??<----? ?? ?? ?低地址?
以上3個存儲單元中eip為main函數的返回地址,buff[0]單元就是buff申明的一個int?
空間。程序中我們定義intbuff[1],那么只有對buff[0]的操作才是合理的(我們只申請?
了一個int空間),而我們的buff[2]why_here操作超出了buff的空間,這個操作越界了,?
也就是溢出了。溢出的后果是:對buff[2]賦值其實就是覆蓋了棧中的eip存放單元的數?
據,將main函數的返回地址改為了why_here函數的入口地址。這樣main函數結束后返回的時候將這個地址作為了返回地址而加以運行。?
上面這個演示是緩沖區溢出最簡單也是最核心的溢出本質的演示,需要仔細的理解。如果還?
不太清楚的話可以結合對應的匯編代碼理解。?
用VC的命令行編譯器編譯的時候指定FA參數可以獲得對應的匯編代碼(Linux平臺可以用?
gcc的-S參數獲得):?
C:\Temp>cl/FA tex.c?
C:\Temp>typetex.asm?
? ?? ???TITLE??tex.c?
? ?? ???.386P?
include listing.inc?
if@Version gt510?
.modelFLAT?
else?
_TEXT??SEGMENTPARA USE32PUBLIC 'CODE'?
_TEXT ENDS?
_DATA??SEGMENTDWORDUSE32PUBLIC 'DATA'?
_DATA??ENDS?
CONST??SEGMENTDWORDUSE32PUBLIC 'CONST'?
CONST ENDS?
_BSS??SEGMENTDWORDUSE32PUBLIC 'BSS'?
_BSS??ENDS?
$$SYMBOLS? ?? ?? ? SEGMENTBYTEUSE32 'DEBSYM'?
$$SYMBOLS? ?? ?? ? ENDS?
_TLS??SEGMENTDWORDUSE32PUBLIC 'TLS'?
_TLS??ENDS?
FLAT??GROUP_DATA,CONST,_BSS?
? ?? ?? ?ASSUME CS:FLAT,DS:FLAT,SS:FLAT?
endif?
INCLUDELIB LIBC?
INCLUDELIB OLDNAMES?
_DATA??SEGMENT?
$SG775 DB? ?? ?? ? 'whyu here?!',0aH,00H?
_DATA??ENDS?
PUBLIC _why_here?
EXTRN _printf:NEAR?
EXTRN __exit:NEAR?
_TEXT??SEGMENT?
_why_herePROCNEAR?
? ?? ?? ? push? ???ebp?
? ?? ?? ?mov? ?? ? ebp, esp?
? ?? ?? ? push? ???OFFSETFLATSG775?
? ?? ?? ? call??_printf?
? ?? ?? ? add? ?? ?esp,4?
? ?? ?? ? push? ???0?
? ?? ?? ? call??__exit?
? ?? ?? ? add? ?? ?esp,4?
? ?? ?? ? pop? ?? ?ebp?
? ?? ?? ? ret? ?? ?0?
_why_hereENDP?
_TEXT ENDS?
PUBLIC _main?
_TEXT??SEGMENT?
_buff$ -4? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ? ;size 4?
_argc$ 8? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ???;size 4?
_argv$??12? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?;size 4?
_main PROCNEAR?
? ?? ?? ? push? ???ebp?
? ?? ?? ?mov? ?? ? ebp, esp?
? ?? ?? ? push? ???ecx?
? ?? ???mov? ???DWORD PTR_buff$[ebp+8],OFFSETFLAT:_why_here?
? ?? ???xor? ???eax, eax?
? ?? ???mov? ???esp, ebp?
? ?? ???pop? ???ebp?
? ?? ???ret? ???0?
_main ENDP?
_TEXT ENDS?
END?
這個例子中我們溢出buff后覆蓋了棧中的函數返回地址,由于覆蓋數據為棧中的數據,所?
以也稱為棧溢出。對應的,如果溢出覆蓋發生在堆中,則稱為堆溢出,發生在已初始化數據?
區的則稱為已初始化數據區溢出。?
實施對緩沖區溢出的利用 (即攻擊有此問題的程序)需要更多尚未涉及的主題:?
??1. shellcode功能?
??2. shellcode存放和地址定位?
??3. 溢出地址定位?
這些將在以后的章節中詳細講解。?
SHELLCODE基礎?
? ? 溢出發生后要控制溢出后的行為關鍵就在于shellcode的功能。shellcode其實就是一?
段機器碼。因為我們平時頂多用匯編寫程序,絕對不會直接用機器碼編寫程序,所以感覺?
shellcode非常神秘。這里讓我們來揭開其神秘面紗。?
看看程序shell0.c:?
#include<stdio.h>?
intadd(intx,inty) {?
? ?? ???return x+y;?
}?
intmain(void) {?
? ?? ???resultadd(129,127);?
? ?? ???printf("result%i ",result);?
? ?? ???return 0;?
}?
這個程序太簡單了!那么我們來看看這個程序呢?shell1.c?
#include<stdio.h>?
#include<stdlib.h>?
int add(intx,inty)?
{?
? ? return x+y;?
}?
typedef int (*PF)(int,int);?
intmain(void)?
{?
? ? unsignedcharbuff[256];?
? ? unsignedchar *ps (unsignedchar *)&add;/*ps指向add函數的開始地址*/?
? ? unsignedchar *pdbuff;?
? ? intresult0;?
? ? PF pf (PF)buff;?
? ? while(1)?
? ? {?
? ?? ???*pd*ps;?
? ?? ???printf("[url=file://\\x%02x]\\x%02x",*ps[/url]);?
? ?? ???if(*ps 0xc3)?
? ?? ???{?
? ?? ?? ?? ?break;?
? ?? ???}?
? ?? ???pd++,ps++;?
? ? }?
? ? resultpf(129,127); /*此時的pf指向buff*/?
? ? printf(" result%i ",result);?
? ? return 0;?
編譯出來運行,結果如下:?
shell:\x55\x89\xe5\x8b\x45\x0c\x03\x45\x08\x5d\xc3?
result25?
shell1和shell0的不同之處在于shell1將add函數對應的機器碼從代碼空間拷貝到了buff?
中(拷貝過程中順便把他們打印出來了),然后通過函數指針運行了buff中的代碼!?
關鍵代碼解釋:?
unsignedchar *ps? ?? ? (unsignedchar *)&add;?
&add 為函數在代碼空間中開始地址,上面語句讓ps指向了add函數的起始地址。?
PF pf (PF)buff;?
讓pf函數指針指向buff,以后調用pf函數指針時將會把buff中的數據當機器碼執行。?
*pd *ps;?
把機器碼從add函數開始的地方拷貝到buff數組中。
if(*ps??0xc3) {break }?
每個函數翻譯為匯編指令后都是以ret指令結束,ret指令對應的機器碼為0xc3,這個判斷?
控制拷貝到函數結尾時停止拷貝,退出循環。?
resultpf(129,127);?
由于pf指向buff,這里調用pf后將把buff中的數據作為代碼執行。?
shell1和shell0做的事情一樣,但機制就差別很大了。值得注意的是shell1的輸出中這?
一行:?
shell:\x55\x89\xe5\x8b\x45\x0c\x03\x45\x08\x5d\xc3?
直接以C語言表示字符串的形式將平時深藏不露的機器碼給打印了出來。其對應的C語言代?
碼是:?
? ?? ?intadd(intx,inty) {?
? ?? ???return x+y;?
? ? }?
對應的匯編碼(AT&T的表示)為:?
? ? pushl %ebp?
? ? movl??%esp,%ebp?
? ? movl? ? 12(%ebp),%eax?
? ? addl??8(%ebp),%eax?
? ? popl??%ebp?
? ? ret?
接下來理解這個程序應該就很容易了shell2.c:?
#include<stdio.h>?
typedef int (*PF)(int,int);?
intmain(void)?
{?
? ? unsignedcharbuff[] "\x55\x89\xe5\x8b\x45\x0c\x03\x45\x08\x5d\xc3";?
? ? PF pf (PF)buff;?
? ? intresult0;?
? ? resultpf(129,127);?
? ? printf("result%i ",result);?
? ? return 0;?
}?
我們直接把add函數對應的機器碼寫到buff數組中,然后直接從buff中運行add功能。?
編譯運行結果為:?
result256?
本質上來看上面的"\x55\x89\xe5\x8b\x45\x0c\x03\x45\x08\x5d\xc3"就是一段?
shellcode。shellcode的名稱來源和Unix的Shell有些關系,早期攻擊程序中shellcode?
的功能是開啟一個新的shell,也就是說溢出攻擊里shellcode的功能遠遠不像我們演示中這么簡單,需要完成更多的功能。無論shellcode完成什么功能,其本質就是一段能完成更?
多功能的機器碼。當然要做更多事情的shellcode的編寫需要解決很多這里沒有遇到的問?
題,如:?
? ? 1. 函數重定位?
? ? 2.?系統調用接口?
? ? 3. 自身優化?
? ? 4. 等等。?
程序進程空間地址定位?
這個標題比較長,得要解釋一下。這里有一個經常會混淆的概念要澄清一下,程序的源代碼?
稱為程序源代碼,源代碼編譯后的二進制可執行文件稱為程序,程序被運行起來后內存中和?
他相關的內存資源和CPU資源的總和稱為進程。程序空間其實指的是進程中內存布局和內存?
中的數據。再通俗點就是程序被運行起來時其內存空間的布局。?
這點需要記住:一個程序被編譯完成后其運行時內部的內存空間布局就已經確定。這個編譯?
好的二進制文件在不同時間,不同機器上(當然操作系統得是一樣的)運行,其內存布局是?
完全相同的(一些特例除外,后面會說到)。這就是內存空間地址定位的基礎!?
寫一程序a.c如下:?
#include<stdio.h>?
char *p "Hello";?
inta 10;?
intmain(intargc,char * argv[])?
{?
? ?? ???intb[0];?
? ?? ???char * fmalloc(8);?
? ?? ???printf("p contentaddr:%p ",p);?
? ?? ???printf("ppointaddr:%p ",&p);?
? ?? ???printf("aaddr:%p ",&a);?
? ?? ???printf("b addr:%p ",&b);?
? ?? ???printf("f contentaddr:%p ",f);?
? ?? ???printf("main fun addr:%p ",&main);?
}?
編譯:gcca.c-oa #Win下用cla.c編譯,以下以Linux為例,Win系統同樣適用?
在我的Ubuntu 7.04上執行:?
cloud@dream:~/Work/cloud$./a?
p contentaddr:0x804852c?
p pointaddr:0x80496a8?
a addr:0x80496ac?
b addr:0xbffff9e4?
f contentaddr:0x804a008?
main fun addr:0x80483b4?
這里我們可以看到我們各變量在內存中的地址。?
過幾分鐘再執行一次:?
cloud@dream:~/Work/cloud$./a?
p contentaddr:0x804852c?
p pointaddr:0x80496a8?
a addr:0x80496ac?
b addr:0xbffff9e4?
f contentaddr:0x804a008?
main fun addr:0x80483b4?
看兩次執行時這些變量在內存中的地址是完全一樣的。?
(如果不一樣的話表示你的Kernel作了棧隨機處理,這個機制是專門防范溢出用的,對安全?
而言這個機制非常有用,但對你學習而言則帶來不少麻煩,為了學習方便,可以先用以下方?
法禁用內核的這個功能:sudoroot,然 echo0 >/proc/sys/kernel/randomize_va_space ;?
如果是RedHat系列,可以通過echo0 >/proc/sys/kernel/exec-shield-randomize禁用。)?
那么我們的程序執行起來時內存布局是啥樣的呢?這點可以通過nm、dumpbin.EⅩE、IDA Pro?
等工具看到,這里是IDA Pro對可執行二進制程序a進行分析的結果:?
從中我們可以看到內存空間被分為多個段,其中.text段存放程序代碼,起始地址為?
0x8048310,結束地址為0x8048508。a程序執行結果中輸出了:?
main fun addr:0x80483b4?
可見main函數起始地址為0x80483b4,正好落在.text段內。?
有空你可以把a程序輸出中各個地址拿到這里來對對,看看各個變量都在什么段里,至于各?
個段存有什么用,這里就不一一講了,有空的話你可以google一下。?
另外需要說明的是棧空間的結束地址是固定的,在Linux下為:0xc0000000,a程序執行時?
輸出的:?
b addr:0xbffff9e4?
這個地址就是在棧中。?
為什么棧的起始地址不固定而是結束地址固定?這個就需要你查查手邊x86匯編手冊關于?
棧和函數調用的章節了。?
以上內容是為了讓你對程序空間有個直觀的認識,如果不是很清楚也沒有關系,這基本不影?
響后面的閱讀。?
好了到現在我們基礎知識已經夠用了,來看看這個程序space.c:?
#include<stdio.h>?
#include<stdlib.h>?
int add(intx,inty)?
{?
? ? return x+y;?
}?
int mul(intx,inty)?
{?
? ? return x*y;?
}?
typedef int (*PF)(int,int);?
intmain(intargc,char *argv[])?
{?
? ? PF pf;/* 函數指針pf*/?
? ? charbuff[4];/*buff溢出后將覆蓋pf*/?
? ? intt0;?
? ? pf (PF)&mul;/* 函數指針默認指向mul函數的起始地址*/?
? ? printf("addr addfun :%p ",&add);?
? ? printf("addrmulfun :%p ",&mul);?
? ? printf("pf0x%x ",pf);?
? ? if(argc >1)?
? ? {?
? ?? ???memcpy(buff,argv[1],8);?
? ? }?
? ? printf("now pf0x%x ",pf);?
? ? tpf(4,8);?
? ? printf("4*8%i ",t);?
}?
程序開始我們定義了PF pf;接著定義了charbuff[4];?
此時程序棧中空間片斷如下:?
[pf值,占4字節 ] [ buff的4字節 ]?
高地址? ?? ???←--------? ?? ?? ???低地址?
這樣buff操作發生溢出則會覆蓋pf的值,而pf中我們默認存放mul函數的起始地址,并?
且我們后面會通過tpf(4,8);來執行其指向地址的機器碼。?
默認情況下如果不指定命令行參數,那么不會執行memcpy操作,此時pf中存放mul函數起?
始地址,pf(4,8)時會執行mul函數。?
這里我們明確強調一點,所謂函數就是程序運行時內存中存放的對應機器碼,函數名如add?
和&add都是指其對應機器碼的起始內存地址。?
執行一下space程序看看輸出:?
cloud@dream:~/Work/cloud$./space?
addr addfun :0x8048374?
addrmulfun :0x804837f?
pf0x804837f?
now pf0x804837f?
4*8 32?
輸出非常正常,add起始地址為0x8048374,從這個地址開始放著add函數對應的機器碼;?
mul起始地址為0x804837f,pf值為0x804837f,即mul起始地址,pf(4,8)就是執行pf所?
指向地址的機器碼,傳入參數為4和8;最后輸出4*8 32。?
好戲開始了,我們指定一下命令行參數aaaaABCD:?
cloud@dream:~/Work/cloud$./spaceABCDABCD?
addr addfun :0x8048374?
addrmulfun :0x804837f?
pf0x804837f?
now pf0x44434241?
段錯誤 (coredumped)?
這次buff發生了溢出,覆蓋了pf中的內容,現在pf值為0x44434241,最后程序崩潰。?
為什么pf值為0x44434241呢?!?
因為:?
字符’A’對應的ascii值為0x41?
字符’B’對應的ascii值為0x42?
字符’C’對應的ascii值為0x43?
字符’D’對應的ascii值為0x44?
考慮到x86內存中字節序為低位在前,反過來就像當于’ABCD了’!?
這表示什么??
這表示我們通過命令行利用溢出buff指定了函數指針pf的值了,我們這里指定了?
0x44434241,這樣pf(4,8)調用時,程序就轉到了地址0x44434241,由于0x44434241是無?
效空間(對照上面的程序空間中段的分布,沒有任何段包含了此地址就知道了),所以程序?
最后崩潰coredumped了。?
用gdb來看更直觀:?
cloud@dream:~/Work/cloud$gdb ./space?
(gdb)r aaaaABCD?
Startingprogram:/mnt/sec/cloud/cloud/spaceaaaaABCD?
addr addfun :0x8048374?
addrmulfun :0x804837f?
pf0x804837f?
now pf0x44434241?
Program received signalSIGSEGV,Segmentation fault.?
0x44434241in ?? ()?
(gdb)p $eip?
$2??(void (*)()) 0x44434241 #eip寄存器現在值為0x44434241?
(gdb)?
現在我們已經通過指定命令行參數,利用溢出修改了程序的執行流程,但由于我們指定的地?
址為無效地址導致程序崩潰。?
我們現在已經知道如果我們指定pf值為0x8048374就會執行add函數,如果指定為?
0x804837f,就會執行mul函數 。?
接下來就好辦了,我們來寫一個程序通過execve來執行space程序,給如下命令行參數:?
./spaceaaaa\x74\x83\x04\x08?
即有針對性的指定命令行參數來修改pf值為0x8048374,這樣space將調用add函數,而?
不是默認的mul !?
/* exp.c*/?
#include<stdio.h>?
intmain(void)?
{?
? ?? ???char * a0 "space";?
? ?? ???unsignedchar a1[128];?
? ?? ???char * arg[] {a0,a1,0};?
? ?? ???a1[0]'a';?
? ?? ???a1[1]'a';?
? ?? ???a1[2]'a';?
? ?? ???a1[3]'a';?
? ?? ???a1[4]0x74;?
? ?? ???a1[5]0x83;?
? ?? ???a1[6]0x04;?
? ?? ???a1[7]0x08;?
? ?? ???a1[8]0;?
? ?? ???execve("./space",arg,0);?
}?
cloud@dream:~/Work/cloud$gcc exp.c-o e?
cloud@dream:~/Work/cloud$./e?
addr addfun :0x8048374?
addrmulfun :0x804837f?
pf0x804837f?
now pf0x8048374?
4*8 12?
看輸出結果是4+8的值12了。?
現在程序的流程被我們通過溢出并指定add的內存地址來進行修改了。?
我們這里設計到了地址空間定位,主要有兩處:?
1. buff寫入多長后會發生溢出。由于這里源程序就在我們手里,一看PF pf;char buff?
? ? [4];就知道超過4字節就將覆蓋到pf值了,但很多時候我們沒有源程序,這就需要逆?
? ? 向工程分析+動態調試來獲取了。?
2. 用于覆蓋pf的數據應該是多少。我們這里用的是add函數的地址值0x8048374,并且我?
? ?們用程序直接打印出了其地址,所以一看就知道了,但如果程序不是我們自己,同樣需?
? ?要用逆向工程技巧+動態調試技巧來確定了。?
好了,以上我們已經可以通過溢出來修改目標程序流程了,已經掌握了溢出利用的精髓。現?
實生活中的溢出利用當然更復雜一點,需要更多的系統體系結構知識和N多的小技巧而已。?
相信你以后會逐步了解到所謂溢出,無論是什么類型的溢出,根本上就涉及兩個問題,用誰?
去覆蓋誰,概況一下就是通過一定技巧將指定的數據寫入到指定內存中。比如上面我們就是將指定數據0x8048374寫入到了pf的值所占有的內存空間中。 原文:www.hackbase.com/tech/2011-08-18/65013.html
總結
- 上一篇: AliasDB:简单统一灵活的数据库访问
- 下一篇: Hamcrest Tutorial