一個簡單的緩沖區溢出的思考


  從大二開始真正接觸技術開始,從最早的HTML,PHP,WEB開發。一直以為以后可能會從事開發的工作,碰巧大三上的時候和同專業的郭子,鄒豪參加了南京的一個信息安全技能大賽,才真正找到了興趣的方向,也從懵懵懂懂開始懂了信安怎么學,像海賊王里面一樣,感覺到了新世界了(好吧,我又YY了......)

  最近參加了ISCC2013的比賽,歷時一個月的過程,雖然過程很辛苦,每天除了吃飯睡覺都在想題目(我不會告訴你我有一題是在睡覺的時候想出來的,做夢夢到單步調試....)。但是感覺收獲頗豐,貌似比以前牛逼了一點點。也因此萌生了要開一個技術博客,寫一點自己原創的東西,算是總結提高吧。

  在做溢出關第一題的時候,遇到了緩沖區溢出的問題,之前從沒接觸過這東西,匯編也不懂。於是各種查資料,看小甲魚的匯編視頻,總算是把原理搞明白了,希望在這里分享一些關於緩沖區溢出和匯編的基礎知識,小白一個,大神路過不要拍磚啊。

  首先看一段代碼:

#include<stdio.h>
void why_here(void) /*這個函數沒有任何地方調用過*/
{
  printf("why u here ?!\n");
  _exit(0);
}
int main(int argc,char * argv[])
{
  int buff[1];
  buff[2]=(int)why_here;
  return 0;
}

用GCC或者VC編譯運行一下,會發現why_here函數被運行了,地球人都知道這是緩沖區溢出,但是在底層代碼到底發生了什么呢?

原因是是因為錯誤的越界溢出賦值導致了函數返回地址被錯誤的覆蓋了,程序流執行到這里之后不是正常的返回而是執行了被覆蓋的地址。這樣說有點抽象,我們用VC進行debug一下,看看匯編代碼到底做了什么。

發生溢出的C代碼:

6:    int main(int argc,char * argv[])
7:    {
00401070   push        ebp
00401071   mov         ebp,esp
00401073   sub         esp,44h
00401076   push        ebx
00401077   push        esi
00401078   push        edi
00401079   lea         edi,[ebp-44h]
0040107C   mov         ecx,11h
00401081   mov         eax,0CCCCCCCCh
00401086   rep stos    dword ptr [edi]
8:        int buff[1];
9:        buff[2]=(int)why_here;
00401088   mov         dword ptr [ebp+4],offset @ILT+5(why_here) (0040100a)
10:       return 0;
0040108F   xor         eax,eax
11:   }
00401091   pop         edi
00401092   pop         esi
00401093   pop         ebx
00401094   mov         esp,ebp
00401096   pop         ebp
00401097   ret

這兩行是函數調用的常用語句,EBP是基址寄存器,目的是在進入函數前將EBP保護起來,一遍出來的時候可以恢復現場(計算機組成原理上的知識)。

下面這句  sub   esp,44h 應該就是為新進入的函數預留棧空間。接下來就是經典的三個push為main函數傳參。

............

我們重點看這行代碼:

00401088   mov     dword ptr [ebp+4],offset @ILT+5(why_here) (0040100a)

將why_here的函數入口基地址放入[ebp+4]中,這樣看也許看不出什么不妥,我們對比一下正常的情況。

下面是正規規范編寫的C代碼:

#include<stdio.h>
void why_here(void) /*這個函數沒有任何地方調用過*/
{
  printf("why u here ?!\n");
  _exit(0);
}
int main(int argc,char * argv[])
{
  int buff[1];
  buff[0]=(int)why_here;(因為buff只申請了一個int型,也就是4字節的內存空間,所以只能只能保存一個32位的函數RVA地址)
  return 0;
}

對應的匯編

6:    int main(int argc,char * argv[])
7:    {
00401070   push        ebp
00401071   mov         ebp,esp
00401073   sub         esp,44h
00401076   push        ebx
00401077   push        esi
00401078   push        edi
00401079   lea         edi,[ebp-44h]
0040107C   mov         ecx,11h
00401081   mov         eax,0CCCCCCCCh
00401086   rep stos    dword ptr [edi]
8:        int buff[1];
9:        buff[0]=(int)why_here;
00401088   mov         dword ptr [ebp-4],offset @ILT+5(why_here) (0040100a)
10:       return 0;
0040108F   xor         eax,eax
11:   }
00401091   pop         edi
00401092   pop         esi
00401093   pop         ebx
00401094   mov         esp,ebp
00401096   pop         ebp
00401097   ret

  我看到這里的時候就恍然大悟了,嗖嘎斯內。因為溢出賦值覆蓋了EIP(也就是指令寄存器,導致了函數返回后,程序的執行流被改變了)。

這里我們詳細討論一下:

進入main 函數后的棧內容下:
[ eip ][ ebp ][ buff[0] ]
高地址<---- 低地址

  因為在棧空間中,棧是向下生長的,而賦值是向上生長的。所以正常情況下[ebp-4]的賦值是正常的系統允許的,因為這個空間本來就系統為你分配好了。但是如果我們越界賦值[ebp+4]就會怎么?

  對!導致了EIP被覆蓋,即發生了緩沖區溢出,可能有朋友要問了,你這么說有什么依據呢?我們還是回到匯編代碼:

00401070   push        ebp
00401071   mov         ebp,esp
00401073   sub         esp,44h
00401076   push        ebx
00401077   push        esi
00401078   push        edi

在開頭的4個push入棧操作對應結尾了4個pop出棧操作。

00401091   pop         edi
00401092   pop         esi
00401093   pop         ebx
00401094   mov         esp,ebp
00401096   pop         ebp

00401097   ret

EBP被完好的保存了,注意到這里有個匯編指令ret,這是返回的意思,它的原理實質上等於2條匯編指令的組合:

pop EIP

jmp

將棧上的最底下的4字節空間彈棧賦值給EIP,並跳轉到EIP對應的那條指令執行。而這個所謂的棧底的4個空間的內容就是在進入main函數前入棧的,目的是為了等main函數執行完后可以繼續執行main之后的指令,這也就是一般的函數調用的原理(因為這里只有一個main函數,所有有些特殊,不過本質上一樣的)。

而所謂的溢出本質上是我們覆蓋了這段EIP對應的棧空間,然后操作系統就傻乎乎的以為這就是真實的返回地址,pop並執行了,然后就.........所以說童話里都是騙人的

這個程序很簡單,但是第一次分析的時候也花了我不少力氣,以后希望能更多的研究一些原理性的東西,也繼續和大家分享!

最后,希望這次的ISCC能玩的開心

  


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM