iT邦幫忙

2024 iThome 鐵人賽

DAY 11
0
Security

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

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

  • 分享至 

  • xImage
  •  

在上一篇文章中,我們提到延遲綁定時 dl-resolve(link_map, index) 將實際地址填入 .got.plt 的流程。

現在我們將繼續說明具體要如何操作此攻擊手法。此篇文章架構如下:

  • dl-resolve(link_map, index)
    • Step 1. 獲取表位址
    • Step 2. 由重定位表找到符號表
    • Step 3. 由符號表找到字串表
  • 攻擊手法 & 保護機制
    • Partial RELRO
    • Full RELRO

dl-resolve(link_map, index)

上篇文章的結尾有提到整個程式的流程如下:

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

為了更清楚了解攻擊流程,接下來我們將逐步搭配程式來詳細說明每個步驟的細節。

Step 1. 獲取表位址

image
我們之前透過 readelf -d 看過 ELF 檔案中的 .dynamic Section。實際上我們現在看到的內容在 ELF 檔中是以名為 Elf64_Dyn 的 Struct 的形式儲存,如下:

struct Elf64_Dyn { 
    Elf64_Sxword d_tag
    union {
        Elf64_Xword   d_val
        Elf64_Addr    d_ptr
    } d_un
}; 

readelf 的內容對應下來如下
image
所以第一步 dl-resolve 函數會使用 link_map 找到 Elf64_Dyn Struct 中 d_tag 為 0x5、0x6、0x17 的 d_ptr,來獲取這三個表(等同於上圖黃色的 Section)的位址。

Step 2. 由重定位表找到符號表

註:使用跟上一篇相同的例子說明,即需要延遲綁定的函數為 printf()

第二個參數 index,代表 printf() 的重定位表實際上是重定位表第幾組,例如:
image
index 當初是被設為 0,因此他現在是重定位表中的第一組。
如果呼叫 printf 之後又呼叫另一個外部函數,例如 scanf,就會像下面這樣:
image
index 被設為 1,且可以看到位於重定位表的第二組。

所以現在我們得知真正的重定位表位址為:JMPPEL(0x17) + index*一組的大小,我們稱呼它為 reloc

我們來看一下 reloc 的內容,重定位表的內容在 ELF 檔中以名為 Elf64_Rel 的 Struct 的形式儲存,如下:

struct Elf64_Rel {
    Elf64_Addr     r_offset;
    Elf64_Xword    r_info;
}; 

其中 reloc -> r_offset.got.plt 的位址,而 .got.plt 的位址上會儲存最後拿到的記憶體實際位址。
reloc -> r_info 的前 8 bytes 用於儲存符號表的索引值,後 8 bytes 代表類型。可以透過 readelf -r 查看,如下:
image
可以看到 Offset 即 reloc -> r_offset 就是.got.plt 的位址,而 Info 即為 reloc -> r_info,前 8 bytes 為 0x00000002(前面補上4個0)、後 8 為 0x00000007,代表 R_X86_64_JUMP_SLO 類型。
我們可以看到上面的資訊表示 printf() 的符號表位於符號表中 Index 為 2 的位置,如下:
image

Step 3. 由符號表找到字串表

從剛剛的 reloc 已經找到 printf() 的符號表實際上位於索引值 2 號,我們稱呼這個實際上的符號表為 sym

來看一下 sym 的內容,它以名為 Elf64_Sym 的 Struct 的形式儲存

struct Elf64_Sym {
  Elf64_Word     st_name;     // Symbol name (index into string table)
  unsigned char  st_info;     // Symbol's type and binding attributes
  unsigned char  st_other;    // Must be zero; reserved
  Elf64_Half     st_shndx;    // Which section (header tbl index) it's defined in
  Elf64_Addr     st_value;    // Value or address associated with the symbol
  Elf64_Xword    st_size;     // Size of the symbol
}

其中 sym -> st_name 儲存字串表的位移,也就是說字串表的開頭 STRTAB(0x5) + sym -> st_name = "printf" 的位址。

最後 dl-resolve 會使用這個字串去尋找實際位址,然後儲存至 reloc -> r_offset.got.plt 的位址。

攻擊手法 & 保護機制

依據上述的流程,可以看出我們只要篡改字串表的內容,例如直接把 printf 改為 system,當程式在延遲綁定 printf() 時,實際上會將 system() 的位址寫入到 .got.plt。這樣,雖然原始的指令是呼叫 printf(),但由於綁定過程中相應位置的字串表被篡改,程式會執行 system(),從而劫持控制流程並執行任意命令。

Partial RELRO

所以既然是藉由篡改內容而造成的攻擊,那就讓那部份的記憶體位址不能被寫就好了,所以就有了 RELRO(ReLocation Read-Only) 的機制,除了 .got.plt 設為可寫,讓實際地址可以寫上以外,其他所有相關 Sections,像是 .dynamic.got 都只可讀。這樣攻擊者就不能篡改字串了。

那不能改字串表就不能攻擊了嗎?只要能控制輸入,就可能發動攻擊,還記得我們提到的第二個參數 index 嗎?

如果我們控制 index,讓程式認為重定位表位於一個可寫區段,並且我們在此可寫區段寫一個假的 relocsym 以及字串表,是不是就能達成攻擊了呢?
所以整個攻擊思路如下:

  1. 控制 index,使得 reloc 位於可寫入位址。
  2. 於此位於可寫入位址創建假的 reloc,使得 reloc -> r_info 能自己控制,因此能於此創建假的 sym
  3. 於假的 sym -> st_name 寫入需要的字串,如 system

如此一來,我們就能成功獲取控制權了。

Full RELRO

從上面可以發現,此攻擊的根本在於延遲綁定的機制,因此 Full RELRO 保護機制是直接取消延遲綁定這個功能,程式一開始就直接全部綁定了。
因此阻絕了所有利用延遲綁定功能的攻擊,例如 ret2dlresolve。


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

尚未有邦友留言

立即登入留言