---
# System prepended metadata

title: OVERFLOW
tags: [技術, 資安]

---

# OVERFLOW

## 算數溢位
算術溢位(arithmetic overflow)，通常直接簡稱overflow，當你目前使用的這塊記憶體空間不足以容納你要運算的數字，就會發生。而如果是有號數(正負號)，數字小於這個記憶體能儲存的最小值，則是叫做underflow。
什麼意思呢？請看以下程式碼：
```clike=
int foo;
foo = 2147483648;
printf("%d", foo);
```
```
輸出結果：-2147483648
```

目前的C編譯器預設int(整數)型別的變數，所能儲存的資料量為4個byte。一個byte = 8 bits，所以說4*8=32，他可以儲存32個二進位的數字。而這段程式碼宣告的int是有號數，所以最高位元數字用來表示這個數字的正負，當它是0的時候表示正數，1的時候表示負數。
例如：
1000 0000 0000 0000 0000 0000 0000 0001 = 1 - (2^31) = -2,147,483,647
1111 1111 1111 1111 1111 1111 1111 1111 = 1 + 2 + 4 + 8 + 16 + ...... - (2^31) = -1

這裡我們先快速學習一下二進位換算十進位的方式：
![](https://i.imgur.com/mqEpKC5.png "備援網址 https://images.plurk.com/hCvwxAjIUStfzBFX66mrk.png")

好，所以上面的程式碼發生了什麼事？

第一行，我們宣告了一個名字叫做"foo"的變數(它是int型別的變數，只能儲存32個bits，而且是有號數)。
```clike=
int foo;
```

第二行，我們硬是在這個變數裡塞進了大於它的最大值的數值。
```clike=
foo = 2147483648;
```
這邊解釋一下，預設的有號整數(signed integer)最大值為：
0111 1111 1111 1111 1111 1111 1111 1111 = 1 + 2 + 4 + 8 + 16 + ...... + (2^30) = (2^31) - 1 = 2,147,483,647

第三行，輸出變數裡的數值到螢幕上。(那個%d的意思是"我要以十進位整數的方式顯示它")
```clike=
printf("%d", foo);
```

這個時候顯示的數字就會依照編譯器不同而改變，但總之不會是你預期的結果。

順帶一提，signed int的最小值為：
1000 0000 0000 0000 0000 0000 0000 0000 = -(2^31) = -2,147,483,648

在這個例子中，塞進了大於最大值的數值之後，顯示出來的反而是最小值。

## 堆疊溢位
堆疊溢位(stack overflow)，則是另一個C語言新手很容易……還是應該說一定會犯過的錯誤？
我們看一下[維基百科](https://zh.wikipedia.org/wiki/%E5%A0%86%E7%96%8A%E6%BA%A2%E4%BD%8D)的解釋：
> 堆疊溢位（英語：stack overflow）在電腦科學中是指使用過多的記憶體時導致呼叫堆疊產生的溢位[1]，也是緩衝區溢位中的一種。堆疊溢位的產生是由於過多的函式呼叫，導致使用的呼叫堆疊大小超過事先規畫的大小，覆蓋其他記憶體內的資料，一般在遞迴中產生。堆疊溢位很可能由無限遞迴（Infinite recursion）產生，但也可能僅僅是過多的堆疊層級。

其實我覺得維基寫得很清楚，這邊就針對專有名詞大概解釋一下。

### 函式呼叫
函式呼叫(function call)，首先必須知道函式是什麼。
```clike=
int foo(int a)
{
    return a + 1;
}

int main()
{
    printf("%d", foo(2));
}
```
```
輸出結果：3
```

上面這段code中，我們宣告了一條叫做foo的函式，它需要你在呼叫時輸入一個叫做a的整數作為參數，並且會回傳一個a+1的數值給你。例如你丟了a=2給它，它就會給你3。等同於數學中的方程式 `f(x) = x + 1`。

而function call就只是這個呼叫的動作──請看第八行的`foo(2)`，這幾個字的意思就是「我要呼叫foo函式，並且給它一個參數2」。也就是在 `f(x) = x + 1` 中，將x帶入2，變成 `f(2) = 2 + 1`。

### 遞迴
遞迴(recursion)，我們在初學程式的時候常常被教授說這東西很危險，不要用XD
它基本上就是「在函式當中呼叫自己」的一種用法。
例如：
```clike=
int foo()
{
    return foo();
}
```
而這段程式碼就是一個絕對會引發stack overflow的範例。

遞迴的應用，最經典的有計算階乘與尋找最大公因數。
這裡以計算階乘來做示範：
```clike=
unsigned int factorial(unsigned int n)
{
    if (n == 1)
        return 1;
    else
        return n * factorial(n - 1);
}
```

假設n=5，這段程式碼會在進行到第六行時執行5＊factorial(4)，接著它會暫時把手邊的工作擱置，先去執行factorial(4)；在factorial(4)執行到第六行時，又會去執行factorial(3)……以此類推，一直到執行factorial(1)的時候，因為它符合第三行`if n == 1`這個條件(雙等於是判斷左值是否等於右值的意思)，所以它會直接回傳1這個數值回去。
回去哪裡呢？回去factorial(2)，執行2＊factorial(1)這個動作，而factorial(1)已經計算完並得到1這個數值，所以這裡會是2＊1=2，並且回傳2這個數值；再來就會回到factorial(3)，執行3＊factorial(2)，也就是3＊2……以此類推，一直到回去最初的factorial(5)時，後面那坨東西應該會是4＊3＊2＊1=24，所以這裡計算並回傳一個5＊24=120，函式就結束了，我們算出了一個5階乘。

一個很重要的點是，n會在每次call function時-1，減到n=1時這支函式就不再繼續call自己──這個"條件"使它可以免於陷入無限遞迴。如果無法觸發這個條件，或是一開始就沒設定條件，它就會永無止盡地call自己，造成stack overflow。

### 堆疊
堆疊(stack)，這裡讓我們回過頭看一下上一段的內容：
> 假設n=5，這段程式碼會在進行到第六行時執行5＊factorial(4)，**接著它會暫時把手邊的工作擱置**，先去執行factorial(4)……

請看粗體的部分，這裡提到了你的程式會暫時擱置手邊的工作。那麼擱置的工作該何去何從？它會被存進堆疊裡。
![](https://i.imgur.com/BOTxeCN.png "備援網址 https://images.plurk.com/2ORDrKTMO28im43fbSrNo0.png")
準確來說是它們的記憶體位址會被存進堆疊裡，當目前的函式執行完畢後，程式會去堆疊查看前一個函式的位址在哪，然後回到那裡去。

所以當你的程式出錯，無限增加堆疊時，stack overflow就發生了。這可能會導致你的程式去調用了他不該碰到的記憶體區塊，例如碰到保留給作業系統用的區域導致當機，或是碰到存有使用者隱私資料的記憶體區段導致資安問題──當然現在的編譯器會防呆，如果發生stack overflow，它會強制中止你的程式。

## 緩衝溢位
緩衝溢位(buffer overflow)，不同於前兩種溢位只是單純的程式設計BUG，buffer overflow就是一種確實出自惡意的攻擊行為了。

前面提過C語言當中的變數都有其儲存資料量的限制，其中儲存字串的變數是必須在宣告時就一併宣告它的記憶體大小的。
例如：
```clike=
char text[] = "hello";
```
這個字串的長度就會是5個字母+一個結尾符號=6個byte。

buffer overflow簡單來說是刻意塞進超過字串長度的內容，此時你輸入的文字就有機率覆蓋掉原先stack中的其他資料，惡意使用的話有可能進一步改變整支程式的執行。(通常是以[Shellcode](https://zh.wikipedia.org/wiki/Shellcode)方式來進行)

我曾經在觀察IPS(入侵預防系統)的LOG時看過幾次進行這種攻擊的封包，它偵測的準則是封包payload中出現"0x0C0C0C0C"字串。這個攻擊手法叫做[Heap Spray](https://blog.csdn.net/magictong/article/details/7391397)，~~我要老實說我其實也沒完全弄懂這個手法。~~

關於buffer overflow，更清楚的解說：[[Day23] 攻擊行為－緩衝區溢位 Buffer Overflow](https://ithelp.ithome.com.tw/articles/10188599)