前言
昨天我們提到了較新版本的 libc 針對 unlink 所引入的一些機制,今天就進一步介紹這些機制的細節。
Corrupted double linked list
Corrupted double linked list 機制會檢查雙向鏈表(double linked list)是否被破壞,具體來說是檢查循環雙向鏈表(circular doubly linked list)的完整性。原則上,一個節點的前向指針與後向指針應該形成一個封閉的循環,如果鏈表被破壞,程式就會顯示 Corrupted double linked list,並且中斷運行。具體條件如下:
P->bk->fd == PP->fd->bk == P
Corrupted size vs. prev_size
Corrupted size vs. prev_size 機制主要是用來防止 size 或 prev_size 被篡改。這個檢查的邏輯可以表示為:
chunksize(P) == next_chunk(P)->prev_size
此機制是從 glibc 2.26 版本開始新增的。
How to bypass
儘管這些機制增加了攻擊的難度,但仍有可能進行攻擊,只是過程會更複雜。攻擊的核心思路是構造出符合檢查條件的假 chunk,整體流程大致如下:
- 偽造
chunk結構。 - 需要知道指向該
chunk的 pointer 及該指針的 address。- 由於能修改的區域有限,可能需要間接進行讀取或寫入操作。
- 構造
chunk size與next_chunk->prev_size一起偽造。 - 假設
r是第二塊chunk,且存在溢出問題,q是第三塊:- 先偽造
chunk,注意偽造r的size時需要扣掉 header 的大小。接著填入fd和bk,並繼續溢出到q的prev_size和size。當這些chunk被巧妙地佈置好後,r會指向我們偽造的chunk。 - 接著
free(q)。 - 此時檢查
q和r是否已被 free(可觀察相關的 flag)。 - 在接下來的操作中,會利用
q的prev_size計算出正確位置,發現r被視為一個chunk,因此進行unlink操作。 - 按照設計進行
unlink(r, FD, BK),具體過程為:FD = r->fd = &r - 0x18BK = r->bk = &r - 0x10
- 進行檢查:
prev_size2 == fake_size == 0x80r->fd->bk == r = *(&r - 0x18 + 0x18) = rr->bk->fd == r = *(&r - 0x10 + 0x10) = r
- 然後更新 pointer:
FD->bk = BK:*(&r - 0x18 + 0x18) == &r - 0x10
BK->fd = FD:*(&r - 0x10 + 0x10) == &r - 0x18
- 最後結束操作:
r通常會成為一個指向 data 的 pointer,因此可以利用r修改附近的 pointer,進而造成任意位置讀寫。如果指針附近有function pointer,甚至可以直接控制執行流程。
- 先偽造
現今較新的 libc 版本引入了更多的檢查機制,因此攻擊 glibc 變得更加困難,甚至在某些情況下已經無法達成。
總結
隨著 glibc 版本的更新,越來越多的檢查機制被引入來防止 heap overflow 攻擊,例如 Corrupted double linked list 和 Corrupted size vs. prev_size 機制。這些檢查增加了攻擊的難度,但透過偽造符合檢查條件的 chunk,仍然有可能進行利用。不過,在最新的 libc 版本中,這類攻擊變得更加困難甚至不可行,這也反映了現代系統在安全性上的逐步加強。