棧溢位漏洞原理及基本利用(ret2addr,ret2arg)
菜雞總結下,方便複習。
ret2addr和ret2arg這兩種利用手法在《黑手緩衝區溢位教程》裡有所提及。這兩種只是基本的利用手法,如果開啟了NX(堆疊程式碼不可執行)或者ASLR就無用武之地了,需要更高階的利用手法,例如ret2libc,ret2plt,和ROP等高階利用手法,這篇筆記就只說下基本的利用手法及漏洞原理。
瞭解棧溢位漏洞,需要對彙編裡的call指令(相當於push eip和jmp 函式首地址 ),ret指令(相當於pop eip),函式的呼叫過程有所瞭解。《加密與解密》的逆向分析技術篇中函式部分說的很清楚。下面也會有所介紹。
簡介
棧溢位是向棧中寫入超過原本長度限制的資料,使棧中的其他資料被覆蓋,常見的是覆蓋棧中返回地址,改變程式的執行流程。
棧溢位漏洞成立需要兩個條件,其一是:有向棧中寫入資料的行為,另一個是:使用了gets,strcpy,strcat等 不限制資料輸入長度或者不檢查陣列長度的函式。
預備知識
函式的呼叫和返回等過程都是在棧中完成的,棧中也儲存著區域性變數和函式的引數。
說之前先複習下函式呼叫的知識。
呼叫函式前,如果函式有引數,需要先將引數傳入棧中(值傳遞本質是將變數複製一份壓入堆疊,而地址傳遞,是將變數的地址直接壓入棧中,通過加上中括號[],直接訪問)
一般情況下引數的入棧是從右往左依次入棧的(cdecl呼叫約定,stdcall,fastcall等)。
值傳遞模板(非fastcall呼叫約定,fastcall呼叫約定前兩個引數會直接用暫存器,不用堆疊,後面的引數仍然用堆疊傳參)如下:
mov eax,dword ptr [EBP-xxx] //push 後無法直接接記憶體單元,需要先傳給暫存器 push eax //壓入堆疊傳參 mov eax,dword ptr [xxx] push eax .....
傳完引數後會call 函式(call 會將呼叫函式即母函式的call指令的下一條要執行的指令的地址壓入堆疊,然後再跳轉到函式程式碼段的首地址,這和cpu執行指令的過程有關,cpu執行指令的過程如下:1。讀取EIP指向的指令,將其放入指令緩衝器,2。EIP指向下一條指令,3。執行指令緩衝器裡的指令,然後返回 1。)
call 函式程式碼首地址
函式內容有個模板(下面為debug版,release版會有所不同)如下:
push ebp mov ebp,esp //這裡提升堆疊 sub esp,0x40 //這裡是開闢緩衝區,不同編譯器開闢的緩衝區大小不同(會根據你所用的變數的多少和大小來開闢)。 push edi push esi push ebx //保留現場 lea edi, dword ptr ss:[ebp-0x40] mov ecx,0x10 mov eax,0xcccccccc rep stos dowrd ptr es:[edi] //填充緩衝區(清除垃圾資料),用於存放區域性變數 ---------------- 這裡是寫函式的功能 ---------------- pop ebx pop esi pop edi //恢復現場 mov esp,ebp pop ebp ret
執行完call函式後的堆疊圖如下(下面是高址,上面是低址):
此時,EBP的位置就很關鍵了,使用EBP+xxx,可以訪問到傳入棧中的引數,向上EBP-xxx,可以訪問區域性變數。
當函式的程式碼執行到
mov esp,ebp
pop ebp
ret
此時ESP指向棧中的返回地址,執行ret執行(相當於pop eip)後就會將返回地址賦值給EIP,函式就執行完畢了,此時EIP重新指向母函式。
漏洞原理
漏洞的關鍵就是利用gets,strcpy,strcat 等函式,輸入或者拼接超過字元陣列原先規定的長度的字串。
例如原先定義的字元陣列 a[8],你使用gets函式,輸入了"AAAAAAAAAAAABBBB",原先編譯器編譯成彙編時並沒有預留足夠的空間,例如前面函式模板中的 sub esp,0x40
,他只開闢了0x40的空間,那麼多輸入的字串就會將堆疊中的其他內容覆蓋掉。
呼叫gets前的堆疊圖如下:
呼叫gets,輸入"AAAAAAAAAAAABBBB"後的堆疊圖如下:
起始EBP值,和返回地址以及後面的堆疊空間都可以被輸入的字串所覆蓋,這就是緩衝區溢位漏洞。
基本利用之ret2addr
ret2addr就是 return to address ,就是將堆疊裡的返回地址覆蓋為你所編寫的shellcode的首地址上,ret2addr特指的是緩衝區裡的shellcode。
利用緩衝區溢位後的堆疊圖如下:
當ret後 ,EIP就會指向shellcode的首地址,這樣就能執行你的shellcode了。如何找到shellcode的首地址,在下篇筆記再提及。
基本利用之ret2arg
與ret2addr不同的之處是 shellcode在返回地址的下面,而不是在棧幀裡。同時返回地址被覆蓋為JMP ESP這個指令的首地址。利用後的堆疊圖如下:
原來的返回地址被覆蓋為JMP ESP指令的首地址。
因為ret後 ,ESP加4,則此時ESP指向shellcode的首地址,而EIP指向了JMP ESP指令的首地址,執行JMP ESP後,EIP就指向了shellcode的首地址,這樣就會執行你的shellcode了。