堆溢位的unlink利用方法
0x00 背景
本文寫給對堆溢位無的放矢的童鞋,分為如下幾部分:
一.經典的unlink利用方法簡介
二.在當今glibc的保護下如何繞過進行unlink利用
建議閱讀本文之前先對glibc的malloc.c
有所瞭解
0x01 第一部分
首先簡要介紹一下堆chunk的結構
我們可以在malloc.c
中找到關於堆chunk
結構的程式碼
#!c
struct malloc_chunk {
INTERNAL_SIZE_T prev_size; /* Size of previous chunk (if free). */
INTERNAL_SIZE_T size; /* Size in bytes, including overhead. */
struct malloc_chunk* fd; /* double links -- used only if free. */
struct malloc_chunk* bk;
/* Only used for large blocks: pointer to next larger size. */
struct malloc_chunk* fd_nextsize; /* double links -- used only if free. */
struct malloc_chunk* bk_nextsize;
};
這指明瞭一個heap chunk
是如下的結構
#!c
+-----------+---------+------+------+-------------+
| | | | | |
| | | | | |
| prev_size |size&Flag| fd | bk | |
| | | | | |
| | | | | |
+-----------+---------+------+------+-------------+
如果本chunk
前面的chunk
是空閒的,那麼第一部分prev_size
會記錄前面一個chunk
的大小,第二部分是本chunk
的size
,因為它的大小需要8位元組對齊,所以size
的低三位一定會空閒出來,這時候這三個位置就用作三個Flag
(最低位:指示前一個chunk
是否正在使用;倒數第二位:指示這個chunk
是否是透過mmap
方式產生的;倒數第三位:這個chunk
是否屬於一個執行緒的arena
)。之後的FD和BK部分在此chunk
是空閒狀態時會發揮作用。FD指向下一個空閒的chunk
,BK指向前一個空閒的chunk
,由此串聯成為一個空閒chunk
的雙向連結串列。如果不是空閒的。那麼從fd開始,就是使用者資料了。(詳細資訊請參考glibc
的malloc.c
部分,在此不再多做解釋。)
首先,為了方便,我直接引用一位外國博主的漏洞示例程式,以便繼續解釋
#!c
/*
Heap overflow vulnerable program.
*/
#include <stdlib.h>
#include <string.h>
int main( int argc, char * argv[] )
{
char * first, * second;
/*[1]*/ first = malloc( 666 );
/*[2]*/ second = malloc( 12 );
if(argc!=1)
/*[3]*/ strcpy( first, argv[1] );
/*[4]*/ free( first );
/*[5]*/ free( second );
/*[6]*/ return( 0 );
}
這個程式在[3]處有很明顯的堆溢位漏洞,argv[1]
中的內容若過長則會越界覆蓋到second部分。
簡單給出此程式的堆結構
#!c
+---------------------+ <--first chunk ptr
| prev_size |
+---------------------+
| size=0x201 |
+---------------------+ <--first
| |
| allocated |
| chunk |
+---------------------+ <--second chunk ptr
| prev_size |
+---------------------+
| size=0x11 |
+---------------------+ <--second
| Allocated |
| chunk |
+---------------------+ <-- top
| prev_size |
+---------------------+
| size=0x205d1 |
+---------------------+
| |
| |
| |
| TOP |
| |
| CHUNK |
| |
+---------------------+
此處不贅餘介紹exploit具體程式碼,只介紹利用方法.
只要我們透過溢位構造,使得second chunk
#!c
prev_size=任意值
size=-4(因為最低位的flag沒有設定,所以prev_size是什麼值是無所謂了)
[email protected]([email protected]定技術”)
bk=shellcode地址
在我們的payload將指定位置的數值改好後。下面介紹在[4][5]行程式碼執行時發生的詳細情況。
第四行執行free(first)
發生如下操作
1).檢查是否可以向後合併
首先需要檢查previous chunk
是否是空閒的(透過當前chunk size
部分中的flag
最低位去判斷),當然在這個例子中,前一個chunk
是正在使用的,不滿足向後合併的條件。
2).檢查是否可以向前合併
在這裡需要檢查next chunk
是否是空閒的(透過下下個chunk
的flag的最低位去判斷),在找下下個chunk(這裡的下、包括下下都是相對於chunk first
而言的)的過程中,首先當前chunk+
當前size
可以引導到下個chunk
,然後從下個chunk
的開頭加上下個chunk
的size
就可以引導到下下個chunk
。但是我們已經把下個chunk
的size
覆蓋為了-4,那麼它會認為下個chunk
從prev_size
開始就是下下個chunk了,既然已經找到了下下個chunk
,那就就要去看看size
的最低位以確定下個chunk
是否在使用,當然這個size
是-4
,所以它指示下個chunk
是空閒的。
在這個時候,就要發生向前合併了。即first chunk
會和 first chunk
的下個chunk
(即second chunk
)發生合併。在此時會觸發unlink(second)
宏,想將second
從它所在的bin list
中解引用。
具體如下
#!c
BK=second->bk(在例子中bk實際上是shellcode的地址)
FD=second->fd ([email protected] - 12)
FD->bk=BK
/*shellcode的地址被寫進了FD+12的位置,[email protected],[email protected]*/
BK->fd=FD
執行unlink
宏之後,再呼叫free
其實就是呼叫shellcode
,這時就可以執行任意命令了。
但是,在現如今,glibc
已經不這麼簡單了,為了使堆溢位不那麼容易就被利用,它加入了許多新的保護措施,如何繞過也就是要在第二部分中討論的內容。
0x02 第二部分
以glibc中的程式碼作為示例,首先拿出最新版本的unlink宏。
#!c
1413 /* Take a chunk off a bin list */
1414 #define unlink(AV, P, BK, FD) {
1415 FD = P->fd;
1416 BK = P->bk;
1417 if (__builtin_expect (FD->bk != P || BK->fd != P, 0))
1418 malloc_printerr (check_action, "corrupted double-linked list", P, AV);
1419 else {
1420 FD->bk = BK;
1421 BK->fd = FD;
1422 if (!in_smallbin_range (P->size)
1423 && __builtin_expect (P->fd_nextsize != NULL, 0)) {
1424 if (__builtin_expect (P->fd_nextsize->bk_nextsize != P, 0)
1425 || __builtin_expect (P->bk_nextsize->fd_nextsize != P, 0))
1426 malloc_printerr (check_action,
1427 "corrupted double-linked list (not small)",
1428 P, AV);
1429 if (FD->fd_nextsize == NULL) {
1430 if (P->fd_nextsize == P)
1431 FD->fd_nextsize = FD->bk_nextsize = FD;
1432 else {
1433 FD->fd_nextsize = P->fd_nextsize;
1434 FD->bk_nextsize = P->bk_nextsize;
1435 P->fd_nextsize->bk_nextsize = FD;
1436 P->bk_nextsize->fd_nextsize = FD;
1437 }
1438 } else {
1439 P->fd_nextsize->bk_nextsize = P->bk_nextsize;
1440 P->bk_nextsize->fd_nextsize = P->fd_nextsize;
1441 }
1442 }
1443 }
1444 }
1445
1446 /*
我們可以看到我們最大的阻礙是下面的這部分程式碼
#!c
if (__builtin_expect (FD->bk != P || BK->fd != P, 0))
malloc_printerr (check_action, "corrupted double-linked list", P);
這段程式碼被新增到了unlink
宏中,所以現在再呼叫unlink
宏的時候,chunk
指標P->fd->bk
(即程式碼中的大寫FD->bk
)應該還是p指標自己。對於BK->fd != p
這部分也是同樣的道理。
在第一部分的利用方法中,我們修改了
#!c
p->[email protected]
p->bk=shellcode_adress
我們在此記FD=p->fd
, BK=p->bk
,再去看FD->bk
已經是[email protected]
了,這部分是不能滿足要求的。再看BK->fd
已經是shellcode+16
了,所以如上文的利用方法已經不能成功了。之所以還加以介紹,是因為這會使我們理解第二部分變得又快又好。
如果繞過還是要根據這段保護程式碼來談。我們勢必需要構造合適的條件的來過掉這行程式碼,那麼就要找一個指向p
的的已知的地址,然後根據這個地址去設定偽造的fd和bk指標就能改掉原p
指標。
以64bit為例,假設找到了一個已知地址的ptr是指向p(p指向堆上的某個地方)的,透過堆溢位,我們可以做如下的修改。
#!c
p->fd=ptr-0x18
p->bk=ptr-0x10
佈置好如此結構後,再觸發unlink宏,會發生如下情況。
#!c
1.FD=p->fd(實際是ptr-0x18)
2.BK=p->bk(實際是ptr-0x10)
3.檢查是否滿足上文所示的限制,由於FD->bk和BK->fd均為*ptr(即p),由此可以過掉這個限制
4.FD->bk=BK
5.BK->fd=FD(p=ptr-0x18)
這時候再對p進行寫入,可以覆蓋掉p原來的值,例如我們用合適的payload
將[email protected]
寫入。p就變成了[email protected]
,那麼再改一次p,把[email protected]
改為shellcode
的地址或者說system
的地址都可以。之後再呼叫free功能,就可以任意命令執行。
為了方便,在這邊拿出一個最近的wargame
出現的一個邏輯非常簡單的程式作為漏洞示例程式,可以在此下載
首先簡單介紹這個Binary的功能以及基本情況
開啟的保護
RELRO STACK CANARY NX PIE RPATH RUNPATH FILE
No RELRO No canary found NX enabled No PIE No RPATH No RUNPATH shellman
基本功能
1.顯示已經建立的堆塊中儲存的內容
2.建立一個新的堆塊,大小和內容又使用者決定
3.對一個已經分配的堆塊做編輯,這個地方沒有限制大小,若太長可造成堆溢位
4.釋放一個已經分配的堆塊
存放的堆塊的基本邏輯結構
.bss:00000000006016C0 ; __int64 usingFLAG[]
.bss:00000000006016C0 usingFLAG dq ? ; DATA XREF: main+38o
.bss:00000000006016C0 ; .text:0000000000400A90o ...
.bss:00000000006016C8 ; __int64 LEN[]
.bss:00000000006016C8 LEN dq ? ; DATA XREF: new+B5w
.bss:00000000006016C8 ; delete+79w
.bss:00000000006016D0 ; __int64 content[]
.bss:00000000006016D0 content dq ? ; DATA XREF: new+BCw
程式有一個全域性陣列會儲存好每一個經過malloc分配的堆塊返回的指標。以及在全域性陣列中儲存長度以及本塊是否正在使用的標誌。
如何利用
按照前文所介紹的,我們希望使用Unlink的方法去利用這個堆溢位漏洞。首先,我們要找一個指向堆上某處的指標。因為儲存malloc返回指標的全域性陣列的存在,這讓我們的利用變得異常的簡單。因為bss段的地址也是固定的,我們可以知道,從而設定滿足需要的bk和fd指標,下面介紹具體步驟。
1.我們可以首先分配兩個長度合適的堆塊。(如下圖所示)
chunk0 malloc返回的ptr chunk1 malloc返回的ptr
| | | |
+-----------+---------+---+---+-------------+------+------+----+----+------+
| | | | | | | | | | |
| | | | | | prev | size&| | | |
| prev_size |size&Flag| | | | size | flag | | | |
| | | | | | | | | | |
| | | | | | | | | | |
+-----------+---------+---+---+-------------+------+------+----+----+------+
這時候這兩塊的fd和bk區域其實都是空的,因為他們都是正在使用的
2.對第一塊進行編輯,編輯的過程中設定好第零塊的bk和fd指標並溢位第一塊,改好第一塊的chunk頭的控制資訊(如下圖所示)
chunk0 malloc返回的ptr chunk1 malloc返回的pt
| | | |
+-----------+---------+----+----+----+----+----+------+------+----+----+------+
| | |fake|fake|fake|fake| D | fake | fake | | | |
| | |prev|size| FD | BK | A | prev | size&| | | |
| prev_size |size&Flag|size| | | | T | size | flag | | | |
| | | | | | | A | | | | | |
| | | | | | | | | | | | |
+-----------+---------+----+----+----+----+----+------+------+----+----+------+
|--------new_size--------|
我們為了欺騙glibc,讓它以為堆塊零malloc返回的指標(我們後文中簡記為p)出就是chunk0指標,所以我們偽造了prev_size和size的部分,然後溢位堆塊1,改掉第1個堆塊的prev_size,數值應該是上圖所示new_size
的大小;另外第1塊的size部分還要把prev_inuse的flag給去掉。如此就做好了unlink觸發之前的準備工作
3.刪掉chunk1,觸發unlink(p),將p給改寫。
在刪除堆塊1時,glib會檢查一下自己的size部分的prev_inuse FLAG,發現到到比較早的一個chunk是空閒的(實際是我們偽造的),glibc希望將即將出現的兩個空閒塊合併。glibc會先將chunk0從它的Binlist中解引用,所以觸發unlink(p)。
1).FD=p->fd(實際是0x6016D0-0x18,因為全域性陣列裡面指向p的那個指標就是0x6016D0)
2).BK=p->bk(實際是6016D0-0x10)
3).檢查是否滿足上文所示的限制,由於FD->bk和BK->fd均為*6016D0(即p),由此可以過掉這個限制
4).FD->bk=BK
5).BK->fd=FD(p=0x6016D0-0x18)
4.對p再次寫入,[email protected]
[email protected],[email protected]實地址,進而算出libc的基址來過掉ASLR。
6.根據已經算出的libc基址再次算出system函式的真實[email protected] (如果沒有libc,可以考慮簡歷多個chunk,[email protected]面的函式,這樣在list時,我們可以得到兩個libc函式的真實地址,根據其偏移,便可以找出伺服器上的libc,若保護再夠複雜無法改got,我們還可以構造ropchain,同樣利用這樣的方式,把ropchain丟進全域性陣列中)
7.因為free已經變成了system,只要再建立一個內容為/bin/sh
的塊,再刪掉,就可以得到shell,由此全部利用完成。
相關文章
- Linux堆溢位漏洞利用之unlink2020-08-19Linux
- Linux kernel 堆溢位利用方法2024-10-18Linux
- Linux kernel 堆溢位利用方法(三)2024-11-27Linux
- Linux kernel 堆溢位利用方法(二)2024-11-11Linux
- ctf pwn中的unlink exploit(堆利用)2018-03-05
- 堆溢位之Overlapping2018-05-05APP
- 阿里大佬講解Java記憶體溢位示例(堆溢位、棧溢位)2020-12-12阿里Java記憶體溢位
- StackOverflowError堆疊溢位錯誤2024-07-05Error
- 堆溢位學習筆記2020-08-19筆記
- 堆疊溢位報錯引發的思考2019-03-03
- 棧溢位基礎及利用2021-03-09
- Redis 報”OutOfDirectMemoryError“(堆外記憶體溢位)2023-04-17RedisError記憶體溢位
- 棧溢位漏洞利用(繞過ASLR)2021-09-18
- win10黑屏了堆疊溢位怎麼辦_win10系統黑屏提示堆疊溢位解決教程2020-05-01Win10
- Java解決遞迴造成的堆疊溢位問題2024-08-13Java遞迴
- windows10下的堆結構及unlink分析2018-09-13Windows
- 【RTOS】FreeRTOS中的任務堆疊溢位檢測機制2021-12-23
- 異常、堆記憶體溢位、OOM的幾種情況2018-09-22記憶體溢位OOM
- N1CTF2018 shopping:多執行緒堆題中堆溢位的應用2024-05-14TF2執行緒
- 如何解決快應用堆疊溢位問題2021-07-30
- 緩衝區溢位漏洞的原理及其利用實戰2022-03-01
- 從CVE復現看棧溢位漏洞利用2024-04-12
- 溢位、上溢、下溢2019-03-22
- Vue專案中出現:Maximum call stack size exceeded(堆疊溢位)2020-03-04Vue
- 記一次公司JVM堆溢位抽絲剝繭定位的過程2020-07-20JVM
- 記一次公司JVM堆溢位抽繭剝絲定位的過程2020-10-14JVM
- html表格比較寬溢位的解決方法2020-05-20HTML
- 64位Linux下的棧溢位2020-08-19Linux
- Windows Tomcat 記憶體溢位解決方法2020-12-21WindowsTomcat記憶體溢位
- 直播平臺搭建,Java 記憶體溢位的排查方法2023-12-16Java記憶體溢位
- 整數溢位2024-08-19
- StackOverFlowError(棧溢位)2020-09-30Error
- 溢位 省略號 …2019-01-25
- tomcat記憶體溢位:PermGen space解決方法2021-01-02Tomcat記憶體溢位
- 自己挖的坑自己填--jxl進行Excel下載堆記憶體溢位問題2021-04-01Excel記憶體溢位
- MikroTik RouterOS 中發現了可遠端利用的緩衝區溢位漏洞2018-03-21ROS
- Unlink學習筆記(off-by-one null byte漏洞利用)2018-08-27筆記Null
- 聊聊資料溢位的事2022-07-09