iT邦幫忙

2024 iThome 鐵人賽

DAY 10
0
Security

Pwn2Noooo! 執行即 Crash 的 PWNer 養成遊戲系列 第 10

[Day10] Stack 攻擊手法 - ret2dlresolve & 保護機制 - RELRO(上)

  • 分享至 

  • xImage
  •  

上一篇文章中,我們介紹了 ret2libc 的攻擊手法,我們藉由 Leak 出實際地址來繞過 ASLR 保護,成功使用 ROP 獲得一個 Shell 並 Pwn 下目標。在過程中,讀者可能注意到一個關鍵點,就是 Leak Address 時程式必須要有輸出函數(如puts)與 pop rdi; ret; 這個 Gadget,藉此操控輸出函數的第一個參數,讓他輸出 GOT 存的實際位置。

然而如果程式達不到這些條件怎麼辦呢?本篇文章將介紹即使無法 Leak Address 也能獲得控制權的攻擊手法,稱為 ret2dlresolve。

ret2dlresolve

為什麼此攻擊手法不用 Leak Address 就能執行一個 Shell 呢?
此攻擊的概念為:去記憶體的可寫區段寫一些假資料,當動態載入器要參考資料去解析某個函數的真實位址時,參考成我們寫的假資料,導致它被假資料欺騙把某個函數的真實位址解析成 system() 等我們需要的函數的真實位址,因此即使不用 Leak Address 我們也能執行 system() 了。

為了要欺騙動態載入器,我們必須先了解它是如何解析出函數的真實位址的。

註:x86 架構與 x86-64 架構在這部份的實作有些微差異,因此最後攻擊的 Payload 也會因架構不同有些許變化,本篇文章僅以 x86-64 架構說明。

延遲綁定(Lazy Binding)流程

image
我們再看一次前一篇文章介紹延遲綁定的例子,當時有提到,當程式第一次呼叫 printf() 這個外部函式時,它會透過 .plt Section 中的對應項目來跳轉到一段動態連結的程式碼,這段程式碼會啟動動態連結器,負責解析 printf() 的實際位址。現在,我們實際追進 printf@plt 看看。
image
可以看到 printf@plt 只有三行。第一行的 jmp 其實是跳到 GOT,如果已經呼叫過 printf() 這個 GOT 存的值就會是 printf() 的實際位址,也就是上一篇文章 Leak 出的內容。但是,如果 printf() 沒有被呼叫過,GOT 的內容即為 printf@plt 的第二行(0x401036 <printf@plt+6> push 0)位址。

我們前面為了方便解釋,都稱呼它為 GOT,然而其實 GOT 是由 .got.got.plt 這兩段 ELF Sections 所組成。說明如下:

  • .got:存全域變數的實際位址
  • .got.plt:初始時,存 .plt 的第二行位址,首次函數解析之後,會更新為函數的實際位址

後續將會精確的稱呼他為 .got.plt

由於我們現在是第一次呼叫 printf(),因此現在 .got.plt 內容為 .plt 的第二行位址,所以現在會看起來像沒跳轉一樣繼續往下執行:
image
可以看到他最後跳轉到一個名為 _dl_runtime_resolve_xsavec() 的函數。

補充:此段程式是以 x86 架構的組合語言撰寫的,由於之前都是在 x86-64 架構(64bits)中練習,這是第一次遇到 x86 架構,除了暫存器能處理的 bits 不同以外,他的呼叫慣例也與 x86-64 不同。差別主要在於傳參數的方式:

  • x86-64:函數的參數會依序存放在 rdi、rsi、rdx、rcx、r8、r9 等暫存器中,如果參數超過6個,從第7個參數開始會存放在 Stack 中
  • x86:參數使用 Stack 儲存,呼叫者會從右到左依序將參數 Push 進 Stack 中,也就是說第一個參數會在 Stack 最上層,被呼叫者會再依序將參數 Pop 出來

由於此段程式是以 x86 寫的,所以這段指令是把兩個參數給 _dl_runtime_resolve_xsavec(link_map, index)
(只有此段是以 x86,進入函式後都還是 x86-64 架構)
image

其中,第一個參數 link_map 是一個結構指標,此結構儲存許多動態載入時需要參考的資訊,這些資訊中最重要的就是 ELF 檔案中名為 .dynamic 的 Section。

我們使用 readelf -d 來看一下 .dynamic 這個 Section 究竟存了什麼資訊:
image
每一行都代表一種類別的資料表,其中跟尋找函數實際位址有關的資料表,我們需要知道的只有圈起來的三種:

  • STRTAB:字串表,用於儲存符號的字串,例如儲存 printf() 的 printf、system() 的 system
  • SYMTAB:符號表,用於儲存符號的資訊,例如 printf 這個值位於字串表的哪裡(以偏移量表示)以及大小、類型等等
  • JMPREL:重定位表,用於儲存符號在記憶體中真正的位址,以及儲存此符號位於符號表的哪裡(以 index 表示:0x0 為符號表中的第一組、0x1 為符號表中的第二組...)

了解上面三種資料表的用途後,我們就能說明 _dl_runtime_resolve_xsavec(link_map, index) 具體做了什麼將實際位址填入 got.plt:

  1. 透過第一個參數 link_map 獲得上面三種表的位址
  2. 透過第二個參數 index ,由重定位表找到符號表
  3. 透過符號表找到字串表中的字串
  4. 透過找到的字串去 libc 找到這個函數在記憶體中的位置
  5. 將這個找到的位址寫回到 .got.plt

下篇文章將會依據這個流程說明如何操作 ret2dlresolve


上一篇
[Day9] Stack 攻擊手法 - ROP:動態連結
下一篇
[Day11] Stack 攻擊手法 - ret2dlresolve & 保護機制 - RELRO(中)
系列文
Pwn2Noooo! 執行即 Crash 的 PWNer 養成遊戲30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言