ARM彙編之內存損壞:堆棧溢出
引言
本篇文章適用於想要學習ARM彙編在漏洞方面的騷年們。特別是對那些對ARM平台上的漏洞利用寫作感興趣的人,本篇文章是根據azeria-labs.com上面的教程自學而成,總結出一些屬於自己的一些學習成果,特意分享給大家。
您可能已經注意到ARM處理器無處不在,就像我們日常用的Android、IOS系統都因了ARM處理器架構。當我環顧四周時,我可以計算出比我的處理器更多的家用ARM處理器的設備。這包括電話,路由器,不要忘記最近銷售似乎爆炸的物聯網設備。也就是說,ARM處理器已成為全球最廣泛的CPU核心之一。
這使我們認識到,像PC一樣,物聯網設備容易受到不正確的輸入驗證濫用,例如緩衝區溢出。鑒於基於ARM的設備的廣泛使用以及濫用的可能性,對這些設備的攻擊變得更加普遍。不管你是想學嵌入式、漏洞挖掘、Android開發等都是需要了解ARM,快來入手新的技能吧!
每次程序啟動的時候,為他分配的內存空間可以分為很多區段,大致歸為三類:
Program Image
Stack
Heap
棧溢出
這裡不介紹棧了,需要了解棧的詳細介紹可以看我的另一篇文章ARM彙編之棧,下面直接進入正題這裡使用的C代碼:
#include "stdio.h"
int main(int argc, char **argv)
{
char buffer[8];
gets(buffer);
}
當我們快要執行到gets函數的時候,我們看一下棧分布,紅框框起來的兩個棧地址,其首地址馬上要附給r0,然後作為參數傳進gets函數:
執行完gets函數,輸入7個a後,r0存放的那個棧地址剛好被寫滿,這裡需要留有1位元組空間讓gets寫入空字元(gets會自動補充空字元給輸入的字元串後面,所以如果出入的是8個a,那麼下面的幀指針地址的低8位就會被空字元覆蓋),表示一句字元串的結束。我們可以看見這些用戶輸入的字元存放的位置是很危險的,在他的下面就是上一個棧幀的幀指針,再往下就是當前棧幀的返回地址,也是當前棧幀的首地址。
返回地址?
,如果我們覆蓋了它,那還不想幹啥就幹啥。下面我們就具體多輸入幾個字元,比如輸入15個a再看看,可以看到覆蓋返回地址完成:
堆溢出:堆(Heap)是一個更複雜的內存區域
堆溢出主要分兩類:塊內堆溢出、塊間堆溢出,為什麼分為這兩種方式呢?主要是因為他的管理方式,堆會將每個對象打包成一個堆塊(eg:malloc,分配堆塊的函數)。所以就出現了兩個情況,一個堆塊內可以覆蓋相鄰變數的內容、相鄰堆塊的覆蓋堆塊結構:頭、用戶數據區。
塊內堆溢出
下面通過一個例子來幫助理解塊內堆溢出。主要是通過給一個有兩個變數的結構體申請堆塊,然後使用一個變數來覆蓋另一個變數,從而達到塊內堆溢出存在溢出的代碼:
#include
#include
struct struct_a
{
char string[8];
int number;
};
int main()
{
struct struct_a* objA = malloc(sizeof(struct struct_a));
objA->number = 1234;
gets(objA->string);
if(objA->number==1234)
{
puts("Memory valid!");
}else{
puts("Memory corrupted!");
}
return 0;
}
編譯後的結果,可以看到當輸入8位元組的數據的時候,結構體對象內部(堆塊內)發生了更改,造成變數number的值變了:
pi@raspberrypi:~/Desktop $ ./a.out
aaaa
Memory valid!
pi@raspberrypi:~/Desktop $ ./a.out
aaaaaaa
Memory valid!
pi@raspberrypi:~/Desktop $ ./a.out
aaaaaaaa
Memory corrupted!
詳細分析
先找到gets函數的位置,disassemble main反彙編程序,第一個bl是malloc申請堆塊,第二個bl就是執行gets函數了,然後下斷點,運行過去即可:
運行過去後,執行這個函數,正常輸入7位元組數據,即7個a,這個時候我們使用vmmap查看整個內存空間的映射,可以從下圖看到,這個堆塊的開始位置是0x00021000,然後使用x/8x查看堆後的8個雙字的數據。可以看到7個a成功寫入這個區域,並且緊隨其後的就是結構體的另一個int型變數number,很明顯如果不對輸入進行長度判斷,就會導致堆塊內另一個變數的內存數據進行覆蓋,下面具體操作一下。
這次驗證我們選擇輸入8個a,再次進行上面同樣的操作,和上面 的輸出對比,0x000004d2被改成了0x00000400,為什麼會這樣呢?因為 gets函數會自動向字元串結尾添加空字元並且堆塊內的這個字元數組變數被佔滿了,所以gets向下添加空字元,造成同一個堆塊內的另一個int型變數被覆蓋了。
gef> x/8x 21000
0x21000: 0x00000000 0x00000011 0x61616161 0x61616161
0x21010: 0x00000400 0x00020ff1 0x00000000 0x00000000
塊間堆溢出
這裡申請了兩個堆塊,一個字元型堆塊、一個int型堆塊:
#include
#include
int main()
{
char string = malloc(8);
int number = malloc(4);
*number = 1234;
gets(string);
if(*number == 1234)
{
puts("Memory vaild!");
}else
{
puts("Memory corrupted!");
}
return 0;
}
這裡我們展示一下,溢出覆蓋過程。第一次輸入15個a,int型堆塊內的數據正常、但是輸入16個a後,int型堆塊就被覆蓋了。
為什麼會這樣呢?
首先int型堆塊(4位元組)在char型堆塊後面(8位元組),當char型堆塊向後覆蓋時,需要先覆蓋int型堆塊的8位元組長度的堆頭,然後就覆蓋到了int型堆塊的用戶數據區,也就是至少需要16位元組數據才可以。
pi@raspberrypi:~/Desktop $ ./a.out
aaaaaaaaaaaaaaa
Memory vaild!
pi@raspberrypi:~/Desktop $ ./a.out
aaaaaaaaaaaaaaaa
Memory corrupted!
下面我們查看用戶輸入16位元組數據後的內存數據:
1.gdb ./a.out
2.disassemble main找到第三個ld就是gets的指令地址,然後下斷點,運行到斷點處,執行後輸入7位元組和16位元組,做對比
3.vmmap查找整個內存空間映射
4.x/8x 21000:查看申請堆塊內的數據輸入7位元組數據後,第一行是char型堆塊的堆頭和用戶數據輸入16位元組數據後,明顯int型堆塊的堆頭被覆蓋後,gets函數將空字元寫入到用戶數據空間,覆蓋了1234的低8位:
到此基本比較基礎的介紹了ARM彙編在堆棧溢出的運用。
參考文章
azeria-labs官方的Arm彙編教程
*文章原創作者:xiongchaochao,本文屬於FreeBuf原創獎勵計劃,未經許可禁止轉載
※PHP利用PCRE回溯次數限制繞過某些安全限制
※BabySploit:一個針對初學者的滲透測試框架
TAG:FreeBuf |